diff --git a/.backportrc.json b/.backportrc.json index af064451595c8..77456cf33f625 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,5 +1,5 @@ { "upstream": "elastic/kibana", - "branches": [{ "name": "7.x", "checked": true }, "7.5", "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], + "branches": [{ "name": "7.x", "checked": true }, "7.6", "7.5", "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], "labels": ["backport"] } diff --git a/.eslintignore b/.eslintignore index 90155ca9cb681..c4fb806b6d394 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,14 +9,14 @@ bower_components /built_assets /html_docs /src/plugins/data/common/es_query/kuery/ast/_generated_/** -/src/fixtures/vislib/mock_data +src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data /src/legacy/ui/public/angular-bootstrap /src/legacy/ui/public/flot-charts /test/fixtures/scenarios /src/legacy/core_plugins/console/public/webpackShims /src/legacy/core_plugins/console/public/tests/webpackShims /src/legacy/ui/public/utils/decode_geo_hash.js -/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.* +/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.* /src/core/lib/kbn_internal_native_observable /packages/*/target /packages/eslint-config-kibana diff --git a/.eslintrc.js b/.eslintrc.js index a7bb204da4775..2c5804da053a6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -77,7 +77,7 @@ module.exports = { }, }, { - files: ['src/legacy/core_plugins/kbn_vislib_vis_types/**/*.{js,ts,tsx}'], + files: ['src/legacy/core_plugins/vis_type_vislib/**/*.{js,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index acfb7307f49c4..ed5721e8756e8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -117,21 +117,21 @@ /x-pack/test/api_integration/apis/security/ @elastic/kibana-security # Kibana Localization -/src/dev/i18n @elastic/kibana-localization +/src/dev/i18n/ @elastic/kibana-localization # Pulse /packages/kbn-analytics/ @elastic/pulse /src/legacy/core_plugins/ui_metric/ @elastic/pulse /src/plugins/usage_collection/ @elastic/pulse -/x-pack/legacy/plugins/telemetry @elastic/pulse +/x-pack/legacy/plugins/telemetry/ @elastic/pulse # Kibana Alerting Services -/x-pack/legacy/plugins/alerting @elastic/kibana-alerting-services -/x-pack/legacy/plugins/actions @elastic/kibana-alerting-services -/x-pack/legacy/plugins/task_manager @elastic/kibana-alerting-services -/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/alerting/ @elastic/kibana-alerting-services +/x-pack/legacy/plugins/actions/ @elastic/kibana-alerting-services +/x-pack/plugins/task_manager/ @elastic/kibana-alerting-services +/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 diff --git a/.i18nrc.json b/.i18nrc.json index 4bc0f773ee8b5..73acf92cda149 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -21,7 +21,7 @@ "interpreter": "src/legacy/core_plugins/interpreter", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", + "kbnVislibVisTypes": "src/legacy/core_plugins/vis_type_vislib", "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", @@ -33,7 +33,7 @@ "statusPage": "src/legacy/core_plugins/status_page", "telemetry": "src/legacy/core_plugins/telemetry", "tileMap": "src/legacy/core_plugins/tile_map", - "timelion": "src/legacy/core_plugins/timelion", + "timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", diff --git a/.sass-lint.yml b/.sass-lint.yml index f6c0f5bb83fcb..fba2c003484f6 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -2,7 +2,7 @@ files: include: - 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss' - - 'src/legacy/ui/public/vislib/**/*.s+(a|c)ss' + - 'src/legacy/core_plugins/vis_type_vislib/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 412019efc7f35..c46ba8a980ce2 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -23,9 +23,9 @@ Want to export multiple workpads? Go to the *Canvas workpads* view, select the w [[create-workpad-pdf]] === Create a PDF -Create a PDF copy of your workpad that you can save and share outside of {kib}. +If you have a license that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. +For more information, refer to <>. . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. @@ -38,12 +38,10 @@ image::images/canvas-generate-pdf.gif[Generate PDF] [[create-workpad-URL]] === Create a POST URL -Create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. +If you have a license that supports the {report-features}, you can create a POST URL that you can use to automatically generate PDF reports using Watcher or a script. For more information, refer to <>. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. - . From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. . Click *Copy POST URL*. @@ -57,8 +55,6 @@ image::images/canvas-create-URL.gif[Create POST URL] beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. If you are using a Gold or Platinum license, enable reporting in your `config/kibana.yml` file. - . From your workpad, click the *Share this workpad* icon in the upper left corner, then select *Share on a website*. . On the *Share on a website* pane, follow the instructions. diff --git a/docs/developer/visualize/development-create-visualization.asciidoc b/docs/developer/visualize/development-create-visualization.asciidoc index b782428b83135..faaa9b36a7a00 100644 --- a/docs/developer/visualize/development-create-visualization.asciidoc +++ b/docs/developer/visualize/development-create-visualization.asciidoc @@ -208,8 +208,8 @@ This is the sidebar editor you see in many of the Kibana visualizations. You can [[development-default-editor]] ==== `default` editor controller -The default editor controller receives an `optionsTemplate` or `optionsTabs` parameter. -These can be either an AngularJS template or React component. +The default editor controller receives an `optionsTemplate` or `optionTabs` parameter. +These tabs should be React components. ["source","js"] ----------- @@ -220,12 +220,9 @@ These can be either an AngularJS template or React component. description: 'Cool new chart', editor: 'default', editorConfig: { - optionsTemplate: '' // or optionsTemplate: MyReactComponent // or if multiple tabs are required: - optionsTabs: [ - { title: 'tab 1', template: '
....
}, - { title: 'tab 2', template: '' }, - { title: 'tab 3', template: MyReactComponent } + optionTabs: [ + { title: 'tab 3', editor: MyReactComponent } ] } } diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md index 2deb9f4a9a151..d1cd2d3b04950 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlinks.update.md @@ -1,25 +1,30 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) - -## ChromeNavLinks.update() method - -Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. - -Signature: - -```typescript -update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| id | string | | -| values | ChromeNavLinkUpdateableFields | | - -Returns: - -`ChromeNavLink | undefined` - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) > [update](./kibana-plugin-public.chromenavlinks.update.md) + +## ChromeNavLinks.update() method + +> Warning: This API is now obsolete. +> +> Uses the [AppBase.updater$](./kibana-plugin-public.appbase.updater_.md) property when registering your application with [ApplicationSetup.register()](./kibana-plugin-public.applicationsetup.register.md) instead. +> + +Update the navlink for the given id with the updated attributes. Returns the updated navlink or `undefined` if it does not exist. + +Signature: + +```typescript +update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| id | string | | +| values | ChromeNavLinkUpdateableFields | | + +Returns: + +`ChromeNavLink | undefined` + 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 cbda9abead9d1..9e662c543eb56 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 c43569e24c63e..cefac180d88c5 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 64cbdd880fed1..27ca9f2d9fd57 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.deprecationsettings.doclinkskey.md b/docs/development/core/server/kibana-plugin-server.deprecationsettings.doclinkskey.md new file mode 100644 index 0000000000000..4296d0d229988 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.deprecationsettings.doclinkskey.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) > [docLinksKey](./kibana-plugin-server.deprecationsettings.doclinkskey.md) + +## DeprecationSettings.docLinksKey property + +Key to documentation links + +Signature: + +```typescript +docLinksKey: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.deprecationsettings.md b/docs/development/core/server/kibana-plugin-server.deprecationsettings.md new file mode 100644 index 0000000000000..64a654c1bcea8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.deprecationsettings.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) + +## DeprecationSettings interface + +UiSettings deprecation field options. + +Signature: + +```typescript +export interface DeprecationSettings +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [docLinksKey](./kibana-plugin-server.deprecationsettings.doclinkskey.md) | string | Key to documentation links | +| [message](./kibana-plugin-server.deprecationsettings.message.md) | string | Deprecation message | + diff --git a/docs/development/core/server/kibana-plugin-server.deprecationsettings.message.md b/docs/development/core/server/kibana-plugin-server.deprecationsettings.message.md new file mode 100644 index 0000000000000..ed52929c3551e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.deprecationsettings.message.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) > [message](./kibana-plugin-server.deprecationsettings.message.md) + +## DeprecationSettings.message property + +Deprecation message + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.imagevalidation.maxsize.md b/docs/development/core/server/kibana-plugin-server.imagevalidation.maxsize.md new file mode 100644 index 0000000000000..9b03924ad2e0f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.imagevalidation.maxsize.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ImageValidation](./kibana-plugin-server.imagevalidation.md) > [maxSize](./kibana-plugin-server.imagevalidation.maxsize.md) + +## ImageValidation.maxSize property + +Signature: + +```typescript +maxSize: { + length: number; + description: string; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-server.imagevalidation.md b/docs/development/core/server/kibana-plugin-server.imagevalidation.md new file mode 100644 index 0000000000000..8d81a7eae1915 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.imagevalidation.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ImageValidation](./kibana-plugin-server.imagevalidation.md) + +## ImageValidation interface + +Signature: + +```typescript +export interface ImageValidation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [maxSize](./kibana-plugin-server.imagevalidation.maxsize.md) | {
length: number;
description: string;
} | | + diff --git a/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md index 1bc78dd84571d..42cbc59c536a6 100644 --- a/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md +++ b/docs/development/core/server/kibana-plugin-server.iscopedrenderingclient.render.md @@ -9,14 +9,14 @@ Generate a `KibanaResponse` which renders an HTML page bootstrapped with the `co Signature: ```typescript -render(options?: IRenderOptions): Promise; +render(options?: Pick): Promise; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| options | IRenderOptions | | +| options | Pick<IRenderOptions, 'includeUserSettings'> | | Returns: diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 5e28643843af3..00ab83123319a 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -56,6 +56,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DeprecationAPIClientParams](./kibana-plugin-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-server.deprecationapiresponse.md) | | | [DeprecationInfo](./kibana-plugin-server.deprecationinfo.md) | | +| [DeprecationSettings](./kibana-plugin-server.deprecationsettings.md) | UiSettings deprecation field options. | | [DiscoveredPlugin](./kibana-plugin-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | | [ElasticsearchError](./kibana-plugin-server.elasticsearcherror.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | @@ -69,6 +70,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ICspConfig](./kibana-plugin-server.icspconfig.md) | CSP configuration for use in Kibana. | | [IKibanaResponse](./kibana-plugin-server.ikibanaresponse.md) | A response data object, expected to returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution | | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | +| [ImageValidation](./kibana-plugin-server.imagevalidation.md) | | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | | [IRenderOptions](./kibana-plugin-server.irenderoptions.md) | | | [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | @@ -140,6 +142,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [StringValidation](./kibana-plugin-server.stringvalidation.md) | | | [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | | | [UiSettingsServiceStart](./kibana-plugin-server.uisettingsservicestart.md) | | diff --git a/docs/development/core/server/kibana-plugin-server.stringvalidation.md b/docs/development/core/server/kibana-plugin-server.stringvalidation.md new file mode 100644 index 0000000000000..cc52c853ce248 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.stringvalidation.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [StringValidation](./kibana-plugin-server.stringvalidation.md) + +## StringValidation interface + +Signature: + +```typescript +export interface StringValidation +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [message](./kibana-plugin-server.stringvalidation.message.md) | string | | +| [regexString](./kibana-plugin-server.stringvalidation.regexstring.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-server.stringvalidation.message.md b/docs/development/core/server/kibana-plugin-server.stringvalidation.message.md new file mode 100644 index 0000000000000..a15fe8b931403 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.stringvalidation.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [StringValidation](./kibana-plugin-server.stringvalidation.md) > [message](./kibana-plugin-server.stringvalidation.message.md) + +## StringValidation.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.stringvalidation.regexstring.md b/docs/development/core/server/kibana-plugin-server.stringvalidation.regexstring.md new file mode 100644 index 0000000000000..e19560237f77d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.stringvalidation.regexstring.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [StringValidation](./kibana-plugin-server.stringvalidation.md) > [regexString](./kibana-plugin-server.stringvalidation.regexstring.md) + +## StringValidation.regexString property + +Signature: + +```typescript +regexString: string; +``` diff --git a/docs/discover/document-data.asciidoc b/docs/discover/document-data.asciidoc index b45a31065aa9a..6e9218d66c115 100644 --- a/docs/discover/document-data.asciidoc +++ b/docs/discover/document-data.asciidoc @@ -15,7 +15,7 @@ tailor the documents table to suit your needs. [horizontal] Add a field column:: -Hover over the list of *Available fields* and then click *add* next to each field you want include as a column in the table. +Hover over the list of *Available fields* and then click *add* next to each field you want to include as a column in the table. The first field you add replaces the `_source` column. Change sort order:: By default, columns are sorted by the values in the field. If a time field is configured for the current index pattern, diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 757c6f10f2a99..695a4d4f45b02 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/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 0ad27e68f7fe9..ecb550d3ab267 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -25,9 +25,9 @@ At the end of the trial period, the platinum features operate in a <>. You can revert to a basic license, extend the trial, or purchase a subscription. -TIP: If {security-features} are enabled, before you revert to a basic license or -install a gold or platinum license, you must configure Transport Layer Security -(TLS) in {es}. See {ref}/encrypting-communications.html[Encrypting communications]. +TIP: If {security-features} are enabled, unless you have a trial license, +you must configure Transport Layer Security (TLS) in {es}. +See {ref}/encrypting-communications.html[Encrypting communications]. {kib} and the {ref}/start-basic.html[start basic API] provide a list of all of the features that will no longer be supported if you revert to a basic license. diff --git a/docs/maps/images/extended_stats_config.png b/docs/maps/images/extended_stats_config.png new file mode 100644 index 0000000000000..018acea96852f Binary files /dev/null and b/docs/maps/images/extended_stats_config.png differ diff --git a/docs/maps/images/gear_icon.png b/docs/maps/images/gear_icon.png new file mode 100644 index 0000000000000..355d55dbbc37a Binary files /dev/null and b/docs/maps/images/gear_icon.png differ diff --git a/docs/maps/images/gs_add_cloropeth_layer.png b/docs/maps/images/gs_add_cloropeth_layer.png index b2ee35f025b6f..2800a5a2d2584 100644 Binary files a/docs/maps/images/gs_add_cloropeth_layer.png and b/docs/maps/images/gs_add_cloropeth_layer.png differ diff --git a/docs/maps/images/gs_add_es_layer.png b/docs/maps/images/gs_add_es_layer.png deleted file mode 100644 index b80ddc47d7e5d..0000000000000 Binary files a/docs/maps/images/gs_add_es_layer.png and /dev/null differ diff --git a/docs/maps/images/gs_link_icon.png b/docs/maps/images/gs_link_icon.png deleted file mode 100644 index 8986648470613..0000000000000 Binary files a/docs/maps/images/gs_link_icon.png and /dev/null differ diff --git a/docs/maps/images/quantitative_data_driven_styling.png b/docs/maps/images/quantitative_data_driven_styling.png new file mode 100644 index 0000000000000..a7852ed202016 Binary files /dev/null and b/docs/maps/images/quantitative_data_driven_styling.png differ diff --git a/docs/maps/images/sample_data_web_logs.png b/docs/maps/images/sample_data_web_logs.png index dda0926a07dfe..3b0c2ba3f12c0 100644 Binary files a/docs/maps/images/sample_data_web_logs.png and b/docs/maps/images/sample_data_web_logs.png differ diff --git a/docs/maps/images/vector_style_class.png b/docs/maps/images/vector_style_class.png index 48658bda73ee8..8c685dfcf0ab6 100644 Binary files a/docs/maps/images/vector_style_class.png and b/docs/maps/images/vector_style_class.png differ diff --git a/docs/maps/images/vector_style_dynamic.png b/docs/maps/images/vector_style_dynamic.png index 90d51144c422d..aeaef412b5220 100644 Binary files a/docs/maps/images/vector_style_dynamic.png and b/docs/maps/images/vector_style_dynamic.png differ diff --git a/docs/maps/images/vector_style_static.png b/docs/maps/images/vector_style_static.png index 6eedb8247e6ba..47d9c3b21fcb6 100644 Binary files a/docs/maps/images/vector_style_static.png and b/docs/maps/images/vector_style_static.png differ diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 627fd49dafa51..05d8c0c605e7f 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -24,13 +24,13 @@ The *Grid aggregation* source uses {ref}/search-aggregations-bucket-geotilegrid- You can symbolize grid aggregation metrics as: -*Points*:: Creates a <> with a point for each gridded cell. -The point location is the weighted centroid for all geo-points in the gridded cell. - *Grid rectangles*:: Creates a <> with a bounding box polygon for each gridded cell. *Heat map*:: Creates a <> that clusters the weighted centroids for each gridded cell. +*Clusters*:: Creates a <> with a cluster symbol for each gridded cell. +The cluster location is the weighted centroid for all geo-points in the gridded cell. + [role="xpack"] [[maps-top-hits-aggregation]] diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index e6908ca773a2f..b13eeebe56fd8 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -68,14 +68,16 @@ and lighter shades symbolize countries with less traffic. . Click the *EMS Boundaries* data source. . From the *Layer* dropdown menu, select *World Countries*. . Click the *Add layer* button. -. Set *Layer name* to `Total Requests by Country`. -. Set *Layer transparency* to 0.5. +. Set *Name* to `Total Requests by Country`. +. Set *Opacity* to 50%. +. Click *Add* under *Tooltip fields*. +. In the popover, select *ISO 3166-1 alpha-2 code* and *name* and click *Add*. ===== Join the vector layer with the sample web log index You now have a vector layer containing the world countries. To symbolize countries by web traffic, you'll need to augment the world country features with the count of Elasticsearch weblog documents originating from each country. -To do this, you'll create a <> to link the vector source *World Countries* to +To do this, you'll create a <> to link the vector source *World Countries* to the {es} index `kibana_sample_data_logs` on the shared key iso2 = geo.src. . Click plus image:maps/images/gs_plus_icon.png[] to the right of *Term Joins* label. @@ -83,15 +85,18 @@ the {es} index `kibana_sample_data_logs` on the shared key iso2 = geo.src. . Set *Left field* to *ISO 3166-1 alpha-2 code*. . Set *Right source* to *kibana_sample_data_logs*. . Set *Right field* to *geo.src*. +. Click *and use metric count*. +. Set *Custom label* to *web logs count*. ===== Set the layer style All of the world countries are still a single color because the layer is using <>. To shade the world countries based on which country is sending the most requests, you'll need to use <>. -. Click image:maps/images/gs_link_icon.png[] to the right of *Fill color*. +. Under *Fill color*, change the selected value from *Solid* to *By value*. +. In the field select input, select *web logs count*. . Select the grey color ramp. -. In the field select input, select *count of kibana_sample_data_logs:geo.src*. +. Under *Border color*, change the selected color to *white*. . Click *Save & close*. + Your map now looks like this: @@ -119,9 +124,11 @@ The layer is only visible when users zoom in the map past zoom level 9. . Click the *Documents* data source. . Set *Index pattern* to *kibana_sample_data_logs*. . Click the *Add layer* button. -. Set *Layer name* to `Actual Requests`. -. Set *Zoom range for layer visibility* to the range [9, 24]. -. Set *Layer transparency* to 1. +. Set *Name* to `Actual Requests`. +. Set *Visibilty* to the range [9, 24]. +. Set *Opacity* to 100%. +. Click *Add* under *Tooltip fields*. +. In the popover, select *clientip*, *timestamp*, *host*, *request*, *response*, *machine.os*, *agent*, and *bytes* and click *Add*. . Set *Fill color* to *#2200ff*. . Click *Save & close*. + @@ -150,30 +157,30 @@ image::maps/images/grid_metrics_both.png[] . In the map legend, click *Add layer*. . Click the *Grid aggregation* data source. . Set *Index pattern* to *kibana_sample_data_logs*. -. Set *Show as* to *points*. +. Set *Show as* to *clusters*. . Click the *Add layer* button. -. Set *Layer name* to `Total Requests and Bytes`. -. Set *Zoom range for layer visibility* to the range [0, 9]. -. Set *Layer transparency* to 1. +. Set *Name* to `Total Requests and Bytes`. +. Set *Visibility* to the range [0, 9]. +. Set *Opacity* to 100%. ===== Configure the aggregation metrics -. Click plus image:maps/images/gs_plus_icon.png[] to the right of *Metrics* label. +. Click *Add metric* under of *Metrics* label. . Select *Sum* in the aggregation select. . Select *bytes* in the field select. ===== Set the layer style . In *Layer style*, change *Symbol size*: - .. Set *Min size* to 1. + .. Set *Min size* to 7. .. Set *Max size* to 25. - .. In the field select, select *sum of bytes*. + .. Change the field select from *count* to *sum of bytes*. . Click *Save & close* button. + Your map now looks like this between zoom levels 0 and 9: + [role="screenshot"] -image::maps/images/gs_add_es_layer.png[] +image::maps/images/sample_data_web_logs.png[] [role="xpack"] [[maps-save]] diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc index 1d4ba9912529a..17c57c82b0f17 100644 --- a/docs/maps/vector-layer.asciidoc +++ b/docs/maps/vector-layer.asciidoc @@ -19,7 +19,7 @@ NOTE: Document results are limited to the `index.max_result_window` index settin Use <> to plot large data sets. *Grid aggregation*:: Geospatial data grouped in grids with metrics for each gridded cell. -Set *Show as* to *grid rectangles* or *points*. +Set *Show as* to *grid rectangles* or *clusters*. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point]. *EMS Boundaries*:: Administrative boundaries from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. diff --git a/docs/maps/vector-style.asciidoc b/docs/maps/vector-style.asciidoc index 609cd712e8b13..cd5b086508ae8 100644 --- a/docs/maps/vector-style.asciidoc +++ b/docs/maps/vector-style.asciidoc @@ -5,6 +5,7 @@ When styling a vector layer, you can customize your data by property, such as size and color. For each property, you can specify whether to use a constant or data driven value for the style. + [float] [[maps-vector-style-static]] ==== Static styling @@ -17,13 +18,41 @@ The *kibana_sample_data_logs* layer uses static styling for all properties. [role="screenshot"] image::maps/images/vector_style_static.png[] + [float] [[maps-vector-style-data-driven]] ==== Data driven styling -Use data driven styling to symbolize features from a range of numeric property values. -To enable data driven styling, click image:maps/images/gs_link_icon.png[] next to the property. -This button is only available when vector features contain numeric properties. +Use data driven styling to symbolize features by property values. +To enable data driven styling for a style property, change the selected value from *Fixed* or *Solid* to *By value*. + +The image below shows an example of data driven styling using the <> data set. +The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. + +* The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. +Light green circles symbolize documents that occur earlier in the day, and dark green circles symbolize documents that occur later in the day. + +* The `bytes` property determines the size of each symbol based on where the value fits on a linear scale. +Smaller circles symbolize documents with smaller payloads, and larger circles symbolize documents with larger payloads. + +[role="screenshot"] +image::maps/images/vector_style_dynamic.png[] + + +[float] +[[maps-vector-style-quantitative-data-driven]] +==== Quantitative data driven styling + +Quantitative data driven styling symbolizes features from a range of numeric property values. + +To ensure symbols are consistent as you pan, zoom, and filter the map, quantitative data driven styling uses {ref}/search-aggregations-metrics-extendedstats-aggregation.html[extended_stats aggregation] to retrieve statistical metadata. + +Click the gear icon image:maps/images/gear_icon.png[] to configure extended_stats. Set *Sigma* to a smaller value to minimize outliers by moving the range minimum and maximum closer to the average. Clear the *Calculate range from indices* checkbox to turn off the extended_stats aggregation request. + +NOTE: When the *Calculate range from indices* checkbox is cleared, symbols might be inconsistent as users pan, zoom, and filter the map. Without extended_stats, the range is calulated with data from the local layer. The range is recalulcated when layer data changes. + +[role="screenshot"] +image::maps/images/extended_stats_config.png[] When the property value is undefined for a feature: @@ -31,22 +60,32 @@ When the property value is undefined for a feature: * *Border width* and *Symbol size* are set to the minimum size. * *Symbol orientation* is set to 0. -When the minimum and maximum are the same and there is no range: +When the symbol range minimum and maximum are the same and there is no range: * *Fill color* and *Border color* are set to last color in the color ramp. * *Border width* and *Symbol size* are set to the maximum size. -The image below shows an example of data driven styling using the <> data set. -The *kibana_sample_data_logs* layer uses data driven styling for fill color and symbol size style properties. -* The `hour_of_day` property determines the fill color for each feature based on where the value fits on a linear scale. -Light green circles symbolize documents that occur earlier in the day, and dark green circles symbolize documents that occur later in the day. +[float] +[[maps-vector-style-qualitative-data-driven]] +==== Qualitative data driven styling -* The `bytes` property determines the size of each symbol based on where the value fits on a linear scale. -Smaller circles symbolize documents with smaller payloads, and larger circles symbolize documents with larger payloads. +Qualitative data driven styling symbolizes non-numeric properties, such as strings and IP addresses, by category. + +Qualitative data driven styling is available for the following styling properties: + +* *Fill color* +* *Border color* +* *Label color* +* *Label border color* + +Qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to retrieve the top nine categories for the property. Feature values within the top categories are assigned a unique color. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. + +The image below shows an example of quantitative data driven styling using the <> data set. +The `machine.os.keyword` property determines the color of each symbol based on category. [role="screenshot"] -image::maps/images/vector_style_dynamic.png[] +image::maps/images/quantitative_data_driven_styling.png[] [float] diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 38a46a3cde5a0..8f445ff25218b 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/images/mutual-tls-role-mapping.png b/docs/user/security/images/mutual-tls-role-mapping.png new file mode 100644 index 0000000000000..d95ce41e130c2 Binary files /dev/null and b/docs/user/security/images/mutual-tls-role-mapping.png differ diff --git a/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc b/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc new file mode 100644 index 0000000000000..1c7e7080e676b --- /dev/null +++ b/docs/user/security/securing-communications/elasticsearch-mutual-tls.asciidoc @@ -0,0 +1,120 @@ +[role="xpack"] +[[elasticsearch-mutual-tls]] +=== Mutual TLS authentication between {kib} and {es} +++++ +Mutual TLS with {es} +++++ + +In a standard Transport Layer Security (TLS/SSL) configuration, the server presents a signed certificate to authenticate itself to the +client. In a mutual TLS configuration, the client also presents a signed certificate to authenticate itself to the server. + +When {security} is enabled on your cluster, each request that {kib} makes to {es} must be authenticated. Most requests made through {kib} to +{es} are authenticated by using the credentials of the logged-in user. There are, however, a few internal requests that the {kib} server +needs to make to the {es} cluster. For this reason, you must configure credentials for the {kib} server to use for those requests. + +If {kib} has `elasticsearch.username` and `elasticsearch.password` configured, it will attempt to use these to authenticate to {es} via the +{ref}/native-realm.html[Native realm]. However, {kib} also supports mutual TLS authentication with {es} via a {ref}/pki-realm.html[Public +Key Infrastructure (PKI) realm]. To do so, {es} needs to verify the signature on the {kib} client certificate, and it also needs to map the +certificate's distinguished name (DN) to the appropriate `kibana_system` role. + +NOTE: Using a PKI realm is a gold feature. For a comparison of the Elastic license levels, see https://www.elastic.co/subscriptions[the +subscription page]. + +To configure {kib} and {es} to use mutual TLS authentication: + +. <> with a username and password. + +. <>. At a minimum, this requires a server certificate for {es}. + +. Create a client certificate and private key for {kib} to use when connecting to {es}. ++ +-- +NOTE: This is not the same as the <> that {kib} will present to web browsers. + +You may choose to generate a certificate and private key using {ref}/certutil.html[the {es} certutil tool]. At this point, you will have +already set up a certificate authority (CA) to sign the {es} server certificate. You may choose to use the same CA to sign the {kib} client +certificate. You would do this like so: + +[source,sh] +-------------------------------------------------------------------------------- +bin/elasticsearch-certutil cert -ca elastic-stack-ca.p12 -name kibana-client +-------------------------------------------------------------------------------- + +This will generate a certificate and private key in a PKCS #12 keystore named `kibana-client.p12`. The certificate has a Common Name (CN) of +"kibana-client". + +You will also need to use the CA certificate when setting up the PKI realm in {es}. While you could use the CA keystore in the above example +for this purpose, it is bad practice to expose the CA's private key in such a manner. Instead, you can extract the CA certificate (without +its private key) like so: + +[source,sh] +-------------------------------------------------------------------------------- +openssl pkcs12 -in kibana-client.p12 -cacerts -nokeys -out ca.crt +-------------------------------------------------------------------------------- +-- + +. Configure a PKI realm and a Native realm in your {es} cluster: ++ +-- +By default, {es} provides a Native realm. However, to support both a PKI realm (for {kib}) and a Native realm (for end users), you must +configure each realm in `elasticsearch.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.security.authc.realms.pki.realm1.order: 1 +xpack.security.authc.realms.pki.realm1.certificate_authorities: "/path/to/ca.crt" +xpack.security.authc.realms.native.realm2.order: 2 +-------------------------------------------------------------------------------- + +-- + +. Configure your {es} cluster to request client certificates: ++ +-- +By default, {es} will not request a client certificate when establishing a TLS connection. To change this, you must set up optional client +certificate authentication in `elasticsearch.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.security.http.ssl.client_authentication: "optional" +-------------------------------------------------------------------------------- +-- + +. Restart your {es} cluster. + +. Use {kib} to create a <> for your new client certificate: ++ +-- +This role mapping will assign the `kibana_system` role to any user that matches the included mapping rule, which is set to equal the client +certificate's DN attribute: + +[role="screenshot"] +image:user/security/images/mutual-tls-role-mapping.png["Role mapping for the {kib} client certificate"] +-- + +. Configure {kib} to use the client certificate: ++ +-- +Assuming you used the {es} certutil tool to generate a certificate and private key in a PKCS #12 keystore, add the following values to +`kibana.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +elasticsearch.ssl.keystore.path: "/path/to/kibana-client.p12" +elasticsearch.ssl.keystore.password: "decryption password" +-------------------------------------------------------------------------------- + +The decryption password should match what you entered when prompted by the {es} certutil tool. + +You must also remove the `elasticsearch.username` and `elasticsearch.password` values from the configuration file. Otherwise, {kib} will +attempt to use those to authenticate via the Native realm. + +TIP: Alternatively, {kib} also supports using a client certificate and private key in PEM format with the `elasticsearch.ssl.certificate` +and `elasticsearch.ssl.key` settings. For more information, see <>. +-- + +. Restart {kib}. + +NOTE: The steps above enable {kib} to authenticate to {es} using a certificate. However, end users will only be able to authenticate to +{kib} with a username and password. To allow end users to authenticate to {kib} using certificates, see <>. diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index a68a2ee285ee3..2d07b57bfabe1 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -88,6 +88,8 @@ xpack.security.session.lifespan: "8h" . Optional: <>. +. Optional: <>. + . Restart {kib}. . [[kibana-roles]]Choose an authentication mechanism and grant users the privileges they need to @@ -141,4 +143,5 @@ NOTE: This must be a user who has been assigned <> instead, which offers more functionality and is easier to use. -If you want to create new coordinate map visualizations, set `xpack.maps.showMapVisualizationTypes` to `true`. +NOTE: Coordinate maps have been replaced with <>, which offers more functionality and is easier to use. -Kibana uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service] -to display map tiles. To use other tile service providers, configure the <> +To create coordinate maps in Visualize: + +* Set `xpack.maps.showMapVisualizationTypes` to `true`. + +* To display map tiles, {kib} uses the https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. +To use other tile service providers, configure the <> in `kibana.yml`. [float] -[[tilemap-configuration]] -=== Configuration +[[coordinate-map-aggregation]] +=== Supported aggregations -[float] -==== Data +Coordinate maps support the metric and bucket aggregations. [float] -===== Metrics - -The default _metrics_ aggregation for a coordinate map is the *Count* aggregation. You can select any of the following -aggregations as the metrics aggregation: - -*Count*:: The {ref}/search-aggregations-metrics-valuecount-aggregation.html[_count_] aggregation returns a raw count of -the elements in the selected index pattern. -*Average*:: This aggregation returns the {ref}/search-aggregations-metrics-avg-aggregation.html[_average_] of a numeric -field. Select a field from the drop-down. -*Sum*:: The {ref}/search-aggregations-metrics-sum-aggregation.html[_sum_] aggregation returns the total sum of a numeric -field. Select a field from the drop-down. -*Min*:: The {ref}/search-aggregations-metrics-min-aggregation.html[_min_] aggregation returns the minimum value of a -numeric field. Select a field from the drop-down. -*Max*:: The {ref}/search-aggregations-metrics-max-aggregation.html[_max_] aggregation returns the maximum value of a -numeric field. Select a field from the drop-down. -*Unique Count*:: The {ref}/search-aggregations-metrics-cardinality-aggregation.html[_cardinality_] aggregation returns -the number of unique values in a field. Select a field from the drop-down. - -Enter a string in the *Custom Label* field to change the display label. +===== Metric aggregations -[float] -===== Buckets +The following metric aggregations are supported: -Coordinate maps use the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html[_geohash_] aggregation. Select a field, typically coordinates, from the -drop-down. +{ref}/search-aggregations-metrics-valuecount-aggregation.html[Count]:: Returns a raw count of +the elements in the index pattern. The default metrics aggregation for a coordinate map is *Count*. -- The _Change precision on map zoom_ box is checked by default. Uncheck the box to disable this behavior. -The _Precision_ slider determines the granularity of the results displayed on the map. See the documentation -for the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid] -aggregation for details on the area specified by each precision level. +{ref}/search-aggregations-metrics-avg-aggregation.html[Average]:: Returns the average of a numeric +field. -NOTE: Higher precisions increase memory usage for the browser displaying Kibana as well as for the underlying -Elasticsearch cluster. +{ref}/search-aggregations-metrics-sum-aggregation.html[Sum]:: Returns the total sum of a numeric +field. -- The _place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])_ box is checked by default. When this box is checked, the markers are -placed in the center of all the documents in that bucket. When unchecked, the markers are placed in the center -of the geohash grid cell. Leaving this checked generally results in a more accurate visualization. +{ref}/search-aggregations-metrics-min-aggregation.html[Min]:: Returns the minimum value of a +numeric field. +{ref}/search-aggregations-metrics-max-aggregation.html[Max]:: Returns the maximum value of a +numeric field. -Enter a string in the *Custom Label* field to change the display label. +{ref}/search-aggregations-metrics-cardinality-aggregation.html[Unique Count]:: Returns +the number of unique values in a field. [float] -==== Options - -*Map type*:: Select one of the following options from the drop-down. -*_Scaled Circle Markers_*:: Scale the size of the markers based on the metric aggregation's value. -*_Shaded Circle Markers_*:: Displays the markers with different shades based on the metric aggregation's value. -*_Shaded Geohash Grid_*:: Displays the rectangular cells of the geohash grid instead of circular markers, with different -shades based on the metric aggregation's value. -*_Heatmap_*:: A heat map applies blurring to the circle markers and applies shading based on the amount of overlap. -Heatmaps have the following options: - -* *Cluster size*: Adjust the size of the heatmap clustering. -* *Show Tooltip*: Check this box to have a tooltip with the values for a given dot when the cursor is on that dot. - -*Desaturate map tiles*:: Desaturate the map's color in order to make the markers stand out more clearly. -*WMS compliant map server*:: Check this box to enable the use of a third-party mapping service that complies with the Web -Map Service (WMS) standard. Specify the following elements: - -* *WMS url*: The URL for the WMS map service. -* *WMS layers*: A comma-separated list of the layers to use in this visualization. Each map server provides its own list of -layers. -* *WMS version*: The WMS version used by this map service. -* *WMS format*: The image format used by this map service. The two most common formats are `image/png` and `image/jpeg`. -* *WMS attribution*: An optional, user-defined string that identifies the map source. Maps display the attribution string -in the lower right corner. -* *WMS styles*: A comma-separated list of the styles to use in this visualization. Each map server provides its own styling -options. - -After changing options, click the *Apply changes* button to update your visualization, or the grey *Discard -changes* button to keep your visualization in its current state. +[[coordinate-bucket-aggregation]] +===== Bucket aggregation + +Coordinate maps support the {ref}/search-aggregations-bucket-geohashgrid-aggregation.html[_geohash_] bucket aggregation. + +When you deselect *Change precision on map zoom*, the *Precision* slider appears. The *Precision* slider determines the granularity of the results displayed on the map. For details on the area specified by each precision level, refer to {ref}/search-aggregations-bucket-geohashgrid-aggregation.html#_cell_dimensions_at_the_equator[geohash grid]. + +NOTE: Higher precisions increase memory usage for the browser that displays {kib} and the underlying +{es} cluster. + +When you select *Place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])*, the markers are +placed in the center of all documents in the bucket, and a more accurate visualization is created. +NOTE: When you have multiple values in the geo_point, the coordinate map is unable to accurately calculate the geo_centroid. + +When you deselect *Place markers off grid (use {ref}/search-aggregations-metrics-geocentroid-aggregation.html[geocentroid])*, the markers are placed in the center +of the geohash grid cell. [float] [[navigate-map]] -=== Navigating the Map +=== Navigate the coordinate map -Once your tilemap visualization is ready, you can explore the map in several ways: +Use the following navigation options: -* Click and hold anywhere on the map and move the cursor to move the map center. Hold Shift and drag a bounding box -across the map to zoom in on the selection. -* Click the *Zoom In/Out* image:images/viz-zoom.png[] buttons to change the zoom level manually. -* Click the *Fit Data Bounds* image:images/viz-fit-bounds.png[] button to automatically crop the map boundaries to the -geohash buckets that have at least one result. -* Click the *Latitude/Longitude Filter* image:images/viz-lat-long-filter.png[] button, then drag a bounding box across the -map, to create a filter for the box coordinates. +* To move the map center, click and hold anywhere on the map and move the cursor. +* To change the zoom level, click *Zoom In* or *Zoom out* image:images/viz-zoom.png[]. +* To automatically crop the map boundaries to the +geohash buckets that have at least one result, click *Fit Data Bounds* image:images/viz-fit-bounds.png[]. diff --git a/examples/README.md b/examples/README.md index 7cade0b35f820..2b214a8d1eb52 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,4 +5,3 @@ This folder contains example plugins. To run the plugins in this folder, use th ``` yarn start --run-examples ``` - diff --git a/examples/bfetch_explorer/kibana.json b/examples/bfetch_explorer/kibana.json new file mode 100644 index 0000000000000..cbdd9be0e658c --- /dev/null +++ b/examples/bfetch_explorer/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "bfetchExplorer", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["bfetch_explorer"], + "server": true, + "ui": true, + "requiredPlugins": ["bfetch"], + "optionalPlugins": [] +} diff --git a/examples/bfetch_explorer/package.json b/examples/bfetch_explorer/package.json new file mode 100644 index 0000000000000..ea5a1b1848613 --- /dev/null +++ b/examples/bfetch_explorer/package.json @@ -0,0 +1,17 @@ +{ + "name": "bfetch_explorer", + "version": "1.0.0", + "main": "target/examples/bfetch_explorer", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/examples/bfetch_explorer/public/components/count_until/index.tsx b/examples/bfetch_explorer/public/components/count_until/index.tsx new file mode 100644 index 0000000000000..ce48ce9dfe61f --- /dev/null +++ b/examples/bfetch_explorer/public/components/count_until/index.tsx @@ -0,0 +1,93 @@ +/* + * 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, { useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import useList from 'react-use/lib/useList'; +import { EuiForm, EuiSpacer, EuiFieldNumber, EuiFormRow, EuiButton } from '@elastic/eui'; +import { BfetchPublicSetup } from '../../../../../src/plugins/bfetch/public'; + +export interface Props { + fetchStreaming: BfetchPublicSetup['fetchStreaming']; +} + +export const CountUntil: React.FC = ({ fetchStreaming }) => { + const isMounted = useMountedState(); + const [data, setData] = useState(5); + const [showingResults, setShowingResults] = useState(false); + const [results, { push: pushResult, clear: clearList }] = useList([]); + const [completed, setCompleted] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = () => { + setShowingResults(true); + const { stream } = fetchStreaming({ + url: '/bfetch_explorer/count', + body: JSON.stringify({ data }), + }); + stream.subscribe({ + next: (next: string) => { + if (!isMounted()) return; + pushResult(next); + }, + error: (nextError: any) => { + if (!isMounted()) return; + setError(nextError); + }, + complete: () => { + if (!isMounted()) return; + setCompleted(true); + }, + }); + }; + + const handleReset = () => { + setShowingResults(false); + clearList(); + setError(null); + setCompleted(false); + }; + + if (showingResults) { + return ( + +
{JSON.stringify(error || results, null, 4)}
+ + + Reset + +
+ ); + } + + return ( + + + setData(Number(e.target.value))} + /> + + + Start + + + ); +}; diff --git a/examples/bfetch_explorer/public/components/double_integers/index.tsx b/examples/bfetch_explorer/public/components/double_integers/index.tsx new file mode 100644 index 0000000000000..d8fbe33ec73be --- /dev/null +++ b/examples/bfetch_explorer/public/components/double_integers/index.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 React, { useState } from 'react'; +import useMountedState from 'react-use/lib/useMountedState'; +import useList from 'react-use/lib/useList'; +import useCounter from 'react-use/lib/useCounter'; +import { EuiForm, EuiSpacer, EuiTextArea, EuiFormRow, EuiButton } from '@elastic/eui'; +import { ExplorerService } from '../../plugin'; + +interface ResultItem { + num: number; + result?: { + num: number; + }; + error?: any; +} + +const defaultNumbers = [2000, 300, -1, 1000].join('\n'); + +export interface Props { + double: ExplorerService['double']; +} + +export const DoubleIntegers: React.FC = ({ double }) => { + const isMounted = useMountedState(); + const [numbers, setNumbers] = useState(defaultNumbers); + const [showingResults, setShowingResults] = useState(false); + const [numberOfResultsAwaiting, counter] = useCounter(0); + const [results, { push: pushResult, clear: clearList }] = useList([]); + + const handleSubmit = () => { + setShowingResults(true); + const nums = numbers + .split('\n') + .map(num => num.trim()) + .filter(Boolean) + .map(Number); + counter.set(nums.length); + nums.forEach(num => { + double({ num }).then( + result => { + if (!isMounted()) return; + counter.dec(); + pushResult({ num, result }); + }, + error => { + if (!isMounted()) return; + counter.dec(); + pushResult({ num, error }); + } + ); + }); + }; + + const handleReset = () => { + setShowingResults(false); + counter.reset(); + clearList(); + }; + + if (showingResults) { + return ( + +
{JSON.stringify(results, null, 4)}
+ + + Reset + +
+ ); + } + + return ( + + + setNumbers(e.target.value)} + /> + + + Send + + + ); +}; diff --git a/examples/bfetch_explorer/public/components/page/index.tsx b/examples/bfetch_explorer/public/components/page/index.tsx new file mode 100644 index 0000000000000..0e7855178a884 --- /dev/null +++ b/examples/bfetch_explorer/public/components/page/index.tsx @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; + +export interface PageProps { + title?: React.ReactNode; +} + +export const Page: React.FC = ({ title = 'Untitled', children }) => { + return ( + + + + +

{title}

+
+
+
+ + + {children} + + +
+ ); +}; diff --git a/src/plugins/data/public/autocomplete_provider/index.ts b/examples/bfetch_explorer/public/containers/app/index.tsx similarity index 50% rename from src/plugins/data/public/autocomplete_provider/index.ts rename to examples/bfetch_explorer/public/containers/app/index.tsx index 6758bd7f379c1..a448c9e4f3a6a 100644 --- a/src/plugins/data/public/autocomplete_provider/index.ts +++ b/examples/bfetch_explorer/public/containers/app/index.tsx @@ -16,25 +16,33 @@ * specific language governing permissions and limitations * under the License. */ -import { AutocompleteProvider } from './types'; -export class AutocompleteProviderRegister { - private readonly registeredProviders: Map = new Map(); +import React from 'react'; +import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; +import { EuiPage } from '@elastic/eui'; +import { useDeps } from '../../hooks/use_deps'; +import { Sidebar } from './sidebar'; +import { routes } from '../../routes'; - /** @public **/ - public addProvider(language: string, provider: AutocompleteProvider): void { - if (language && provider) { - this.registeredProviders.set(language, provider); - } - } +export const App: React.FC = () => { + const { appBasePath } = useDeps(); - /** @public **/ - public getProvider(language: string): AutocompleteProvider | undefined { - return this.registeredProviders.get(language); + const routeElements: React.ReactElement[] = []; + for (const { items } of routes) { + for (const { id, component } of items) { + routeElements.push( component} />); + } } - /** @internal **/ - public clearProviders(): void { - this.registeredProviders.clear(); - } -} + return ( + + + + + {routeElements} + + + + + ); +}; diff --git a/examples/bfetch_explorer/public/containers/app/pages/page_count_until/index.tsx b/examples/bfetch_explorer/public/containers/app/pages/page_count_until/index.tsx new file mode 100644 index 0000000000000..7b4eac6eea44c --- /dev/null +++ b/examples/bfetch_explorer/public/containers/app/pages/page_count_until/index.tsx @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiPanel, EuiText } from '@elastic/eui'; +import { CountUntil } from '../../../../components/count_until'; +import { Page } from '../../../../components/page'; +import { useDeps } from '../../../../hooks/use_deps'; + +// eslint-disable-next-line +export interface Props {} + +export const PageCountUntil: React.FC = () => { + const { plugins } = useDeps(); + + return ( + + + This demo sends a single number N using fetchStreaming to the server. The + server will stream back N number of messages with 1 second delay each containing a number + from 1 to N, after which it will close the stream. + +
+ + + +
+ ); +}; diff --git a/examples/bfetch_explorer/public/containers/app/pages/page_double_integers/index.tsx b/examples/bfetch_explorer/public/containers/app/pages/page_double_integers/index.tsx new file mode 100644 index 0000000000000..7bd5feb836674 --- /dev/null +++ b/examples/bfetch_explorer/public/containers/app/pages/page_double_integers/index.tsx @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiPanel, EuiText } from '@elastic/eui'; +import { DoubleIntegers } from '../../../../components/double_integers'; +import { Page } from '../../../../components/page'; +import { useDeps } from '../../../../hooks/use_deps'; + +// eslint-disable-next-line +export interface Props {} + +export const PageDoubleIntegers: React.FC = () => { + const { explorer } = useDeps(); + + return ( + + + Below is a list of numbers in milliseconds. They are sent as a batch to the server. For each + number server waits given number of milliseconds then doubles the number and streams it + back. + +
+ + + +
+ ); +}; diff --git a/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx b/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx new file mode 100644 index 0000000000000..cc50698e05908 --- /dev/null +++ b/examples/bfetch_explorer/public/containers/app/sidebar/index.tsx @@ -0,0 +1,54 @@ +/* + * 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 { EuiPageSideBar, EuiSideNav } from '@elastic/eui'; +import { useHistory } from 'react-router-dom'; +import { routes } from '../../../routes'; + +// eslint-disable-next-line +interface SidebarProps {} + +export const Sidebar: React.FC = () => { + const history = useHistory(); + + return ( + + ({ + id, + name: title, + isSelected: true, + items: items.map(route => ({ + id: route.id, + name: route.title, + onClick: () => history.push(`/${route.id}`), + 'data-test-subj': route.id, + })), + })), + }, + ]} + /> + + ); +}; diff --git a/examples/bfetch_explorer/public/hooks/use_deps.ts b/examples/bfetch_explorer/public/hooks/use_deps.ts new file mode 100644 index 0000000000000..c68b4e759c21c --- /dev/null +++ b/examples/bfetch_explorer/public/hooks/use_deps.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useKibana } from '../../../../src/plugins/kibana_react/public'; +import { BfetchDeps } from '../mount'; + +export const useDeps = () => useKibana().services as BfetchDeps; diff --git a/examples/bfetch_explorer/public/index.ts b/examples/bfetch_explorer/public/index.ts new file mode 100644 index 0000000000000..76d0a1d1c6334 --- /dev/null +++ b/examples/bfetch_explorer/public/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. + */ + +import { BfetchExplorerPlugin } from './plugin'; + +export const plugin = () => new BfetchExplorerPlugin(); diff --git a/examples/bfetch_explorer/public/mount.tsx b/examples/bfetch_explorer/public/mount.tsx new file mode 100644 index 0000000000000..5ad53ef4a1988 --- /dev/null +++ b/examples/bfetch_explorer/public/mount.tsx @@ -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 * as React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { CoreSetup, CoreStart, AppMountParameters } from 'kibana/public'; +import { KibanaContextProvider } from '../../../src/plugins/kibana_react/public'; +import { BfetchExplorerStartPlugins, ExplorerService } from './plugin'; +import { App } from './containers/app'; + +export interface BfetchDeps { + appBasePath: string; + core: CoreStart; + plugins: BfetchExplorerStartPlugins; + explorer: ExplorerService; +} + +export const mount = ( + coreSetup: CoreSetup, + explorer: ExplorerService +) => async ({ appBasePath, element }: AppMountParameters) => { + const [core, plugins] = await coreSetup.getStartServices(); + const deps: BfetchDeps = { appBasePath, core, plugins, explorer }; + const reactElement = ( + + + + ); + render(reactElement, element); + return () => unmountComponentAtNode(element); +}; diff --git a/examples/bfetch_explorer/public/plugin.tsx b/examples/bfetch_explorer/public/plugin.tsx new file mode 100644 index 0000000000000..3155354c91fd4 --- /dev/null +++ b/examples/bfetch_explorer/public/plugin.tsx @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup } from 'kibana/public'; +import { BfetchPublicSetup, BfetchPublicStart } from '../../../src/plugins/bfetch/public'; +import { mount } from './mount'; + +export interface ExplorerService { + double: (number: { num: number }) => Promise<{ num: number }>; +} + +export interface BfetchExplorerSetupPlugins { + bfetch: BfetchPublicSetup; +} + +export interface BfetchExplorerStartPlugins { + bfetch: BfetchPublicStart; +} + +export class BfetchExplorerPlugin implements Plugin { + public setup(core: CoreSetup, plugins: BfetchExplorerSetupPlugins) { + const double = plugins.bfetch.batchedFunction<{ num: number }, { num: number }>({ + url: '/bfetch_explorer/double', + }); + + const explorer: ExplorerService = { + double, + }; + + core.application.register({ + id: 'bfetch-explorer', + title: 'bfetch explorer', + mount: mount(core, explorer), + }); + } + + public start() {} + public stop() {} +} diff --git a/examples/bfetch_explorer/public/routes.tsx b/examples/bfetch_explorer/public/routes.tsx new file mode 100644 index 0000000000000..2008811d75795 --- /dev/null +++ b/examples/bfetch_explorer/public/routes.tsx @@ -0,0 +1,59 @@ +/* + * 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 { PageDoubleIntegers } from './containers/app/pages/page_double_integers'; +import { PageCountUntil } from './containers/app/pages/page_count_until'; + +interface RouteSectionDef { + title: string; + id: string; + items: RouteDef[]; +} + +interface RouteDef { + title: string; + id: string; + component: React.ReactNode; +} + +export const routes: RouteSectionDef[] = [ + { + title: 'fetchStreaming', + id: 'fetchStreaming', + items: [ + { + title: 'Count until', + id: 'count-until', + component: , + }, + ], + }, + { + title: 'batchedFunction', + id: 'batchedFunction', + items: [ + { + title: 'Double integers', + id: 'double-integers', + component: , + }, + ], + }, +]; diff --git a/examples/bfetch_explorer/server/index.ts b/examples/bfetch_explorer/server/index.ts new file mode 100644 index 0000000000000..76d0a1d1c6334 --- /dev/null +++ b/examples/bfetch_explorer/server/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. + */ + +import { BfetchExplorerPlugin } from './plugin'; + +export const plugin = () => new BfetchExplorerPlugin(); diff --git a/examples/bfetch_explorer/server/plugin.ts b/examples/bfetch_explorer/server/plugin.ts new file mode 100644 index 0000000000000..bf3b7f50ca6c8 --- /dev/null +++ b/examples/bfetch_explorer/server/plugin.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Subject } from 'rxjs'; +import { Plugin, CoreSetup, CoreStart } from '../../../src/core/server'; +import { BfetchServerSetup, BfetchServerStart } from '../../../src/plugins/bfetch/server'; + +export interface BfetchExplorerSetupPlugins { + bfetch: BfetchServerSetup; +} + +export interface BfetchExplorerStartPlugins { + bfetch: BfetchServerStart; +} + +export class BfetchExplorerPlugin implements Plugin { + public setup(core: CoreSetup, plugins: BfetchExplorerSetupPlugins) { + plugins.bfetch.addStreamingResponseRoute('/bfetch_explorer/count', () => ({ + getResponseStream: ({ data }: any) => { + const subject = new Subject(); + const countTo = Number(data); + for (let cnt = 1; cnt <= countTo; cnt++) { + setTimeout(() => { + subject.next(String(cnt)); + }, cnt * 1000); + } + setTimeout(() => { + subject.complete(); + }, countTo * 1000); + return subject; + }, + })); + + plugins.bfetch.addBatchProcessingRoute<{ num: number }, { num: number }>( + '/bfetch_explorer/double', + () => ({ + onBatchItem: async ({ num }) => { + // Validate inputs. + if (num < 0) throw new Error('Invalid number'); + // Wait number of specified milliseconds. + await new Promise(r => setTimeout(r, num)); + // Double the number and send it back. + return { num: 2 * num }; + }, + }) + ); + } + + public start(core: CoreStart, plugins: BfetchExplorerStartPlugins) {} + + public stop() {} +} diff --git a/examples/bfetch_explorer/tsconfig.json b/examples/bfetch_explorer/tsconfig.json new file mode 100644 index 0000000000000..d508076b33199 --- /dev/null +++ b/examples/bfetch_explorer/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*", + ], + "exclude": [] +} diff --git a/package.json b/package.json index a623b656ec9a1..430ab9e1ba77d 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "kbn:bootstrap": "yarn build:types && node scripts/register_git_hook", "spec_to_console": "node scripts/spec_to_console", "backport-skip-ci": "backport --prDescription \"[skip-ci]\"", + "storybook": "node scripts/storybook", "cover:report": "nyc report --temp-dir target/kibana-coverage/functional --report-dir target/coverage/report --reporter=lcov && open ./target/coverage/report/lcov-report/index.html" }, "repository": { @@ -117,8 +118,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": "18.0.0", + "@elastic/ems-client": "7.6.0", + "@elastic/eui": "18.2.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -135,6 +136,7 @@ "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", + "@types/flot": "^0.0.31", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", "@types/node-forge": "^0.9.0", @@ -460,10 +462,13 @@ "postcss-url": "^8.0.0", "prettier": "^1.19.1", "proxyquire": "1.8.0", + "react-popper-tooltip": "^2.10.1", + "react-textarea-autosize": "^7.1.2", "regenerate": "^1.4.0", "sass-lint": "^1.12.1", "selenium-webdriver": "^4.0.0-alpha.5", "simple-git": "1.116.0", + "simplebar-react": "^2.1.0", "sinon": "^7.4.2", "strip-ansi": "^3.0.1", "supertest": "^3.1.0", diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 2fc29b71b262e..714ed56ac4703 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export { withProcRunner } from './proc_runner'; +export { withProcRunner, ProcRunner } from './proc_runner'; export { ToolingLog, ToolingLogTextWriter, diff --git a/packages/kbn-dev-utils/src/proc_runner/index.ts b/packages/kbn-dev-utils/src/proc_runner/index.ts index ad5e7c8ee946e..af9dc5c65a3a3 100644 --- a/packages/kbn-dev-utils/src/proc_runner/index.ts +++ b/packages/kbn-dev-utils/src/proc_runner/index.ts @@ -18,3 +18,4 @@ */ export { withProcRunner } from './with_proc_runner'; +export { ProcRunner } from './proc_runner'; diff --git a/packages/kbn-dev-utils/src/run/run.ts b/packages/kbn-dev-utils/src/run/run.ts index 1d28d43575729..e185f86cc3bf7 100644 --- a/packages/kbn-dev-utils/src/run/run.ts +++ b/packages/kbn-dev-utils/src/run/run.ts @@ -23,11 +23,13 @@ import exitHook from 'exit-hook'; import { pickLevelFromFlags, ToolingLog } from '../tooling_log'; import { createFlagError, isFailError } from './fail'; import { Flags, getFlags, getHelp } from './flags'; +import { ProcRunner, withProcRunner } from '../proc_runner'; type CleanupTask = () => void; type RunFn = (args: { log: ToolingLog; flags: Flags; + procRunner: ProcRunner; addCleanupTask: (task: CleanupTask) => void; }) => Promise | void; @@ -102,10 +104,13 @@ export async function run(fn: RunFn, options: Options = {}) { } try { - await fn({ - log, - flags, - addCleanupTask: (task: CleanupTask) => cleanupTasks.push(task), + await withProcRunner(log, async procRunner => { + await fn({ + log, + flags, + procRunner, + addCleanupTask: (task: CleanupTask) => cleanupTasks.push(task), + }); }); } finally { doCleanup(); diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index a3debf78fb8c8..8bded9d403c21 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -4489,6 +4489,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = __webpack_require__(36); var proc_runner_1 = __webpack_require__(37); exports.withProcRunner = proc_runner_1.withProcRunner; +exports.ProcRunner = proc_runner_1.ProcRunner; var tooling_log_1 = __webpack_require__(415); exports.ToolingLog = tooling_log_1.ToolingLog; exports.ToolingLogTextWriter = tooling_log_1.ToolingLogTextWriter; @@ -4761,6 +4762,8 @@ function __importDefault(mod) { Object.defineProperty(exports, "__esModule", { value: true }); var with_proc_runner_1 = __webpack_require__(38); exports.withProcRunner = with_proc_runner_1.withProcRunner; +var proc_runner_1 = __webpack_require__(39); +exports.ProcRunner = proc_runner_1.ProcRunner; /***/ }), @@ -37069,6 +37072,7 @@ const exit_hook_1 = tslib_1.__importDefault(__webpack_require__(348)); const tooling_log_1 = __webpack_require__(415); const fail_1 = __webpack_require__(425); const flags_1 = __webpack_require__(426); +const proc_runner_1 = __webpack_require__(37); async function run(fn, options = {}) { var _a; const flags = flags_1.getFlags(process.argv.slice(2), options); @@ -37118,10 +37122,13 @@ async function run(fn, options = {}) { throw fail_1.createFlagError(`Unknown flag(s) "${flags.unexpected.join('", "')}"`); } try { - await fn({ - log, - flags, - addCleanupTask: (task) => cleanupTasks.push(task), + await proc_runner_1.withProcRunner(log, async (procRunner) => { + await fn({ + log, + flags, + procRunner, + addCleanupTask: (task) => cleanupTasks.push(task), + }); }); } finally { diff --git a/packages/kbn-storybook/README.md b/packages/kbn-storybook/README.md new file mode 100644 index 0000000000000..c9195f41ebf26 --- /dev/null +++ b/packages/kbn-storybook/README.md @@ -0,0 +1,33 @@ +# Kibana Storybook + +This package provides ability to add [Storybook](https://storybook.js.org/) to any Kibana plugin. + +- [Setup Instructions](#setup-instructions) + + +## Setup Instructions + +1. Add `storybook.js` launcher file to your plugin. For example, create a file at + `src/plugins//scripts/storybook.js`, with the following contents: + + ```js + import { join } from 'path'; + + // eslint-disable-next-line + require('@kbn/storybook').runStorybookCli({ + name: '', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.examples.tsx')], + }); + ``` +2. Add your plugin alias to `src/dev/storybook/aliases.ts` config. +3. Create sample Storybook stories. For example, in your plugin create create a file at + `src/plugins//public/components/hello_world/__examples__/hello_world.examples.tsx` with + the following contents: + + ```jsx + import * as React from 'react'; + import { storiesOf } from '@storybook/react'; + + storiesOf('Hello world', module).add('default', () =>
Hello world!
); + ``` +4. Launch Storybook with `yarn storybook `. diff --git a/packages/kbn-storybook/index.js b/packages/kbn-storybook/index.js new file mode 100644 index 0000000000000..78e2cf7f5073b --- /dev/null +++ b/packages/kbn-storybook/index.js @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const fs = require('fs'); +const { join } = require('path'); +const Rx = require('rxjs'); +const { first } = require('rxjs/operators'); +const storybook = require('@storybook/react/standalone'); +const { run } = require('@kbn/dev-utils'); +const { generateStorybookEntry } = require('./lib/storybook_entry'); +const { REPO_ROOT, CURRENT_CONFIG } = require('./lib/constants'); +const { buildDll } = require('./lib/dll'); + +exports.runStorybookCli = config => { + const { name, storyGlobs } = config; + run( + async ({ flags, log, procRunner }) => { + log.debug('Global config:\n', require('./lib/constants')); + + const currentConfig = JSON.stringify(config, null, 2); + const currentConfigDir = join(CURRENT_CONFIG, '..'); + await fs.promises.mkdir(currentConfigDir, { recursive: true }); + log.debug('Writing currentConfig:\n', CURRENT_CONFIG + '\n', currentConfig); + await fs.promises.writeFile(CURRENT_CONFIG, `exports.currentConfig = ${currentConfig};`); + + await buildDll({ + rebuildDll: flags.rebuildDll, + log, + procRunner, + }); + + // Build sass and continue when initial build complete + await procRunner.run('watch sass', { + cmd: process.execPath, + args: ['scripts/build_sass', '--watch'], + cwd: REPO_ROOT, + wait: /scss bundles created/, + }); + + const subj = new Rx.ReplaySubject(1); + generateStorybookEntry({ log, storyGlobs }).subscribe(subj); + + await subj.pipe(first()).toPromise(); + + await Promise.all([ + // route errors + subj.toPromise(), + + new Promise(() => { + // storybook never completes, so neither will this promise + const configDir = join(__dirname, 'storybook_config'); + log.debug('Config dir:', configDir); + storybook({ + mode: 'dev', + port: 9001, + configDir, + }); + }), + ]); + }, + { + flags: { + boolean: ['rebuildDll'], + }, + description: ` + Run the storybook examples for ${name} + `, + } + ); +}; diff --git a/packages/kbn-storybook/lib/constants.js b/packages/kbn-storybook/lib/constants.js new file mode 100644 index 0000000000000..9d216d347eada --- /dev/null +++ b/packages/kbn-storybook/lib/constants.js @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve, dirname } = require('path'); + +exports.REPO_ROOT = dirname(require.resolve('../../../package.json')); +exports.ASSET_DIR = resolve(exports.REPO_ROOT, 'built_assets/storybook'); +exports.CURRENT_CONFIG = resolve(exports.ASSET_DIR, 'current.config.js'); +exports.STORY_ENTRY_PATH = resolve(exports.ASSET_DIR, 'stories.entry.js'); +exports.DLL_DIST_DIR = resolve(exports.ASSET_DIR, 'dll'); +exports.DLL_NAME = 'storybook_dll'; diff --git a/packages/kbn-storybook/lib/dll.js b/packages/kbn-storybook/lib/dll.js new file mode 100644 index 0000000000000..a9154ca972120 --- /dev/null +++ b/packages/kbn-storybook/lib/dll.js @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve } = require('path'); +const { existsSync } = require('fs'); + +const { REPO_ROOT, DLL_DIST_DIR } = require('./constants'); + +exports.buildDll = async ({ rebuildDll, log, procRunner }) => { + if (rebuildDll) { + log.info('rebuilding dll'); + } else if (!existsSync(resolve(DLL_DIST_DIR, 'dll.js'))) { + log.info('dll missing, rebuilding'); + } else { + log.info('dll exists'); + return; + } + + await procRunner.run('build dll ', { + cmd: require.resolve('webpack/bin/webpack'), + args: ['--config', require.resolve('./webpack.dll.config.js')], + cwd: REPO_ROOT, + wait: true, + }); +}; diff --git a/packages/kbn-storybook/lib/storybook_entry.js b/packages/kbn-storybook/lib/storybook_entry.js new file mode 100644 index 0000000000000..dececef47f40e --- /dev/null +++ b/packages/kbn-storybook/lib/storybook_entry.js @@ -0,0 +1,89 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve, relative, dirname } = require('path'); +const Fs = require('fs'); +const Rx = require('rxjs'); +const { mergeMap, map, debounceTime } = require('rxjs/operators'); +const normalize = require('normalize-path'); +const { promisify } = require('util'); + +const watch = require('glob-watcher'); +const mkdirp = require('mkdirp'); // eslint-disable-line +const glob = require('fast-glob'); + +const mkdirpAsync = promisify(mkdirp); +const writeFileAsync = promisify(Fs.writeFile); + +const { REPO_ROOT, STORY_ENTRY_PATH } = require('./constants'); +const STORE_ENTRY_DIR = dirname(STORY_ENTRY_PATH); + +exports.generateStorybookEntry = ({ log, storyGlobs }) => { + const globs = ['built_assets/css/**/*.light.css', ...storyGlobs]; + log.info('Storybook globs:\n', globs); + const norm = p => normalize(relative(STORE_ENTRY_DIR, p)); + + return Rx.defer(() => + glob(globs, { + absolute: true, + cwd: REPO_ROOT, + onlyFiles: true, + }) + ).pipe( + map(paths => { + log.info('Discovered Storybook entry points:\n', paths); + return new Set(paths.map(norm)); + }), + mergeMap( + paths => + new Rx.Observable(observer => { + observer.next(paths); + + const chokidar = watch(globs, { cwd: REPO_ROOT }) + .on('add', path => { + observer.next(paths.add(norm(resolve(REPO_ROOT, path)))); + }) + .on('unlink', path => { + observer.next(paths.delete(norm(resolve(REPO_ROOT, path)))); + }); + + return () => { + chokidar.close(); + }; + }) + ), + debounceTime(200), + mergeMap(async (paths, i) => { + await mkdirpAsync(STORE_ENTRY_DIR); + + let content = ''; + for (const path of paths) { + content += `require('${path}');\n`; + } + + await writeFileAsync(STORY_ENTRY_PATH, content); + + if (i === 0) { + log.info('%d paths written to entry file', paths.size); + } else { + log.info('entry file updated'); + } + }) + ); +}; diff --git a/packages/kbn-storybook/lib/webpack.dll.config.js b/packages/kbn-storybook/lib/webpack.dll.config.js new file mode 100644 index 0000000000000..bc871fab471b2 --- /dev/null +++ b/packages/kbn-storybook/lib/webpack.dll.config.js @@ -0,0 +1,149 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const webpack = require('webpack'); +const path = require('path'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const { DLL_NAME, REPO_ROOT, DLL_DIST_DIR } = require('./constants'); + +// This is the Webpack config for the DLL of CSS and JS assets that are +// not expected to change during development. This saves compile and run +// times considerably. +module.exports = { + context: REPO_ROOT, + mode: 'development', + + // This is a (potentially growing) list of modules that can be safely + // included in the DLL. Only add to this list modules or other code + // which Storybook stories and their components would require, but don't + // change during development. + entry: [ + '@elastic/eui/dist/eui_theme_light.css', + '@kbn/ui-framework/dist/kui_light.css', + '@storybook/addon-info', + '@storybook/addon-knobs', + '@storybook/addon-knobs/react', + '@storybook/addon-knobs/register', + '@storybook/addon-options', + '@storybook/addon-options/register', + '@storybook/core', + '@storybook/core/dist/server/common/polyfills.js', + '@storybook/react', + '@storybook/theming', + 'angular-mocks', + 'angular', + 'brace', + 'chroma-js', + 'highlight.js', + 'html-entities', + 'jquery', + 'lodash.clone', + 'lodash', + 'markdown-it', + 'mocha', + 'prop-types', + 'react-ace', + 'react-beautiful-dnd', + 'react-dom', + 'react-focus-lock', + 'react-markdown', + 'react-resize-detector', + 'react-virtualized', + 'react', + 'recompose', + 'redux-actions', + 'remark-parse', + 'rxjs', + 'sinon', + 'tinycolor2', + './src/legacy/ui/public/styles/font_awesome.less', + './src/legacy/ui/public/styles/bootstrap/bootstrap_light.less', + ], + plugins: [ + // Produce the DLL and its manifest + new webpack.DllPlugin({ + name: DLL_NAME, + path: path.resolve(DLL_DIST_DIR, 'manifest.json'), + }), + // Produce the DLL CSS file + new MiniCssExtractPlugin({ + filename: 'dll.css', + }), + ], + // Output the DLL JS file + output: { + path: DLL_DIST_DIR, + filename: 'dll.js', + library: DLL_NAME, + }, + // Include a require alias for legacy UI code and styles + resolve: { + alias: { + ui: path.resolve(REPO_ROOT, 'src/legacy/ui/public'), + }, + mainFields: ['browser', 'main'], + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + { + loader: MiniCssExtractPlugin.loader, + options: {}, + }, + { loader: 'css-loader' }, + { + loader: 'string-replace-loader', + options: { + search: '__REPLACE_WITH_PUBLIC_PATH__', + replace: '/', + flags: 'g', + }, + }, + ], + }, + { + test: /\.less$/, + use: [ + { loader: 'style-loader' }, + { loader: 'css-loader', options: { importLoaders: 2 } }, + { + loader: 'postcss-loader', + options: { + config: { + path: path.resolve(REPO_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + }, + { loader: 'less-loader' }, + ], + }, + { + test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/, + loader: 'file-loader', + }, + ], + }, + node: { + fs: 'empty', + child_process: 'empty', + }, +}; diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json new file mode 100644 index 0000000000000..6948ae81806eb --- /dev/null +++ b/packages/kbn-storybook/package.json @@ -0,0 +1,32 @@ +{ + "name": "@kbn/storybook", + "version": "1.0.0", + "private": true, + "license": "Apache-2.0", + "dependencies": { + "@kbn/babel-preset": "1.0.0", + "@kbn/dev-utils": "1.0.0", + "@storybook/addon-actions": "^5.2.8", + "@storybook/addon-console": "^1.2.1", + "@storybook/addon-info": "^5.2.8", + "@storybook/addon-knobs": "^5.2.8", + "@storybook/addon-options": "^5.2.8", + "@storybook/addon-storyshots": "^5.2.8", + "@storybook/react": "^5.2.8", + "@storybook/theming": "^5.2.8", + "copy-webpack-plugin": "5.0.3", + "execa": "1.0.0", + "fast-glob": "2.2.7", + "glob-watcher": "5.0.3", + "jest-specific-snapshot": "2.0.0", + "jest-styled-components": "6.3.1", + "mkdirp": "0.5.1", + "mini-css-extract-plugin": "0.7.0", + "normalize-path": "3.0.0", + "react-docgen-typescript-loader": "3.1.0", + "rxjs": "6.5.2", + "serve-static": "1.14.1", + "styled-components": "^3", + "webpack": "4.34.0" + } +} \ No newline at end of file diff --git a/packages/kbn-storybook/storybook_config/addons.js b/packages/kbn-storybook/storybook_config/addons.js new file mode 100644 index 0000000000000..f439d1d8892f8 --- /dev/null +++ b/packages/kbn-storybook/storybook_config/addons.js @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import '@storybook/addon-options/register'; +import '@storybook/addon-actions/register'; +import '@storybook/addon-knobs/register'; +import '@storybook/addon-console'; diff --git a/packages/kbn-storybook/storybook_config/config.js b/packages/kbn-storybook/storybook_config/config.js new file mode 100644 index 0000000000000..a7975773e675b --- /dev/null +++ b/packages/kbn-storybook/storybook_config/config.js @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { configure, addDecorator, addParameters } from '@storybook/react'; +import { withKnobs } from '@storybook/addon-knobs/react'; +import { withInfo } from '@storybook/addon-info'; +import { create } from '@storybook/theming'; + +// If we're running Storyshots, be sure to register the require context hook. +// Otherwise, add the other decorators. +if (process.env.NODE_ENV === 'test') { + // eslint-disable-next-line + require('babel-plugin-require-context-hook/register')(); +} else { + // Customize the info for each story. + addDecorator( + withInfo({ + inline: true, + styles: { + infoBody: { + margin: 20, + }, + infoStory: { + margin: '40px 60px', + }, + }, + }) + ); + + // Add optional knobs to customize each story. + addDecorator(withKnobs); +} + +// Set up the Storybook environment with custom settings. +addParameters({ + options: { + theme: create({ + base: 'light', + brandTitle: 'Kibana Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/master/packages/kbn-storybook', + }), + showPanel: true, + isFullscreen: false, + panelPosition: 'bottom', + isToolshown: true, + }, +}); + +configure(() => { + // eslint-disable-next-line + require('../../../built_assets/storybook/stories.entry.js'); +}, module); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.d.ts b/packages/kbn-storybook/storybook_config/middleware.js similarity index 73% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.d.ts rename to packages/kbn-storybook/storybook_config/middleware.js index bf55abfe8161a..f517477b405bd 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.d.ts +++ b/packages/kbn-storybook/storybook_config/middleware.js @@ -17,16 +17,10 @@ * under the License. */ -import { CommonVislibParams } from './types'; +const serve = require('serve-static'); +const path = require('path'); -export interface PieVisParams extends CommonVislibParams { - type: 'pie'; - addLegend: boolean; - isDonut: boolean; - labels: { - show: boolean; - values: boolean; - last_level: boolean; - truncate: number | null; - }; -} +// Extend the Storybook Middleware to include a route to access Legacy UI assets +module.exports = function(router) { + router.get('/ui', serve(path.resolve(__dirname, '../../../../src/legacy/ui/public/assets'))); +}; diff --git a/packages/kbn-storybook/storybook_config/mocks/absolute_to_parsed_url.js b/packages/kbn-storybook/storybook_config/mocks/absolute_to_parsed_url.js new file mode 100644 index 0000000000000..65a27b095f84e --- /dev/null +++ b/packages/kbn-storybook/storybook_config/mocks/absolute_to_parsed_url.js @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const absoluteToParsedUrl = () => { + getAbsoluteUrl: () => + 'http://localhost:5601/kbp/app/canvas#/workpad/workpad-24d56dad-ae70-42b8-9ef1-c5350ecd426c/page/1'; +}; // noop diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js b/packages/kbn-storybook/storybook_config/mocks/noop.js old mode 100644 new mode 100755 similarity index 96% rename from src/legacy/ui/public/vis/editors/default/fancy_forms/index.js rename to packages/kbn-storybook/storybook_config/mocks/noop.js index 927e6d69e3c8a..aaddfb2ed8ac3 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/index.js +++ b/packages/kbn-storybook/storybook_config/mocks/noop.js @@ -17,4 +17,4 @@ * under the License. */ -import './fancy_forms'; +export default function() {} diff --git a/packages/kbn-storybook/storybook_config/mocks/state_store.js b/packages/kbn-storybook/storybook_config/mocks/state_store.js new file mode 100644 index 0000000000000..11bdf6632321d --- /dev/null +++ b/packages/kbn-storybook/storybook_config/mocks/state_store.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. + */ + +export function getState() { + return { + assets: { + yay: { value: 'here is your image' }, + }, + }; +} diff --git a/packages/kbn-storybook/storybook_config/mocks/ui_storage.js b/packages/kbn-storybook/storybook_config/mocks/ui_storage.js new file mode 100644 index 0000000000000..4bd8cdeddfc22 --- /dev/null +++ b/packages/kbn-storybook/storybook_config/mocks/ui_storage.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export class Storage { + get(key) { + return this[key]; + } + + set(key, value) { + this[key] = value; + } +} diff --git a/packages/kbn-storybook/storybook_config/preview-head.html b/packages/kbn-storybook/storybook_config/preview-head.html new file mode 100644 index 0000000000000..bef08a5120a36 --- /dev/null +++ b/packages/kbn-storybook/storybook_config/preview-head.html @@ -0,0 +1,6 @@ + + + diff --git a/packages/kbn-storybook/storybook_config/webpack.config.js b/packages/kbn-storybook/storybook_config/webpack.config.js new file mode 100644 index 0000000000000..72ff9162ffe6c --- /dev/null +++ b/packages/kbn-storybook/storybook_config/webpack.config.js @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve } = require('path'); +const webpack = require('webpack'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const { REPO_ROOT, DLL_DIST_DIR } = require('../lib/constants'); +// eslint-disable-next-line import/no-unresolved +const { currentConfig } = require('../../../built_assets/storybook/current.config'); + +// Extend the Storybook Webpack config with some customizations +module.exports = async ({ config }) => { + // Find and alter the CSS rule to replace the Kibana public path string with a path + // to the route we've added in middleware.js + const cssRule = config.module.rules.find(rule => rule.test.source.includes('.css$')); + cssRule.use.push({ + loader: 'string-replace-loader', + options: { + search: '__REPLACE_WITH_PUBLIC_PATH__', + replace: '/', + flags: 'g', + }, + }); + + // Include the React preset from Kibana for Storybook JS files. + config.module.rules.push({ + test: /\.js$/, + exclude: /node_modules/, + loaders: 'babel-loader', + options: { + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }); + + // Handle Typescript files + config.module.rules.push({ + test: /\.tsx?$/, + use: [ + { + loader: 'babel-loader', + options: { + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }, + ], + }); + + // Parse props data for .tsx files + config.module.rules.push({ + test: /\.tsx$/, + // Exclude example files, as we don't display props info for them + exclude: /\.examples.tsx$/, + use: [ + // Parse TS comments to create Props tables in the UI + require.resolve('react-docgen-typescript-loader'), + ], + }); + + // Reference the built DLL file of static(ish) dependencies, which are removed + // during kbn:bootstrap and rebuilt if missing. + config.plugins.push( + new webpack.DllReferencePlugin({ + manifest: resolve(DLL_DIST_DIR, 'manifest.json'), + context: REPO_ROOT, + }) + ); + + // Copy the DLL files to the Webpack build for use in the Storybook UI + config.plugins.push( + new CopyWebpackPlugin([ + { + from: resolve(DLL_DIST_DIR, 'dll.js'), + to: 'dll.js', + }, + { + from: resolve(DLL_DIST_DIR, 'dll.css'), + to: 'dll.css', + }, + ]) + ); + + // Tell Webpack about the ts/x extensions + config.resolve.extensions.push('.ts', '.tsx'); + + // Load custom Webpack config specified by a plugin. + if (currentConfig.webpackHook) { + // eslint-disable-next-line import/no-dynamic-require + config = await require(currentConfig.webpackHook)({ config }); + } + + return config; +}; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index c9434f3ec1c38..488f57b01e168 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,7 +9,7 @@ "kbn:watch": "node scripts/build --watch" }, "devDependencies": { - "@elastic/eui": "18.0.0", + "@elastic/eui": "18.2.1", "@elastic/charts": "^16.1.0", "@kbn/dev-utils": "1.0.0", "@yarnpkg/lockfile": "^1.1.0", diff --git a/renovate.json5 b/renovate.json5 index 7f67fae894110..5af62d0acef85 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -281,6 +281,14 @@ '@types/file-saver', ], }, + { + groupSlug: 'flot', + groupName: 'flot related packages', + packageNames: [ + 'flot', + '@types/flot', + ], + }, { groupSlug: 'getopts', groupName: 'getopts related packages', diff --git a/scripts/storybook.js b/scripts/storybook.js new file mode 100644 index 0000000000000..cc517bd5a4a32 --- /dev/null +++ b/scripts/storybook.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env'); +require('../src/dev/storybook/run_storybook_cli'); diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 18f82766bdbc1..61c5d5b076a44 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 0000000000000..76f3be1595258 --- /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/MIGRATION.md b/src/core/MIGRATION.md index 173d73ffab664..f51afd35586bd 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1131,6 +1131,7 @@ import { npStart: { core } } from 'ui/new_platform'; | Legacy Platform | New Platform | Notes | | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | +| `chrome.navLinks.update` | [`core.appbase.updater`](/docs/development/core/public/kibana-plugin-public.appbase.updater_.md) | Use the `updater$` property when registering your application via `core.application.register` | | `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | | `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | | `chrome.helpExtension.set` | [`core.chrome.setHelpExtension`](/docs/development/core/public/kibana-plugin-public.chromestart.sethelpextension.md) | | diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 04535039acbe7..d7964a53358ef 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -15,6 +15,7 @@ APIs to their New Platform equivalents. - [4. New Platform plugin](#4-new-platform-plugin) - [Accessing Services](#accessing-services) - [Chrome](#chrome) + - [Updating an application navlink](#updating-application-navlink) ## Configuration @@ -462,7 +463,59 @@ elsewhere. | `chrome.setVisible` | [`core.chrome.setIsVisible`](/docs/development/core/public/kibana-plugin-public.chromestart.setisvisible.md) | | | `chrome.getInjected` | [`core.injectedMetadata.getInjected`](/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md) (temporary) | A temporary API is available to read injected vars provided by legacy plugins. This will be removed after [#41990](https://github.com/elastic/kibana/issues/41990) is completed. | | `chrome.setRootTemplate` / `chrome.setRootController` | -- | Use application mounting via `core.application.register` (not currently avaiable to legacy plugins). | +| `chrome.navLinks.update` | [`core.appbase.updater`](/docs/development/core/public/kibana-plugin-public.appbase.updater_.md) | Use the `updater$` property when registering your application via `core.application.register` | In most cases, the most convenient way to access these APIs will be via the [AppMountContext](/docs/development/core/public/kibana-plugin-public.appmountcontext.md) object passed to your application when your app is mounted on the page. + +### Updating an application navlink + +In the legacy platform, the navlink could be updated using `chrome.navLinks.update` + +```ts +uiModules.get('xpack/ml').run(() => { + const showAppLink = xpackInfo.get('features.ml.showLinks', false); + const isAvailable = xpackInfo.get('features.ml.isAvailable', false); + + const navLinkUpdates = { + // hide by default, only show once the xpackInfo is initialized + hidden: !showAppLink, + disabled: !showAppLink || (showAppLink && !isAvailable), + }; + + npStart.core.chrome.navLinks.update('ml', navLinkUpdates); +}); +``` + +In the new platform, navlinks should not be updated directly. Instead, it is now possible to add an `updater` when +registering an application to change the application or the navlink state at runtime. + +```ts +// my_plugin has a required dependencie to the `licensing` plugin +interface MyPluginSetupDeps { + licensing: LicensingPluginSetup; +} + +export class MyPlugin implements Plugin { + setup({ application }, { licensing }: MyPluginSetupDeps) { + const updater$ = licensing.license$.pipe( + map(license => { + const { hidden, disabled } = calcStatusFor(license); + if (hidden) return { navLinkStatus: AppNavLinkStatus.hidden }; + if (disabled) return { navLinkStatus: AppNavLinkStatus.disabled }; + return { navLinkStatus: AppNavLinkStatus.default }; + }) + ); + + application.register({ + id: 'my-app', + title: 'My App', + updater$, + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } +``` \ No newline at end of file diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index 650ef77b6fe42..fec9322b0d77d 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -72,6 +72,10 @@ export interface ChromeNavLinks { /** * Update the navlink for the given id with the updated attributes. * Returns the updated navlink or `undefined` if it does not exist. + * + * @deprecated Uses the {@link AppBase.updater$} property when registering + * your application with {@link ApplicationSetup.register} instead. + * * @param id * @param values */ diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 36b220f16f395..1046f7a17dc51 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`, @@ -199,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 aef689162f45a..abd39e864bd30 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -279,6 +279,7 @@ export interface ChromeNavLinks { getNavLinks$(): Observable>>; has(id: string): boolean; showOnly(id: string): void; + // @deprecated update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; } @@ -486,7 +487,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/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 36fe95e05cb53..c63c9384da9d8 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/index.ts b/src/core/server/index.ts index eccf3985fc495..50d291b173640 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -152,7 +152,7 @@ export { SessionCookieValidationResult, SessionStorageFactory, } from './http'; -export { RenderingServiceSetup, IRenderOptions, LegacyRenderOptions } from './rendering'; +export { RenderingServiceSetup, IRenderOptions } from './rendering'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; export { @@ -216,6 +216,9 @@ export { UiSettingsServiceSetup, UiSettingsServiceStart, UserProvidedValues, + ImageValidation, + DeprecationSettings, + StringValidation, } from './ui_settings'; export { RecursiveReadonly } from '../utils'; diff --git a/src/core/server/legacy/legacy_internals.ts b/src/core/server/legacy/legacy_internals.ts index 3bf54e5f75dce..628ca4ed12f6b 100644 --- a/src/core/server/legacy/legacy_internals.ts +++ b/src/core/server/legacy/legacy_internals.ts @@ -19,7 +19,8 @@ import { Server } from 'hapi'; -import { LegacyRequest } from '../http'; +import { KibanaRequest, LegacyRequest } from '../http'; +import { ensureRawRequest } from '../http/router'; import { mergeVars } from './merge_vars'; import { ILegacyInternals, LegacyVars, VarsInjector, LegacyConfig, LegacyUiExports } from './types'; @@ -51,11 +52,12 @@ export class LegacyInternals implements ILegacyInternals { )); } - private replaceVars(vars: LegacyVars, request: LegacyRequest) { + private replaceVars(vars: LegacyVars, request: KibanaRequest | LegacyRequest) { const { injectedVarsReplacers = [] } = this.uiExports; return injectedVarsReplacers.reduce( - async (injected, replacer) => replacer(await injected, request, this.server), + async (injected, replacer) => + replacer(await injected, ensureRawRequest(request), this.server), Promise.resolve(vars) ); } @@ -78,7 +80,11 @@ export class LegacyInternals implements ILegacyInternals { ); } - public async getVars(id: string, request: LegacyRequest, injected: LegacyVars = {}) { + public async getVars( + id: string, + request: KibanaRequest | LegacyRequest, + injected: LegacyVars = {} + ) { return this.replaceVars( mergeVars(this.defaultVars, await this.getInjectedUiAppVars(id), injected), request diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 7a03cefc38c1a..ffcbf1662ee85 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -31,6 +31,7 @@ import { PathConfigType } from '../path'; import { findLegacyPluginSpecs } from './plugins'; import { convertLegacyDeprecationProvider } from './config'; import { + ILegacyInternals, LegacyServiceSetupDeps, LegacyServiceStartDeps, LegacyPlugins, @@ -82,6 +83,7 @@ export class LegacyService implements CoreService { private legacyRawConfig?: LegacyConfig; private legacyPlugins?: LegacyPlugins; private settings?: LegacyVars; + public legacyInternals?: ILegacyInternals; constructor(private readonly coreContext: CoreContext) { const { logger, configService } = coreContext; @@ -183,6 +185,11 @@ export class LegacyService implements CoreService { // propagate the instance uuid to the legacy config, as it was the legacy way to access it. this.legacyRawConfig!.set('server.uuid', setupDeps.core.uuid.getInstanceUuid()); this.setupDeps = setupDeps; + this.legacyInternals = new LegacyInternals( + this.legacyPlugins.uiExports, + this.legacyRawConfig!, + setupDeps.core.http.server + ); } public async start(startDeps: LegacyServiceStartDeps) { @@ -317,7 +324,7 @@ export class LegacyService implements CoreService { rendering: setupDeps.core.rendering, uiSettings: setupDeps.core.uiSettings, savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, - legacy: new LegacyInternals(legacyPlugins.uiExports, config, setupDeps.core.http.server), + legacy: this.legacyInternals, }, logger: this.coreContext.logger, }, diff --git a/src/core/server/legacy/types.ts b/src/core/server/legacy/types.ts index 6ec893be9b310..40b8244a31890 100644 --- a/src/core/server/legacy/types.ts +++ b/src/core/server/legacy/types.ts @@ -20,7 +20,7 @@ import { Server } from 'hapi'; import { ChromeNavLink } from '../../public'; -import { LegacyRequest } from '../http'; +import { KibanaRequest, LegacyRequest } from '../http'; import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { PluginsServiceSetup, PluginsServiceStart } from '../plugins'; import { RenderingServiceSetup } from '../rendering'; @@ -198,7 +198,11 @@ export interface ILegacyInternals { /** * Get the metadata vars for a particular plugin */ - getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise; + getVars( + id: string, + request: KibanaRequest | LegacyRequest, + injected?: LegacyVars + ): Promise; } /** diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 41810c6a10655..11d1fb271c81d 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -27,10 +27,10 @@ import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Template } from './views'; import { + IRenderOptions, RenderingSetupDeps, RenderingServiceSetup, RenderingMetadata, - LegacyRenderOptions, } from './types'; /** @internal */ @@ -56,7 +56,7 @@ export class RenderingService implements CoreService { app = { getId: () => 'core' }, includeUserSettings = true, vars = {}, - }: LegacyRenderOptions = {} + }: IRenderOptions = {} ) => { const { env } = this.coreContext; const basePath = http.basePath.get(request); diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 31b326bab6c78..3f9f6ff294909 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -84,21 +84,19 @@ export interface IRenderOptions { * `true` by default. */ includeUserSettings?: boolean; -} -/** - * @internal - * @deprecated for legacy use only, remove with ui_render_mixin - */ -export interface LegacyRenderOptions extends IRenderOptions { /** * Render the bootstrapped HTML content for an optional legacy application. * Defaults to `core`. + * @deprecated for legacy use only, remove with ui_render_mixin + * @internal */ app?: { getId(): string }; /** * Inject custom vars into the page metadata. + * @deprecated for legacy use only, remove with ui_render_mixin + * @internal */ vars?: Record; } @@ -123,7 +121,7 @@ export interface IScopedRenderingClient { * ); * ``` */ - render(options?: IRenderOptions): Promise; + render(options?: Pick): Promise; } /** @internal */ @@ -140,6 +138,6 @@ export interface RenderingServiceSetup { render( request: R, uiSettings: IUiSettingsClient, - options?: R extends LegacyRequest ? LegacyRenderOptions : IRenderOptions + options?: IRenderOptions ): Promise; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 7f3a960571012..6a58666716f42 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -635,6 +635,12 @@ export interface DeprecationInfo { url: string; } +// @public +export interface DeprecationSettings { + docLinksKey: string; + message: string; +} + // @public export interface DiscoveredPlugin { readonly configPath: ConfigPath; @@ -795,6 +801,17 @@ export interface IKibanaSocket { getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; } +// Warning: (ae-missing-release-tag) "ImageValidation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ImageValidation { + // (undocumented) + maxSize: { + length: number; + description: string; + }; +} + // @public (undocumented) export interface IndexSettingsDeprecationInfo { // (undocumented) @@ -803,7 +820,13 @@ export interface IndexSettingsDeprecationInfo { // @public (undocumented) export interface IRenderOptions { + // @internal @deprecated + app?: { + getId(): string; + }; includeUserSettings?: boolean; + // @internal @deprecated + vars?: Record; } // @public @@ -832,7 +855,7 @@ export type IScopedClusterClient = Pick; + render(options?: Pick): Promise; } // @public @@ -932,21 +955,13 @@ export class LegacyInternals implements ILegacyInternals { // (undocumented) getInjectedUiAppVars(id: string): Promise>; // (undocumented) - getVars(id: string, request: LegacyRequest, injected?: LegacyVars): Promise>; + getVars(id: string, request: KibanaRequest | LegacyRequest, injected?: LegacyVars): Promise>; // Warning: (ae-forgotten-export) The symbol "VarsInjector" needs to be exported by the entry point index.d.ts // // (undocumented) injectUiAppVars(id: string, injector: VarsInjector): void; } -// @internal @deprecated (undocumented) -export interface LegacyRenderOptions extends IRenderOptions { - app?: { - getId(): string; - }; - vars?: Record; -} - // @public @deprecated (undocumented) export interface LegacyRequest extends Request { } @@ -1233,7 +1248,7 @@ export type RedirectResponseOptions = HttpResponseOptions & { // @internal (undocumented) export interface RenderingServiceSetup { - render(request: R, uiSettings: IUiSettingsClient, options?: R extends LegacyRequest ? LegacyRenderOptions : IRenderOptions): Promise; + render(request: R, uiSettings: IUiSettingsClient, options?: IRenderOptions): Promise; } // @public @@ -1925,10 +1940,19 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ path: Pick; }>; +// Warning: (ae-missing-release-tag) "StringValidation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface StringValidation { + // (undocumented) + message: string; + // (undocumented) + regexString: string; +} + // @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; @@ -1937,9 +1961,6 @@ export interface UiSettingsParams { readonly?: boolean; requiresPageReload?: boolean; type?: UiSettingsType; - // Warning: (ae-forgotten-export) The symbol "ImageValidation" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "StringValidation" needs to be exported by the entry point index.d.ts - // // (undocumented) validation?: ImageValidation | StringValidation; value?: SavedObjectAttribute; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 7c3f9f249db13..89a5bdc4802fd 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -220,7 +220,11 @@ export class Server { return { rendering: { - render: rendering.render.bind(rendering, req, uiSettingsClient), + render: async (options = {}) => + rendering.render(req, uiSettingsClient, { + ...options, + vars: await this.legacy.legacyInternals!.getVars('core', req), + }), }, savedObjects: { client: savedObjectsClient, diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts index f1185474c2160..39eb0046010b3 100644 --- a/src/core/server/ui_settings/index.ts +++ b/src/core/server/ui_settings/index.ts @@ -31,4 +31,7 @@ export { InternalUiSettingsServiceStart, UiSettingsType, UserProvidedValues, + ImageValidation, + DeprecationSettings, + StringValidation, } from './types'; diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index 14eb71a22cefc..a74a31bbbd671 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -78,7 +78,9 @@ export interface UserProvidedValues { * @public * */ export interface DeprecationSettings { + /** Deprecation message */ message: string; + /** Key to documentation links */ docLinksKey: string; } diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 1c95e75396bcc..807a3fbf4782b 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -58,6 +58,7 @@ export default { '^ui/(.*)': '/src/legacy/ui/public/$1', '^uiExports/(.*)': '/src/dev/jest/mocks/file_mock.js', '^test_utils/(.*)': '/src/test_utils/public/$1', + '^fixtures/(.*)': '/src/fixtures/$1', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/dev/jest/mocks/file_mock.js', '\\.(css|less|scss)$': '/src/dev/jest/mocks/style_mock.js', diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index bd084767a723f..8dccb9830526c 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -101,9 +101,6 @@ export const LICENSE_OVERRIDES = { // TODO can be removed if the PR#9 is accepted on the source 'pause-stream@0.0.11': ['MIT'], - // TODO can be removed once we upgrade past or equal pdf-image@2.0.1 - 'pdf-image@1.1.0': ['MIT'], - // TODO can be removed once we upgrade the use of walk dependency past or equal to v2.3.14 'walk@2.3.9': ['MIT'], }; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index e18852e353b00..e5493df0aecf7 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -52,6 +52,9 @@ export const IGNORE_FILE_GLOBS = [ // filename must match language code which requires capital letters '**/translations/*.json', + + // filename is required by storybook + 'packages/kbn-storybook/storybook_config/preview-head.html', ]; /** @@ -117,7 +120,7 @@ export const TEMPORARILY_IGNORED_PATHS = [ 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/seriesList.js', 'src/legacy/core_plugins/timelion/server/series_functions/__tests__/fixtures/tlConfig.js', 'src/fixtures/config_upgrade_from_4.0.0_to_4.0.1-snapshot.json', - 'src/fixtures/vislib/mock_data/terms/_seriesMultiple.js', + 'src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js', 'src/legacy/ui/public/angular-bootstrap/bindHtml/bindHtml.js', 'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-html-unsafe-popup.html', 'src/legacy/ui/public/angular-bootstrap/tooltip/tooltip-popup.html', diff --git a/src/dev/register_git_hook/register_git_hook.js b/src/dev/register_git_hook/register_git_hook.js index 99ae351fe383e..8820327d3adc0 100644 --- a/src/dev/register_git_hook/register_git_hook.js +++ b/src/dev/register_git_hook/register_git_hook.js @@ -65,7 +65,7 @@ function getKbnPrecommitGitHookScript(rootPath, nodeHome, platform) { # The correct exit code on that situation # according the linux documentation project is 130 # https://www.tldp.org/LDP/abs/html/exitcodes.html - trap "exit 130" SIGINT + trap "exit 130" INT has_node() { command -v node >/dev/null 2>&1 diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts new file mode 100644 index 0000000000000..1dce53b6c2a84 --- /dev/null +++ b/src/dev/storybook/aliases.ts @@ -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. + */ + +export const storybookAliases = { + apm: 'x-pack/legacy/plugins/apm/scripts/storybook.js', + canvas: 'x-pack/legacy/plugins/canvas/scripts/storybook_new.js', + embeddable: 'src/plugins/embeddable/scripts/storybook.js', + infra: 'x-pack/legacy/plugins/infra/scripts/storybook.js', + siem: 'x-pack/legacy/plugins/siem/scripts/storybook.js', +}; diff --git a/src/dev/storybook/commands/clean.ts b/src/dev/storybook/commands/clean.ts new file mode 100644 index 0000000000000..328c4d9e2c23c --- /dev/null +++ b/src/dev/storybook/commands/clean.ts @@ -0,0 +1,32 @@ +/* + * 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 { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { join } from 'path'; +import del from 'del'; + +export const clean = async ({ log }: { log: ToolingLog }) => { + log.info('Cleaning Storybook build folder'); + + const dir = join(REPO_ROOT, 'built_assets', 'storybook'); + log.info('Deleting folder:', dir); + await del([join(dir, '*')]); + await del([dir]); +}; diff --git a/src/dev/storybook/run_storybook_cli.ts b/src/dev/storybook/run_storybook_cli.ts new file mode 100644 index 0000000000000..0f7dc40ceef0b --- /dev/null +++ b/src/dev/storybook/run_storybook_cli.ts @@ -0,0 +1,78 @@ +/* + * 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 { join } from 'path'; +import { run, createFlagError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { storybookAliases } from './aliases'; +import { clean } from './commands/clean'; + +run( + async params => { + const { flags, log } = params; + const { + _: [alias], + } = flags; + + if (flags.verbose) { + log.verbose('Flags:', flags); + } + + if (flags.clean) { + await clean({ log }); + return; + } + + if (!alias) { + throw createFlagError('missing alias'); + } + + if (!storybookAliases.hasOwnProperty(alias)) { + throw createFlagError(`unknown alias [${alias}]`); + } + + const relative = (storybookAliases as any)[alias]; + const absolute = join(REPO_ROOT, relative); + + log.verbose('Loading Storybook:', absolute); + process.chdir(join(absolute, '..', '..')); + require(absolute); + }, + { + usage: `node scripts/storybook `, + description: ` + Start a 📕 Storybook for a plugin + + Available aliases: + ${Object.keys(storybookAliases) + .map(alias => `📕 ${alias}`) + .join('\n ')} + + Add your alias in src/dev/storybook/aliases.ts + `, + flags: { + default: {}, + string: [], + boolean: ['clean'], + help: ` + --clean Clean Storybook build folder. + `, + }, + } +); diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js b/src/legacy/core_plugins/console/common/text_object.ts similarity index 58% rename from src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js rename to src/legacy/core_plugins/console/common/text_object.ts index 1f0788cf74d1d..3b3464a77ac4e 100644 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/fancy_forms.js +++ b/src/legacy/core_plugins/console/common/text_object.ts @@ -17,13 +17,32 @@ * under the License. */ -import { uiModules } from '../../../../modules'; +export const textObjectTypeName = 'text-object'; -import { decorateFormController } from './kbn_form_controller'; -import { decorateModelController } from './kbn_model_controller'; +/** + * 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; -uiModules.get('kibana').config(function($provide) { - $provide.decorator('formDirective', decorateFormController); - $provide.decorator('ngFormDirective', decorateFormController); - $provide.decorator('ngModelDirective', decorateModelController); -}); + /** + * 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 0000000000000..33d6907ff60b8 --- /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 0000000000000..2ae4545ad24ef --- /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 0f109e99b0b39..4669e210e7c2d 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 0000000000000..7b643bc84dd34 --- /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/split_panel/__snapshots__/split_panel.test.tsx.snap b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap index 0a40e3e84211d..36f4dec1a1f54 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/__snapshots__/split_panel.test.tsx.snap @@ -8,13 +8,13 @@ exports[`Split panel should render correctly 1`] = ` "panels": Array [ Object { "getWidth": [Function], - "initialWidth": "100%", "setWidth": [Function], + "width": 100, }, Object { "getWidth": [Function], - "initialWidth": "100%", "setWidth": [Function], + "width": 100, }, ], } @@ -55,15 +55,39 @@ exports[`Split panel should render correctly 1`] = ` -
- ︙ -
+ + + + + +
; +export type ResizerMouseEvent = React.MouseEvent; +export type ResizerKeyDownEvent = React.KeyboardEvent; export interface Props { + onKeyDown: (eve: ResizerKeyDownEvent) => void; onMouseDown: (eve: ResizerMouseEvent) => void; + className?: string; } -/** - * TODO: This component uses styling constants from public UI - should be removed, next iteration should incl. horizontal and vertical resizers. - */ export function Resizer(props: Props) { return ( -
- ︙ -
+ ); } diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx index 80960a7772ba1..2eb39f0808ad0 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel.tsx @@ -22,20 +22,26 @@ import { usePanelContext } from '../context'; export interface Props { children: ReactNode[] | ReactNode; - initialWidth?: string; + className?: string; + + /** + * initial width of the panel in percents + */ + initialWidth?: number; style?: CSSProperties; } -export function Panel({ children, initialWidth = '100%', style = {} }: Props) { - const [width, setWidth] = useState(initialWidth); +export function Panel({ children, className, initialWidth = 100, style = {} }: Props) { + const [width, setWidth] = useState(`${initialWidth}%`); const { registry } = usePanelContext(); const divRef = useRef(null); useEffect(() => { registry.registerPanel({ - initialWidth, + width: initialWidth, setWidth(value) { setWidth(value + '%'); + this.width = value; }, getWidth() { return divRef.current!.getBoundingClientRect().width; @@ -44,7 +50,7 @@ export function Panel({ children, initialWidth = '100%', style = {} }: Props) { }, [initialWidth, registry]); return ( -
+
{children}
); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx index fef65a954bd60..c9d7b01f87967 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/containers/panel_container.tsx @@ -17,14 +17,17 @@ * under the License. */ -import React, { Children, ReactNode, useRef, useState } from 'react'; +import React, { Children, ReactNode, useRef, useState, useCallback } from 'react'; +import { keyCodes } from '@elastic/eui'; import { PanelContextProvider } from '../context'; -import { Resizer } from '../components/resizer'; +import { Resizer, ResizerMouseEvent, ResizerKeyDownEvent } from '../components/resizer'; import { PanelRegistry } from '../registry'; export interface Props { children: ReactNode; + className?: string; + resizerClassName?: string; onPanelWidthChange?: (arrayOfPanelWidths: number[]) => any; } @@ -37,7 +40,12 @@ const initialState: State = { isDragging: false, currentResizerPos: -1 }; const pxToPercent = (proportion: number, whole: number) => (proportion / whole) * 100; -export function PanelsContainer({ children, onPanelWidthChange }: Props) { +export function PanelsContainer({ + children, + className, + onPanelWidthChange, + resizerClassName, +}: Props) { const [firstChild, secondChild] = Children.toArray(children); const registryRef = useRef(new PanelRegistry()); @@ -48,18 +56,48 @@ export function PanelsContainer({ children, onPanelWidthChange }: Props) { return containerRef.current!.getBoundingClientRect().width; }; + const handleMouseDown = useCallback( + (event: ResizerMouseEvent) => { + setState({ + ...state, + isDragging: true, + currentResizerPos: event.clientX, + }); + }, + [state] + ); + + const handleKeyDown = useCallback( + (ev: ResizerKeyDownEvent) => { + const { keyCode } = ev; + + if (keyCode === keyCodes.LEFT || keyCode === keyCodes.RIGHT) { + ev.preventDefault(); + + const { current: registry } = registryRef; + const [left, right] = registry.getPanels(); + + const leftPercent = left.width - (keyCode === keyCodes.LEFT ? 1 : -1); + const rightPercent = right.width - (keyCode === keyCodes.RIGHT ? 1 : -1); + + left.setWidth(leftPercent); + right.setWidth(rightPercent); + + if (onPanelWidthChange) { + onPanelWidthChange([leftPercent, rightPercent]); + } + } + }, + [onPanelWidthChange] + ); + const childrenWithResizer = [ firstChild, { - event.preventDefault(); - setState({ - ...state, - isDragging: true, - currentResizerPos: event.clientX, - }); - }} + className={resizerClassName} + onKeyDown={handleKeyDown} + onMouseDown={handleMouseDown} />, secondChild, ]; @@ -67,6 +105,7 @@ export function PanelsContainer({ children, onPanelWidthChange }: Props) { return (
{ diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts index 5f06ab8915270..e275da9e2ac74 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/registry.ts @@ -20,7 +20,7 @@ export interface PanelController { setWidth: (percent: number) => void; getWidth: () => number; - initialWidth: string; + width: number; } export class PanelRegistry { @@ -35,6 +35,6 @@ export class PanelRegistry { } getPanels() { - return this.panels.map(panel => ({ ...panel })); + return this.panels; } } diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx index 304535421a78a..02153d1a1d3cd 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/split_panel/split_panel.test.tsx @@ -65,8 +65,8 @@ describe('Split panel', () => { const panelContainer = mount( - {testComponentA} - {testComponentB} + {testComponentA} + {testComponentB} ); 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 c83237c52febd..92f3f32da9a4e 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 07b48c083bf61..7be1382760eb9 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 73ee6d160613f..d4079fcea33f8 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 761a252b56a87..759e3dbafb39c 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 764c4b8e87100..902d800b3e56b 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 f14685ecd4ac7..d7f036e1aecb6 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 8c5a8d599a0df..72e83e883553d 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 0000000000000..08acd78ba2b8a --- /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/plugins/data/public/suggestions_provider/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts similarity index 92% rename from src/plugins/data/public/suggestions_provider/index.ts rename to src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts index c83662043b822..582aa047f7d40 100644 --- a/src/plugins/data/public/suggestions_provider/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts @@ -16,5 +16,4 @@ * specific language governing permissions and limitations * under the License. */ - -export { getSuggestionsProvider } from './value_suggestions'; +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 0000000000000..2212827c1f598 --- /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 0000000000000..ab517ba1bfdd1 --- /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 89756513b2b22..efd0f2ba86024 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/mode/input_highlight_rules.js b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js index b7b6c5bade9ad..842736428e8bb 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/mode/input_highlight_rules.js @@ -89,6 +89,7 @@ export function InputHighlightRules() { addEOL(['url.amp'], /(&)/, 'start') ), 'url-sql': mergeTokens( + addEOL(['url.part'], /([^?\/,\s]+)/, 'start-sql'), addEOL(['url.comma'], /(,)/, 'start-sql'), addEOL(['url.slash'], /(\/)/, 'start-sql'), addEOL(['url.questionmark'], /(\?)/, 'start-sql', 'urlParams-sql') 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 339a2f7a2c4af..844eacd9b91a8 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 159e9f9e8a173..c69440225236b 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 0000000000000..36948b9acb962 --- /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 0000000000000..c170b8721a04d --- /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 0000000000000..41c88d23b2533 --- /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 cbe262b124677..22351ae95ba87 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 6cb6f7ba35cd4..04dae0beacefe 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/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 278811ca85df9..249f42a6ebf3f 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 @@ -44,11 +44,10 @@ exports[`renders ControlsTab 1`] = ` } } getIndexPattern={[Function]} - handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} handleIndexPatternChange={[Function]} handleLabelChange={[Function]} - handleNumberOptionChange={[Function]} + handleOptionsChange={[Function]} handleParentChange={[Function]} handleRemoveControl={[Function]} key="1" @@ -101,11 +100,10 @@ exports[`renders ControlsTab 1`] = ` } } getIndexPattern={[Function]} - handleCheckboxOptionChange={[Function]} handleFieldNameChange={[Function]} handleIndexPatternChange={[Function]} handleLabelChange={[Function]} - handleNumberOptionChange={[Function]} + handleOptionsChange={[Function]} handleParentChange={[Function]} handleRemoveControl={[Function]} key="2" diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx index dbac5d9d94371..2bd0baea6eff8 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -29,7 +29,6 @@ import { EuiFormRow, EuiPanel, EuiSpacer, - EuiSwitchEvent, } from '@elastic/eui'; import { RangeControlEditor } from './range_control_editor'; @@ -41,33 +40,28 @@ import { InputControlVisDependencies } from '../../plugin'; interface ControlEditorUiProps { controlIndex: number; controlParams: ControlParams; - handleLabelChange: (controlIndex: number, event: ChangeEvent) => void; + handleLabelChange: (controlIndex: number, value: string) => void; moveControl: (controlIndex: number, direction: number) => void; handleRemoveControl: (controlIndex: number) => void; handleIndexPatternChange: (controlIndex: number, indexPatternId: string) => void; handleFieldNameChange: (controlIndex: number, fieldName: string) => void; getIndexPattern: (indexPatternId: string) => Promise; - handleCheckboxOptionChange: ( + handleOptionsChange: ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: EuiSwitchEvent - ) => void; - handleNumberOptionChange: ( - controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent + optionName: T, + value: ControlParamsOptions[T] ) => void; parentCandidates: Array<{ value: string; text: string; }>; - handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + handleParentChange: (controlIndex: number, parent: string) => void; deps: InputControlVisDependencies; } class ControlEditorUi extends PureComponent { changeLabel = (event: ChangeEvent) => { - this.props.handleLabelChange(this.props.controlIndex, event); + this.props.handleLabelChange(this.props.controlIndex, event.target.value); }; removeControl = () => { @@ -101,8 +95,7 @@ class ControlEditorUi extends PureComponent ); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx index 56381ef7d1570..214cff4ddf9d5 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { PureComponent, ChangeEvent } from 'react'; +import React, { PureComponent } from 'react'; import { InjectedIntlProps } from 'react-intl'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; @@ -28,7 +28,6 @@ import { EuiFormRow, EuiPanel, EuiSelect, - EuiSwitchEvent, } from '@elastic/eui'; import { ControlEditor } from './control_editor'; @@ -73,44 +72,44 @@ class ControlsTabUi extends PureComponent this.props.setValue('controls', value); - handleLabelChange = (controlIndex: number, event: ChangeEvent) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.label = event.target.value; + handleLabelChange = (controlIndex: number, label: string) => { + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + label, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleIndexPatternChange = (controlIndex: number, indexPatternId: string) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.indexPattern = indexPatternId; - updatedControl.fieldName = ''; + handleIndexPatternChange = (controlIndex: number, indexPattern: string) => { + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + indexPattern, + fieldName: '', + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; handleFieldNameChange = (controlIndex: number, fieldName: string) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.fieldName = fieldName; + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + fieldName, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; - handleCheckboxOptionChange = ( + handleOptionsChange = ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: EuiSwitchEvent + optionName: T, + value: ControlParamsOptions[T] ) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - // @ts-ignore - updatedControl.options[optionName] = event.target.checked; - this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); - }; - - handleNumberOptionChange = ( - controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent - ) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - // @ts-ignore - updatedControl.options[optionName] = parseFloat(event.target.value); + const control = this.props.stateParams.controls[controlIndex]; + const updatedControl = { + ...control, + options: { + ...control.options, + [optionName]: value, + }, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; @@ -126,9 +125,11 @@ class ControlsTabUi extends PureComponent) => { - const updatedControl = this.props.stateParams.controls[controlIndex]; - updatedControl.parent = event.target.value; + handleParentChange = (controlIndex: number, parent: string) => { + const updatedControl = { + ...this.props.stateParams.controls[controlIndex], + parent, + }; this.onChange(setControl(this.props.stateParams.controls, controlIndex, updatedControl)); }; @@ -151,8 +152,7 @@ class ControlsTabUi extends PureComponent { handleFieldNameChange = sinon.spy(); handleIndexPatternChange = sinon.spy(); - handleCheckboxOptionChange = sinon.spy(); - handleNumberOptionChange = sinon.spy(); + handleOptionsChange = sinon.spy(); }); describe('renders', () => { @@ -82,8 +80,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -107,8 +104,7 @@ describe('renders', () => { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={parentCandidates} /> @@ -143,8 +139,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -178,8 +173,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -213,8 +207,7 @@ describe('renders', () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -227,7 +220,7 @@ describe('renders', () => { }); }); -test('handleCheckboxOptionChange - multiselect', async () => { +test('handleOptionsChange - multiselect', async () => { const component = mountWithIntl( { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -249,25 +241,12 @@ test('handleCheckboxOptionChange - multiselect', async () => { checkbox.simulate('click'); sinon.assert.notCalled(handleFieldNameChange); sinon.assert.notCalled(handleIndexPatternChange); - sinon.assert.notCalled(handleNumberOptionChange); const expectedControlIndex = 0; const expectedOptionName = 'multiselect'; - sinon.assert.calledWith( - handleCheckboxOptionChange, - expectedControlIndex, - expectedOptionName, - sinon.match(event => { - // Synthetic `event.target.checked` does not get altered by EuiSwitch, - // but its aria attribute is correctly updated - if (event.target.getAttribute('aria-checked') === 'true') { - return true; - } - return false; - }, 'unexpected checkbox input event') - ); + sinon.assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName); }); -test('handleNumberOptionChange - size', async () => { +test('handleOptionsChange - size', async () => { const component = mountWithIntl( { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> @@ -286,23 +264,12 @@ test('handleNumberOptionChange - size', async () => { await updateComponent(component); const input = findTestSubject(component, 'listControlSizeInput'); - input.simulate('change', { target: { value: 7 } }); - sinon.assert.notCalled(handleCheckboxOptionChange); + input.simulate('change', { target: { valueAsNumber: 7 } }); sinon.assert.notCalled(handleFieldNameChange); sinon.assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'size'; - sinon.assert.calledWith( - handleNumberOptionChange, - expectedControlIndex, - expectedOptionName, - sinon.match(event => { - if (event.target.value === 7) { - return true; - } - return false; - }, 'unexpected input event') - ); + sinon.assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 7); }); test('field name change', async () => { @@ -314,8 +281,7 @@ test('field name change', async () => { controlParams={controlParamsBase} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleCheckboxOptionChange={handleCheckboxOptionChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} handleParentChange={() => {}} parentCandidates={[]} /> diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx index ed68894d39ae4..9772cb5fc2548 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.tsx @@ -17,10 +17,10 @@ * under the License. */ -import React, { PureComponent, ChangeEvent, ComponentType } from 'react'; +import React, { PureComponent, ComponentType } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect, EuiSwitchEvent } from '@elastic/eui'; +import { EuiFormRow, EuiFieldNumber, EuiSwitch, EuiSelect } from '@elastic/eui'; import { IndexPatternSelectFormRow } from './index_pattern_select_form_row'; import { FieldSelect } from './field_select'; @@ -45,17 +45,12 @@ interface ListControlEditorProps { controlParams: ControlParams; handleFieldNameChange: (fieldName: string) => void; handleIndexPatternChange: (indexPatternId: string) => void; - handleCheckboxOptionChange: ( + handleOptionsChange: ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: EuiSwitchEvent + optionName: T, + value: ControlParamsOptions[T] ) => void; - handleNumberOptionChange: ( - controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent - ) => void; - handleParentChange: (controlIndex: number, event: ChangeEvent) => void; + handleParentChange: (controlIndex: number, parent: string) => void; parentCandidates: React.ComponentProps['options']; deps: InputControlVisDependencies; } @@ -177,7 +172,7 @@ export class ListControlEditor extends PureComponent< options={parentCandidatesOptions} value={this.props.controlParams.parent} onChange={event => { - this.props.handleParentChange(this.props.controlIndex, event); + this.props.handleParentChange(this.props.controlIndex, event.target.value); }} /> @@ -204,7 +199,11 @@ export class ListControlEditor extends PureComponent< } checked={this.props.controlParams.options.multiselect ?? true} onChange={event => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'multiselect', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'multiselect', + event.target.checked + ); }} data-test-subj="listControlMultiselectInput" /> @@ -237,7 +236,11 @@ export class ListControlEditor extends PureComponent< } checked={this.props.controlParams.options.dynamicOptions ?? false} onChange={event => { - this.props.handleCheckboxOptionChange(this.props.controlIndex, 'dynamicOptions', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'dynamicOptions', + event.target.checked + ); }} disabled={this.state.isStringField ? false : true} data-test-subj="listControlDynamicOptionsSwitch" @@ -268,7 +271,11 @@ export class ListControlEditor extends PureComponent< min={1} value={this.props.controlParams.options.size} onChange={event => { - this.props.handleNumberOptionChange(this.props.controlIndex, 'size', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'size', + event.target.valueAsNumber + ); }} data-test-subj="listControlSizeInput" /> diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx index e7f9b6083890c..55c4c71ce430b 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { SinonSpy, spy, assert, match } from 'sinon'; +import { SinonSpy, spy, assert } from 'sinon'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; // @ts-ignore @@ -46,12 +46,12 @@ const controlParams: ControlParams = { const deps = getDepsMock(); let handleFieldNameChange: SinonSpy; let handleIndexPatternChange: SinonSpy; -let handleNumberOptionChange: SinonSpy; +let handleOptionsChange: SinonSpy; beforeEach(() => { handleFieldNameChange = spy(); handleIndexPatternChange = spy(); - handleNumberOptionChange = spy(); + handleOptionsChange = spy(); }); test('renders RangeControlEditor', async () => { @@ -63,7 +63,7 @@ test('renders RangeControlEditor', async () => { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} /> ); @@ -72,7 +72,7 @@ test('renders RangeControlEditor', async () => { expect(component).toMatchSnapshot(); // eslint-disable-line }); -test('handleNumberOptionChange - step', async () => { +test('handleOptionsChange - step', async () => { const component = mountWithIntl( { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} /> ); await updateComponent(component); findTestSubject(component, 'rangeControlSizeInput0').simulate('change', { - target: { value: 0.5 }, + target: { valueAsNumber: 0.5 }, }); assert.notCalled(handleFieldNameChange); assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'step'; - assert.calledWith( - handleNumberOptionChange, - expectedControlIndex, - expectedOptionName, - match(event => { - if (event.target.value === 0.5) { - return true; - } - return false; - }, 'unexpected input event') - ); + assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 0.5); }); -test('handleNumberOptionChange - decimalPlaces', async () => { +test('handleOptionsChange - decimalPlaces', async () => { const component = mountWithIntl( { controlParams={controlParams} handleFieldNameChange={handleFieldNameChange} handleIndexPatternChange={handleIndexPatternChange} - handleNumberOptionChange={handleNumberOptionChange} + handleOptionsChange={handleOptionsChange} /> ); await updateComponent(component); findTestSubject(component, 'rangeControlDecimalPlacesInput0').simulate('change', { - target: { value: 2 }, + target: { valueAsNumber: 2 }, }); assert.notCalled(handleFieldNameChange); assert.notCalled(handleIndexPatternChange); const expectedControlIndex = 0; const expectedOptionName = 'decimalPlaces'; - assert.calledWith( - handleNumberOptionChange, - expectedControlIndex, - expectedOptionName, - match(event => { - if (event.target.value === 2) { - return true; - } - return false; - }, 'unexpected input event') - ); + assert.calledWith(handleOptionsChange, expectedControlIndex, expectedOptionName, 2); }); diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx index 44477eafda6b1..97850879a2d38 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { Component, Fragment, ChangeEvent, ComponentType } from 'react'; +import React, { Component, Fragment, ComponentType } from 'react'; import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -37,10 +37,10 @@ interface RangeControlEditorProps { getIndexPattern: (indexPatternId: string) => Promise; handleFieldNameChange: (fieldName: string) => void; handleIndexPatternChange: (indexPatternId: string) => void; - handleNumberOptionChange: ( + handleOptionsChange: ( controlIndex: number, - optionName: keyof ControlParamsOptions, - event: ChangeEvent + optionName: T, + value: ControlParamsOptions[T] ) => void; deps: InputControlVisDependencies; } @@ -109,7 +109,11 @@ export class RangeControlEditor extends Component< { - this.props.handleNumberOptionChange(this.props.controlIndex, 'step', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'step', + event.target.valueAsNumber + ); }} data-test-subj={`rangeControlSizeInput${this.props.controlIndex}`} /> @@ -128,7 +132,11 @@ export class RangeControlEditor extends Component< min={0} value={this.props.controlParams.options.decimalPlaces} onChange={event => { - this.props.handleNumberOptionChange(this.props.controlIndex, 'decimalPlaces', event); + this.props.handleOptionsChange( + this.props.controlIndex, + 'decimalPlaces', + event.target.valueAsNumber + ); }} data-test-subj={`rangeControlDecimalPlacesInput${this.props.controlIndex}`} /> diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts index b6774aa87b43c..9473ea5a20b35 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts @@ -50,7 +50,6 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende pinFilters: false, }, }, - editor: 'default', editorConfig: { optionTabs: [ { diff --git a/src/legacy/core_plugins/interpreter/init.ts b/src/legacy/core_plugins/interpreter/init.ts index 768d76fbf744e..46da1539afadb 100644 --- a/src/legacy/core_plugins/interpreter/init.ts +++ b/src/legacy/core_plugins/interpreter/init.ts @@ -22,35 +22,10 @@ // @ts-ignore import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common'; -// @ts-ignore -import { routes } from './server/routes'; - -import { typeSpecs as types, Type } from '../../../plugins/expressions/common'; import { Legacy } from '../../../../kibana'; -export class TypesRegistry extends Registry { - wrapper(obj: any) { - return new (Type as any)(obj); - } -} - -export class FunctionsRegistry extends Registry { - wrapper(obj: any) { - return new Fn(obj); - } -} - -export const registries = { - types: new TypesRegistry(), - serverFunctions: new FunctionsRegistry(), -}; - export async function init(server: Legacy.Server /* options */) { server.injectUiAppVars('canvas', () => { - register(registries, { - types, - }); - const config = server.config(); const basePath = config.get('server.basePath'); const reportingBrowserType = (() => { @@ -63,7 +38,9 @@ export async function init(server: Legacy.Server /* options */) { return { kbnIndex: config.get('kibana.index'), - serverFunctions: registries.serverFunctions.toArray(), + serverFunctions: (server.newPlatform.setup.plugins.expressions as any).__LEGACY + .registries() + .serverFunctions.toArray(), basePath, reportingBrowserType, }; @@ -71,7 +48,5 @@ export async function init(server: Legacy.Server /* options */) { // Expose server.plugins.interpreter.register(specs) and // server.plugins.interpreter.registries() (a getter). - server.expose(registryFactory(registries)); - - routes(server); + server.expose((server.newPlatform.setup.plugins.expressions as any).__LEGACY); } diff --git a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts index 2c2f79b3d6f51..fed157846a1a1 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts +++ b/src/legacy/core_plugins/interpreter/public/canvas/load_legacy_server_function_wrappers.ts @@ -28,62 +28,6 @@ * server side, it should be respective function's internal implementation detail. */ -import { get, identity } from 'lodash'; -// @ts-ignore -import { npSetup, npStart } from 'ui/new_platform'; -import { FUNCTIONS_URL } from './consts'; -import { batchedFetch } from './batched_fetch'; +import { npSetup } from 'ui/new_platform'; -export function getType(node: any) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; - } - return typeof node; -} - -export function serializeProvider(types: any) { - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; - - function provider(key: any) { - return (context: any) => { - const type = getType(context); - const typeDef = types[type]; - const fn: any = get(typeDef, key) || identity; - return fn(context); - }; - } -} - -let cached: Promise | null = null; - -export const loadLegacyServerFunctionWrappers = async () => { - if (!cached) { - cached = (async () => { - const serverFunctionList = await npSetup.core.http.get(FUNCTIONS_URL); - const types = npSetup.plugins.expressions.__LEGACY.types.toJS(); - const { serialize } = serializeProvider(types); - const batch = batchedFetch({ - fetchStreaming: npStart.plugins.bfetch.fetchStreaming, - serialize, - }); - - // For every sever-side function, register a client-side - // function that matches its definition, but which simply - // calls the server-side function endpoint. - Object.keys(serverFunctionList).forEach(functionName => { - const fn = () => ({ - ...serverFunctionList[functionName], - fn: (context: any, args: any) => batch({ functionName, args, context }), - }); - npSetup.plugins.expressions.registerFunction(fn); - }); - })(); - } - - return cached; -}; +export const { loadLegacyServerFunctionWrappers } = npSetup.plugins.expressions.__LEGACY; diff --git a/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts b/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts index 66c51167c7b59..0f37f33cc1b13 100644 --- a/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts +++ b/src/legacy/core_plugins/interpreter/public/registries.karma_mock.ts @@ -26,6 +26,7 @@ export const registries = { browserFunctions: functionsRegistry, renderers: renderersRegistry, types: typesRegistry, + loadLegacyServerFunctionWrappers: () => Promise.resolve(), }; const resetRegistry = (registry: any) => { diff --git a/src/legacy/core_plugins/interpreter/server/lib/__tests__/create_handlers.ts b/src/legacy/core_plugins/interpreter/server/lib/__tests__/create_handlers.ts deleted file mode 100644 index 0088663080774..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/lib/__tests__/create_handlers.ts +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { createHandlers } from '../create_handlers'; - -const mockRequest = { - headers: 'i can haz headers', -}; - -const mockServer = { - plugins: { - elasticsearch: { - getCluster: () => ({ - callWithRequest: (...args: any) => Promise.resolve(args), - }), - }, - }, - config: () => ({ - has: () => false, - get: (val: any) => val, - }), - info: { - uri: 'serveruri', - }, -}; - -describe('server createHandlers', () => { - it('provides helper methods and properties', () => { - const handlers = createHandlers(mockRequest, mockServer); - - expect(handlers).to.have.property('environment', 'server'); - expect(handlers).to.have.property('serverUri'); - expect(handlers).to.have.property('elasticsearchClient'); - }); - - describe('elasticsearchClient', () => { - it('executes callWithRequest', async () => { - const handlers = createHandlers(mockRequest, mockServer); - const [request, endpoint, payload] = await handlers.elasticsearchClient( - 'endpoint', - 'payload' - ); - expect(request).to.equal(mockRequest); - expect(endpoint).to.equal('endpoint'); - expect(payload).to.equal('payload'); - }); - }); -}); diff --git a/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts b/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts deleted file mode 100644 index e03ad361b5555..0000000000000 --- a/src/legacy/core_plugins/interpreter/server/routes/server_functions.ts +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import Boom from 'boom'; -import Joi from 'joi'; -import { serializeProvider } from '../../../../../plugins/expressions/common'; -import { createHandlers } from '../lib/create_handlers'; - -const API_ROUTE = '/api/interpreter'; - -/** - * Register the Canvas function endopints. - * - * @param {*} server - The Kibana server - */ -export function registerServerFunctions(server: any) { - getServerFunctions(server); - runServerFunctions(server); -} - -/** - * Register the endpoint that executes a batch of functions, and sends the result back as a single response. - * - * @param {*} server - The Kibana server - */ -function runServerFunctions(server: any) { - server.route({ - method: 'POST', - path: `${API_ROUTE}/fns`, - options: { - payload: { - allow: 'application/json', - maxBytes: 26214400, // 25MB payload limit - }, - validate: { - payload: Joi.object({ - functions: Joi.array() - .items( - Joi.object().keys({ - id: Joi.number().required(), - functionName: Joi.string().required(), - args: Joi.object().default({}), - context: Joi.any().default(null), - }) - ) - .required(), - }).required(), - }, - }, - async handler(req: any) { - const handlers = await createHandlers(req, server); - const { functions } = req.payload; - - // Grab the raw Node response object. - const res = req.raw.res; - - // Tell Hapi not to manage the response https://github.com/hapijs/hapi/issues/3884 - req._isReplied = true; - - // Send the initial headers. - res.writeHead(200, { - 'Content-Type': 'application/x-ndjson', - Connection: 'keep-alive', - 'Transfer-Encoding': 'chunked', - 'Cache-Control': 'no-cache', - }); - - // Write a length-delimited response - const streamResult = (result: any) => { - res.write(JSON.stringify(result) + '\n'); - }; - - // Tries to run an interpreter function, and ensures a consistent error payload on failure. - const tryFunction = async (id: any, fnCall: any) => { - try { - const result = await runFunction(server, handlers, fnCall); - - if (typeof result === 'undefined') { - return batchError(id, `Function ${fnCall.functionName} did not return anything.`); - } - - return { id, statusCode: 200, result }; - } catch (err) { - if (Boom.isBoom(err)) { - return batchError(id, err.output.payload, (err as any).statusCode); - } else if (err instanceof Error) { - return batchError(id, err.message); - } - - server.log(['interpreter', 'error'], err); - return batchError(id, 'See server logs for details.'); - } - }; - - // Process each function individually, and stream the responses back to the client - await Promise.all( - functions.map(({ id, ...fnCall }: any) => tryFunction(id, fnCall).then(streamResult)) - ); - - // All of the responses have been written, so we can close the response. - res.end(); - }, - }); -} - -/** - * A helper function for bundling up errors. - */ -function batchError(id: any, message: any, statusCode = 500) { - return { - id, - statusCode, - result: { statusCode, message }, - }; -} - -/** - * Register the endpoint that returns the list of server-only functions. - * @param {*} server - The Kibana server - */ -function getServerFunctions(server: any) { - server.route({ - method: 'GET', - path: `${API_ROUTE}/fns`, - handler() { - return server.plugins.interpreter.registries().serverFunctions.toJS(); - }, - }); -} - -/** - * Run a single Canvas function. - * - * @param {*} server - The Kibana server object - * @param {*} handlers - The Canvas handlers - * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` - */ -async function runFunction(server: any, handlers: any, fnCall: any) { - const registries = server.plugins.interpreter.registries(); - const { functionName, args, context } = fnCall; - const types = registries.types.toJS(); - const { deserialize } = serializeProvider(types); - const fnDef = registries.serverFunctions.toJS()[functionName]; - - if (!fnDef) { - throw Boom.notFound(`Function "${functionName}" could not be found.`); - } - - return fnDef.fn(deserialize(context), args, handlers); -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/package.json b/src/legacy/core_plugins/kbn_vislib_vis_types/package.json deleted file mode 100644 index b6df6eb794de8..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "kbn_vislib_vis_types", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js deleted file mode 100644 index 46f2376cb03eb..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import React from 'react'; - -import { - CUSTOM_LEGEND_VIS_TYPES, - VisLegend, -} from '../../../ui/public/vis/vis_types/vislib_vis_legend'; -import { VislibVisProvider } from '../../../ui/public/vislib/vis'; -import chrome from '../../../ui/public/chrome'; -import { mountReactNode } from '../../../../core/public/utils'; - -const legendClassName = { - top: 'visLib--legend-top', - bottom: 'visLib--legend-bottom', - left: 'visLib--legend-left', - right: 'visLib--legend-right', -}; - -export class vislibVisController { - constructor(el, vis) { - this.el = el; - this.vis = vis; - this.unmount = null; - this.legendRef = React.createRef(); - - // vis mount point - this.container = document.createElement('div'); - this.container.className = 'visLib'; - this.el.appendChild(this.container); - - // chart mount point - this.chartEl = document.createElement('div'); - this.chartEl.className = 'visLib__chart'; - this.container.appendChild(this.chartEl); - // legend mount point - this.legendEl = document.createElement('div'); - this.legendEl.className = 'visLib__legend'; - this.container.appendChild(this.legendEl); - } - - render(esResponse, visParams) { - if (this.vislibVis) { - this.destroy(); - } - - return new Promise(async resolve => { - if (!this.vislib) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private'); - this.Vislib = Private(VislibVisProvider); - } - - if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { - return resolve(); - } - - this.vislibVis = new this.Vislib(this.chartEl, visParams); - this.vislibVis.on('brush', this.vis.API.events.brush); - this.vislibVis.on('click', this.vis.API.events.filter); - this.vislibVis.on('renderComplete', resolve); - - this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); - - if (visParams.addLegend) { - $(this.container) - .attr('class', (i, cls) => { - return cls.replace(/visLib--legend-\S+/g, ''); - }) - .addClass(legendClassName[visParams.legendPosition]); - - this.mountLegend(esResponse, visParams.legendPosition); - } - - this.vislibVis.render(esResponse, this.vis.getUiState()); - - // refreshing the legend after the chart is rendered. - // this is necessary because some visualizations - // provide data necessary for the legend only after a render cycle. - if ( - visParams.addLegend && - CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) - ) { - this.unmountLegend(); - this.mountLegend(esResponse, visParams.legendPosition); - this.vislibVis.render(esResponse, this.vis.getUiState()); - } - }); - } - - mountLegend(visData, position) { - this.unmount = mountReactNode( - - )(this.legendEl); - } - - unmountLegend() { - if (this.unmount) { - this.unmount(); - } - } - - destroy() { - if (this.unmount) { - this.unmount(); - } - - if (this.vislibVis) { - this.vislibVis.off('brush', this.vis.API.events.brush); - this.vislibVis.off('click', this.vis.API.events.filter); - this.vislibVis.destroy(); - delete this.vislibVis; - } - } -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.d.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.d.ts deleted file mode 100644 index 13c676854ead2..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.d.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; -import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; -import { TimeMarker } from 'ui/vislib/visualizations/time_marker'; -import { CommonVislibParams, ColorSchemaVislibParams, ValueAxis } from './types'; -import { Positions } from './utils/collections'; - -export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibParams { - type: 'heatmap'; - addLegend: boolean; - enableHover: boolean; - colorsNumber: number | ''; - colorsRange: RangeValues[]; - valueAxes: ValueAxis[]; - setColorRange: boolean; - percentageMode: boolean; - times: TimeMarker[]; -} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js deleted file mode 100644 index c82073ff582b8..0000000000000 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; - -import { histogramDefinition } from './histogram'; -import { lineDefinition } from './line'; -import { pieDefinition } from './pie'; -import { areaDefinition } from './area'; -import { heatmapDefinition } from './heatmap'; -import { horizontalBarDefinition } from './horizontal_bar'; -import { gaugeDefinition } from './gauge'; -import { goalDefinition } from './goal'; - -visualizations.types.createBaseVisualization(histogramDefinition); -visualizations.types.createBaseVisualization(lineDefinition); -visualizations.types.createBaseVisualization(pieDefinition); -visualizations.types.createBaseVisualization(areaDefinition); -visualizations.types.createBaseVisualization(heatmapDefinition); -visualizations.types.createBaseVisualization(horizontalBarDefinition); -visualizations.types.createBaseVisualization(gaugeDefinition); -visualizations.types.createBaseVisualization(goalDefinition); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 111b74d8aa6af..e6a0420534ef2 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -61,10 +61,7 @@ export default function(kibana) { uiExports: { hacks: ['plugins/kibana/discover', 'plugins/kibana/dev_tools', 'plugins/kibana/visualize'], - savedObjectTypes: [ - 'plugins/kibana/visualize/saved_visualizations/saved_visualization_register', - 'plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register', - ], + savedObjectTypes: ['plugins/kibana/dashboard/saved_dashboard/saved_dashboard_register'], app: { id: 'kibana', title: 'Kibana', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index ba01919431080..b44d1993db23a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -65,6 +65,6 @@ export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { unhashUrl } from '../../../../../plugins/kibana_utils/public'; export { IInjector } from 'ui/chrome'; export { SavedObjectLoader } from 'ui/saved_objects'; -export { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize_embeddable'; +export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts index 4d5101e1f9e5f..d9a93b2ceedc3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts @@ -28,6 +28,12 @@ import { ViewMode } from 'src/plugins/embeddable/public'; jest.mock('ui/state_management/state', () => ({ State: {}, })); +jest.mock('ui/agg_types', () => ({ + aggTypes: { + metrics: [], + buckets: [], + }, +})); describe('DashboardState', function() { let dashboardState: DashboardStateManager; 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 2e360567c4653..73383f2ff3f68 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 + 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/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 78ac99567d10e..2927565e61dce 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -826,9 +826,14 @@ function discoverController( }; $scope.updateQueryAndFetch = function({ query, dateRange }) { + const oldDateRange = timefilter.getTime(); timefilter.setTime(dateRange); $state.query = query; - $scope.fetch(); + // storing the updated timerange in the state will trigger a fetch + // call automatically, so only trigger fetch in case this is a refresh call (no changes in parameters). + if (_.isEqual(oldDateRange, dateRange)) { + $scope.fetch(); + } }; function onResults(resp) { diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index bd3a0b38ec3f0..b2d90f1444654 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 3ec095f4f26bf..0eb55a3902eda 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 6532737cc02e8..e49f00b949da5 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 a998e4d07ab15..42ab049eb5b3a 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/home/tutorial_resources/ibmmq_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/ibmmq_metrics/screenshot.png new file mode 100644 index 0000000000000..c4f202ad13bb7 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/ibmmq_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg index ad0cb64b161dd..e474d93359beb 100644 --- a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index dfe4aa1fd3b9f..324458c0814d9 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -7,6 +7,9 @@ // Public UI styles @import 'src/legacy/ui/public/index'; +// vis_type_vislib UI styles +@import 'src/legacy/core_plugins/vis_type_vislib/public/index'; + // Dev tools styles @import './dev_tools/index'; @@ -18,7 +21,6 @@ // Visualize styles @import './visualize/index'; -@import './visualize_embeddable/index'; // Has to come after visualize because of some // bad cascading in the Editor layout @import 'src/legacy/ui/public/vis/index'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 334397c479a1d..4100ae7205869 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -52,7 +52,7 @@ import './visualize'; import './dashboard'; import './management'; import './dev_tools'; -import 'ui/vislib'; +import 'ui/color_maps'; import 'ui/agg_response'; import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index a389a44197baf..f113c81256f8e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,9 +17,6 @@ * under the License. */ -import 'ui/collapsible_sidebar'; // used in default editor -import 'ui/vis/editors/default/sidebar'; - import { IPrivate, legacyChrome, diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 141063adcbd6a..7d0a07323378a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -66,12 +66,9 @@ export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -// @ts-ignore -export { defaultEditor } from 'ui/vis/editors/default/default'; export { VisType } from 'ui/vis'; export { wrapInI18nContext } from 'ui/i18n'; export { DashboardConstants } from '../dashboard/np_ready/dashboard_constants'; -export { VisSavedObject } from '../visualize_embeddable/visualize_embeddable'; -export { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize_embeddable'; -export { VisualizeEmbeddableFactory } from '../visualize_embeddable/visualize_embeddable_factory'; +export { VisSavedObject } from '../../../visualizations/public/embeddable/visualize_embeddable'; +export { VISUALIZE_EMBEDDABLE_TYPE } from '../../../visualizations/public/embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss index f738820677beb..2f48ecc322fea 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/_editor.scss @@ -30,9 +30,4 @@ a tilemap in an iframe: https://github.com/elastic/kibana/issues/16457 */ @include flex-parent(); width: 100%; z-index: 0; - - // overrides for tablet and desktop - @include euiBreakpoint('l', 'xl') { - flex-direction: row; - } } 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 64653730473cd..0e085b8553bf0 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 @@ -116,6 +116,11 @@ function VisualizeAppController( dirty: !savedVis.id, }); + vis.on('dirtyStateChange', ({ isDirty }) => { + vis.dirty = isDirty; + $scope.$digest(); + }); + $scope.topNavMenu = [ ...(visualizeCapabilities.save ? [ @@ -357,7 +362,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/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts index f47a54baac9a1..2e0eaeb484c0a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/types.d.ts @@ -17,7 +17,16 @@ * under the License. */ -import { VisSavedObject } from '../legacy_imports'; +import { TimeRange, Query, esFilters } from 'src/plugins/data/public'; +import { VisSavedObject, AppState, PersistedState } from '../legacy_imports'; + +export interface EditorRenderProps { + appState: AppState; + filters: esFilters.Filter[]; + uiState: PersistedState; + timeRange: TimeRange; + query?: Query; +} export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 9ea26f129895c..80e17b1631f3e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -27,6 +27,9 @@ import { SavedObjectsClientContract, } from 'kibana/public'; +// @ts-ignore +import { uiModules } from 'ui/modules'; + import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; @@ -40,14 +43,10 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../../plugins/home/public'; -import { - defaultEditor, - VisEditorTypesRegistryProvider, - VisualizeEmbeddableFactory, - VISUALIZE_EMBEDDABLE_TYPE, -} from './legacy_imports'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { createSavedVisLoader } from './saved_visualizations/saved_visualizations'; +// @ts-ignore +import { savedObjectManagementRegistry } from '../management/saved_object_registry'; export interface LegacyAngularInjectedDependencies { legacyChrome: any; @@ -113,6 +112,7 @@ export class VisualizePlugin implements Plugin { indexPatterns: data.indexPatterns, chrome: contextCore.chrome, overlays: contextCore.overlays, + visualizations, }); const deps: VisualizeKibanaServices = { ...angularDependencies, @@ -154,24 +154,35 @@ export class VisualizePlugin implements Plugin { showOnHomePage: true, category: FeatureCatalogueCategory.DATA, }); - - VisEditorTypesRegistryProvider.register(defaultEditor); } public start( - { savedObjects: { client: savedObjectsClient } }: CoreStart, + core: CoreStart, { embeddables, navigation, data, share, visualizations }: VisualizePluginStartDependencies ) { this.startDependencies = { data, embeddables, navigation, - savedObjectsClient, + savedObjectsClient: core.savedObjects.client, share, visualizations, }; - const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); - embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + const savedVisualizations = createSavedVisLoader({ + savedObjectsClient: core.savedObjects.client, + indexPatterns: data.indexPatterns, + chrome: core.chrome, + overlays: core.overlays, + visualizations, + }); + + // TODO: remove once savedobjectregistry is refactored + savedObjectManagementRegistry.register({ + service: 'savedVisualizations', + title: 'visualizations', + }); + + uiModules.get('app/visualize').service('savedVisualizations', () => savedVisualizations); } } diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts index 9f7ba342d803f..a0a6f8ea1c8a2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.ts @@ -34,7 +34,7 @@ import { IIndexPattern } from '../../../../../../plugins/data/public'; import { VisSavedObject } from '../legacy_imports'; import { createSavedSearchesService } from '../../discover'; -import { VisualizeConstants } from '..'; +import { VisualizeConstants } from '../np_ready/visualize_constants'; async function _afterEsResp(savedVis: VisSavedObject, services: any) { await _getLinkedSavedSearch(savedVis, services); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts index b71a10ab000d8..98f5458d5eecc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.test.ts @@ -18,7 +18,7 @@ */ import { extractReferences, injectReferences } from './saved_visualization_references'; -import { VisSavedObject } from '../../visualize_embeddable/visualize_embeddable'; +import { VisSavedObject } from '../../../../visualizations/public/embeddable/visualize_embeddable'; describe('extractReferences', () => { test('extracts nothing if savedSearchId is empty', () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts index 0c76aaff4345d..403e9c5a8172d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_references.ts @@ -17,7 +17,7 @@ * under the License. */ import { SavedObjectAttributes, SavedObjectReference } from 'kibana/server'; -import { VisSavedObject } from '../../visualize_embeddable/visualize_embeddable'; +import { VisSavedObject } from '../../../../visualizations/public/embeddable/visualize_embeddable'; export function extractReferences({ attributes, diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts deleted file mode 100644 index cbf72339804ce..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualization_register.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { npStart } from 'ui/new_platform'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -// @ts-ignore -import { savedObjectManagementRegistry } from '../../management/saved_object_registry'; -import './saved_visualizations'; -import { createSavedVisLoader } from './saved_visualizations'; - -const services = { - savedObjectsClient: npStart.core.savedObjects.client, - indexPatterns: npStart.plugins.data.indexPatterns, - chrome: npStart.core.chrome, - overlays: npStart.core.overlays, -}; - -const savedObjectLoaderVisualize = createSavedVisLoader(services); - -// Register this service with the saved object registry so it can be -// edited by the object editor. -savedObjectManagementRegistry.register({ - service: 'savedVisualizations', - title: 'visualizations', -}); - -uiModules.get('app/visualize').service('savedVisualizations', () => savedObjectLoaderVisualize); diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts index c19c7818c1fbd..d51fae7428939 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/saved_visualizations.ts @@ -19,14 +19,18 @@ import { SavedObjectLoader } from 'ui/saved_objects'; import { SavedObjectKibanaServices } from 'ui/saved_objects/types'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; // @ts-ignore import { findListItems } from './find_list_items'; import { createSavedVisClass } from './_saved_vis'; -import { createVisualizeEditUrl } from '..'; +import { createVisualizeEditUrl } from '../np_ready/visualize_constants'; +import { VisualizationsStart } from '../../../../visualizations/public/np_ready/public'; -export function createSavedVisLoader(services: SavedObjectKibanaServices) { - const { savedObjectsClient } = services; +interface SavedObjectKibanaServicesWithVisualizations extends SavedObjectKibanaServices { + visualizations: VisualizationsStart; +} + +export function createSavedVisLoader(services: SavedObjectKibanaServicesWithVisualizations) { + const { savedObjectsClient, visualizations } = services; class SavedObjectLoaderVisualize extends SavedObjectLoader { mapHitSource = (source: Record, id: string) => { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js new file mode 100644 index 0000000000000..b2824832dc14c --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/ibmmq_metrics/index.js @@ -0,0 +1,67 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../../../common/tutorials/metricbeat_instructions'; + +export function ibmmqMetricsSpecProvider(context) { + const moduleName = 'ibmmq'; + return { + id: 'ibmmqMetrics', + name: i18n.translate('kbn.server.tutorials.ibmmqMetrics.nameTitle', { + defaultMessage: 'IBM MQ metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.ibmmqMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from IBM MQ instances.', + }), + longDescription: i18n.translate('kbn.server.tutorials.ibmmqMetrics.longDescription', { + defaultMessage: + 'The `ibmmq` Metricbeat module fetches monitoring metrics from IBM MQ instances \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-ibmmq.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/ibmmq.svg', + isBeta: true, + artifacts: { + application: { + label: i18n.translate('kbn.server.tutorials.ibmmqMetrics.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/kibana#/discover', + }, + dashboards: [], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-ibmmq.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/ibmmq_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName), + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index 69a6ac76e4a8f..2f69e7dbcbc7d 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -84,6 +84,7 @@ import { activemqLogsSpecProvider } from './activemq_logs'; import { activemqMetricsSpecProvider } from './activemq_metrics'; import { azureMetricsSpecProvider } from './azure_metrics'; import { ibmmqLogsSpecProvider } from './ibmmq_logs'; +import { ibmmqMetricsSpecProvider } from './ibmmq_metrics'; import { stanMetricsSpecProvider } from './stan_metrics'; import { envoyproxyMetricsSpecProvider } from './envoyproxy_metrics'; @@ -158,6 +159,7 @@ export function registerTutorials(server) { server.newPlatform.setup.plugins.home.tutorials.registerTutorial(activemqMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(azureMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ibmmqLogsSpecProvider); + server.newPlatform.setup.plugins.home.tutorials.registerTutorial(ibmmqMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(stanMetricsSpecProvider); server.newPlatform.setup.plugins.home.tutorials.registerTutorial(envoyproxyMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/region_map/public/choropleth_layer.js b/src/legacy/core_plugins/region_map/public/choropleth_layer.js index f4c6bfd6bedf5..8132976fcbc69 100644 --- a/src/legacy/core_plugins/region_map/public/choropleth_layer.js +++ b/src/legacy/core_plugins/region_map/public/choropleth_layer.js @@ -23,7 +23,7 @@ import _ from 'lodash'; import d3 from 'd3'; import { i18n } from '@kbn/i18n'; import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; -import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorMaps } from 'ui/color_maps'; import * as topojson from 'topojson-client'; import { toastNotifications } from 'ui/notify'; import * as colorUtil from 'ui/vis/map/color_util'; diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 8306b3274a914..73fe07ec60102 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -28,7 +28,7 @@ import { NumberInputOption, SelectOption, SwitchOption, -} from '../../../kbn_vislib_vis_types/public/components'; +} from '../../../vis_type_vislib/public/components'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; import { RegionMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/region_map/public/region_map_type.js b/src/legacy/core_plugins/region_map/public/region_map_type.js index 6f83ae912e184..39353a379ce52 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_type.js +++ b/src/legacy/core_plugins/region_map/public/region_map_type.js @@ -19,7 +19,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorSchemas as colorSchemas } from 'ui/color_maps'; import { mapToLayerWithId } from './util'; import { createRegionMapVisualization } from './region_map_visualization'; import { Status } from '../../visualizations/public'; diff --git a/src/legacy/core_plugins/region_map/public/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/region_map_visualization.js index b58de5d9c6ab7..f9a5793ca8137 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/region_map_visualization.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import ChoroplethLayer from './choropleth_layer'; -import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorMaps } from 'ui/color_maps'; import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { toastNotifications } from 'ui/notify'; 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 2f8a2264530d5..94263e7b76a97 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -114,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/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index 15c92f4617497..e57cea8467d12 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -27,7 +27,7 @@ import { RangeOption, SelectOption, SwitchOption, -} from '../../../kbn_vislib_vis_types/public/components'; +} from '../../../vis_type_vislib/public/components'; import { WmsOptions } from './wms_options'; import { TileMapVisParams } from '../types'; import { MapTypes } from '../map_types'; diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx index f2cf69bb63ba4..2989f6ce7ebd5 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_internal_options.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiLink, EuiSpacer, EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { TextInputOption } from '../../../kbn_vislib_vis_types/public/components'; +import { TextInputOption } from '../../../vis_type_vislib/public/components'; import { WMSOptions } from '../types'; interface WmsInternalOptions { diff --git a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx index c5ccc3acba610..d9dca5afd7377 100644 --- a/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/wms_options.tsx @@ -25,7 +25,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { TmsLayer } from 'ui/vis/map/service_settings'; import { Vis } from 'ui/vis'; import { RegionMapVisParams } from '../../../region_map/public/types'; -import { SelectOption, SwitchOption } from '../../../kbn_vislib_vis_types/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; import { WmsInternalOptions } from './wms_internal_options'; import { WMSOptions, TileMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js b/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js index e9334ff91def9..fe29d9b6aad21 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js +++ b/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js @@ -22,7 +22,7 @@ import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import { EventEmitter } from 'events'; -import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorMaps } from 'ui/color_maps'; import * as colorUtil from 'ui/vis/map/color_util'; export class ScaledCirclesMarkers extends EventEmitter { diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index f2e6469e768e7..c58c226f0aba0 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -21,7 +21,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; +import { truncatedColorSchemas as colorSchemas } from 'ui/color_maps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; import { createTileMapVisualization } from './tile_map_visualization'; diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index 7ef722ee3a277..084e497761e43 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -59,8 +59,6 @@ document.title = 'Timelion - Kibana'; const app = require('ui/modules').get('apps/timelion', []); -require('./vis'); - require('ui/routes').enable(); require('ui/routes').when('/:id?', { diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index 231330b898edb..ea2d44bcaefe0 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -19,10 +19,13 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../../chain.peg'; +import grammar from 'raw-loader!../../../../vis_type_timelion/public/chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; -import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services'; +import { getArgValueSuggestions } from '../../../../vis_type_timelion/public/helpers/arg_value_suggestions'; +import { + setIndexPatterns, + setSavedObjectsClient, +} from '../../../../vis_type_timelion/public/helpers/plugin_services'; describe('Timelion expression suggestions', () => { setIndexPatterns({}); diff --git a/src/legacy/core_plugins/timelion/public/directives/_index.scss b/src/legacy/core_plugins/timelion/public/directives/_index.scss index 6ee2f81539032..cd46a1a0a369e 100644 --- a/src/legacy/core_plugins/timelion/public/directives/_index.scss +++ b/src/legacy/core_plugins/timelion/public/directives/_index.scss @@ -1,6 +1,5 @@ @import './timelion_expression_input'; @import './cells/index'; -@import './chart/index'; @import './timelion_expression_suggestions/index'; @import './timelion_help/index'; @import './timelion_interval/index'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss b/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss deleted file mode 100644 index 33c188decd4f1..0000000000000 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './chart'; diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 449c0489fea25..1fec243a277f8 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -43,7 +43,7 @@ import _ from 'lodash'; import $ from 'jquery'; import PEG from 'pegjs'; -import grammar from 'raw-loader!../chain.peg'; +import grammar from 'raw-loader!../../../vis_type_timelion/public/chain.peg'; import timelionExpressionInputTemplate from './timelion_expression_input.html'; import { SUGGESTION_TYPE, @@ -52,7 +52,7 @@ import { insertAtLocation, } from './timelion_expression_input_helpers'; import { comboBoxKeyCodes } from '@elastic/eui'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../../../vis_type_timelion/public/helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index 7ccc6c300bc40..ebf000d160b54 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -11,6 +11,4 @@ // timChart__legend-isLoading @import './app'; -@import './components/index'; @import './directives/index'; -@import './vis/index'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index 1cf6bb65cdc02..63030fcbce387 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -20,15 +20,10 @@ import { PluginInitializerContext } from 'kibana/public'; import { npSetup, npStart } from 'ui/new_platform'; import { plugin } from '.'; -import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; import { TimelionPluginSetupDependencies } from './plugin'; import { LegacyDependenciesPlugin } from './shim'; const setupPlugins: Readonly = { - visualizations, - data: npSetup.plugins.data, - expressions: npSetup.plugins.expressions, - // Temporary solution // It will be removed when all dependent services are migrated to the new platform. __LEGACY: new LegacyDependenciesPlugin(), @@ -37,4 +32,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); +export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 0bbda4bf3646f..57ee99f5268b0 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -17,19 +17,18 @@ * under the License. */ -import './flot'; +import '../../../../vis_type_timelion/public/flot'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment-timezone'; import { timefilter } from 'ui/timefilter'; // @ts-ignore import observeResize from '../../lib/observe_resize'; -// @ts-ignore -import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../common/lib'; +import { calculateInterval, DEFAULT_TIME_FORMAT } from '../../../../vis_type_timelion/common/lib'; +import { tickFormatters } from '../../../../vis_type_timelion/public/helpers/tick_formatters'; import { TimelionVisualizationDependencies } from '../../plugin'; -import { tickFormatters } from '../../services/tick_formatters'; -import { xaxisFormatterProvider } from './xaxis_formatter'; -import { generateTicksProvider } from './tick_generator'; +import { xaxisFormatterProvider } from '../../../../vis_type_timelion/public/helpers/xaxis_formatter'; +import { generateTicksProvider } from '../../../../vis_type_timelion/public/helpers/tick_generator'; const DEBOUNCE_DELAY = 50; diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 42f0ee3ad4725..636b8bf8e128a 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -22,33 +22,19 @@ import { Plugin, PluginInitializerContext, IUiSettingsClient, - HttpSetup, } from 'kibana/public'; -import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; -import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; -import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; -import { getTimelionVisualizationConfig } from './timelion_vis_fn'; -import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; -import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { uiSettings: IUiSettingsClient; - http: HttpSetup; timelionPanels: Map; - timefilter: TimefilterContract; } /** @internal */ export interface TimelionPluginSetupDependencies { - expressions: ReturnType; - visualizations: VisualizationsSetup; - data: DataPublicPluginSetup; - // Temporary solution __LEGACY: LegacyDependenciesPlugin; } @@ -61,24 +47,16 @@ export class TimelionPlugin implements Plugin, void> { this.initializerContext = initializerContext; } - public async setup( - core: CoreSetup, - { __LEGACY, expressions, visualizations, data }: TimelionPluginSetupDependencies - ) { + public async setup(core: CoreSetup, { __LEGACY }: TimelionPluginSetupDependencies) { const timelionPanels: Map = new Map(); const dependencies: TimelionVisualizationDependencies = { uiSettings: core.uiSettings, - http: core.http, timelionPanels, - timefilter: data.query.timefilter.timefilter, ...(await __LEGACY.setup(core, timelionPanels)), }; this.registerPanels(dependencies); - - expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { @@ -87,15 +65,12 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start(core: CoreStart, plugins: PluginsStart) { + public start(core: CoreStart) { const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); if (timelionUiEnabled === false) { core.chrome.navLinks.update('timelion', { hidden: true }); } - - setIndexPatterns(plugins.data.indexPatterns); - setSavedObjectsClient(core.savedObjects.client); } public stop(): void {} diff --git a/src/legacy/core_plugins/timelion/public/vis/_index.scss b/src/legacy/core_plugins/timelion/public/vis/_index.scss deleted file mode 100644 index 17a2018f7a56a..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import './timelion_vis'; -@import './timelion_editor'; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html deleted file mode 100644 index 8bfb9e91a2523..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis.html +++ /dev/null @@ -1,3 +0,0 @@ -
-
-
diff --git a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js index 9514e479d36f4..9056362cb723a 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js +++ b/src/legacy/core_plugins/timelion/server/handlers/chain_runner.js @@ -26,7 +26,7 @@ import parseSheet from './lib/parse_sheet.js'; import repositionArguments from './lib/reposition_arguments.js'; import indexArguments from './lib/index_arguments.js'; import validateTime from './lib/validate_time.js'; -import { calculateInterval } from '../../common/lib'; +import { calculateInterval } from '../../../vis_type_timelion/common/lib'; export default function chainRunner(tlConfig) { const preprocessChain = require('./lib/preprocess_chain')(tlConfig); diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js index 74ef76d1a50cd..4957d3cb78b85 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/parse_sheet.js @@ -21,7 +21,10 @@ import { i18n } from '@kbn/i18n'; import fs from 'fs'; import path from 'path'; import _ from 'lodash'; -const grammar = fs.readFileSync(path.resolve(__dirname, '../../../public/chain.peg'), 'utf8'); +const grammar = fs.readFileSync( + path.resolve(__dirname, '../../../../vis_type_timelion/public/chain.peg'), + 'utf8' +); import PEG from 'pegjs'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js index 8b1f8998557be..db924e33be5e9 100644 --- a/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js +++ b/src/legacy/core_plugins/timelion/server/handlers/lib/validate_time.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment'; -import toMS from '../../lib/to_milliseconds.js'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default function validateTime(time, tlConfig) { const span = moment.duration(moment(time.to).diff(moment(time.from))).asMilliseconds(); diff --git a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 798902aa133de..08358b9d81f78 100644 --- a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts +++ b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { TimelionFunctionArgs } from '../../../common/types'; +import { TimelionFunctionArgs } from '../../../../vis_type_timelion/common/types'; export interface TimelionFunctionInterface extends TimelionFunctionConfig { chainable: boolean; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js index 970d146c45b91..0cc41df933e8c 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/holt/index.js @@ -23,7 +23,7 @@ import Chainable from '../../lib/classes/chainable'; import ses from './lib/ses'; import des from './lib/des'; import tes from './lib/tes'; -import toMilliseconds from '../../lib/to_milliseconds'; +import { toMS } from '../../../../vis_type_timelion/common/lib'; export default new Chainable('holt', { args: [ @@ -125,9 +125,7 @@ export default new Chainable('holt', { }) ); } - const season = Math.round( - toMilliseconds(args.byName.season) / toMilliseconds(tlConfig.time.interval) - ); + const season = Math.round(toMS(args.byName.season) / toMS(tlConfig.time.interval)); points = tes(points, alpha, beta, gamma, season, sample); } diff --git a/src/legacy/core_plugins/timelion/server/series_functions/legend.js b/src/legacy/core_plugins/timelion/server/series_functions/legend.js index b467318686729..fd9ff53a1391f 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/legend.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/legend.js @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import Chainable from '../lib/classes/chainable'; -import { DEFAULT_TIME_FORMAT } from '../../common/lib'; +import { DEFAULT_TIME_FORMAT } from '../../../vis_type_timelion/common/lib'; export default new Chainable('legend', { args: [ diff --git a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js index 361cd1f9dfb67..a4b458991c1bc 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/movingaverage.js @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; const validPositions = ['left', 'right', 'center']; const defaultPosition = 'center'; diff --git a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js index 778c91d30f2cb..b604015624dfd 100644 --- a/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js +++ b/src/legacy/core_plugins/timelion/server/series_functions/scale_interval.js @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import alter from '../lib/alter.js'; -import toMS from '../lib/to_milliseconds.js'; +import { toMS } from '../../../vis_type_timelion/common/lib'; import _ from 'lodash'; import Chainable from '../lib/classes/chainable'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss b/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss index da38d6d2ed211..fb0a3d05e5e85 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss +++ b/src/legacy/core_plugins/vis_type_markdown/public/_markdown_vis.scss @@ -4,13 +4,8 @@ } .visEditor--markdown { - vis-editor-vis-options, vis-options-react-wrapper { - flex-grow: 1; - display: flex; - flex-direction: column; - } - - .visEditor--markdown__textarea { + .visEditorSidebar__config > *, + .visEditor--markdown__textarea { flex-grow: 1; } diff --git a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx index cce92a08f2c2b..125577815c207 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx @@ -22,7 +22,7 @@ import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { RangeOption, SwitchOption } from '../../kbn_vislib_vis_types/public/components'; +import { RangeOption, SwitchOption } from '../../vis_type_vislib/public/components'; import { MarkdownVisParams } from './types'; function SettingsOptions({ stateParams, setValue }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap b/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap index d0fba4d164dbf..44414cedf966a 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap +++ b/src/legacy/core_plugins/vis_type_metric/public/__snapshots__/metric_vis_fn.test.ts.snap @@ -13,7 +13,7 @@ Object { "metrics": undefined, }, "metric": Object { - "colorSchema": "\\"Green to Red\\"", + "colorSchema": "Green to Red", "colorsRange": "{range from=0 to=10000}", "invertColors": false, "labels": Object { diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js deleted file mode 100644 index f4786123f7b9d..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; - -import { Vis } from 'ui/vis'; -import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; - -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; - -describe('metric_vis - createMetricVisTypeDefinition', () => { - let setup = null; - let vis; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(Private => { - setup = () => { - const metricVisType = visualizations.types.get('metric'); - const indexPattern = Private(LogstashIndexPatternStubProvider); - - indexPattern.stubSetFieldFormat('ip', 'url', { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }); - - vis = new Vis(indexPattern, { - type: 'metric', - aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }], - }); - - vis.params.dimensions = { - metrics: [ - { - accessor: 0, - format: { - id: 'url', - params: { - urlTemplate: 'http://ip.info?address={{value}}', - labelTemplate: 'ip[{{value}}]', - }, - }, - }, - ], - }; - - const el = document.createElement('div'); - const Controller = metricVisType.visualization; - const controller = new Controller(el, vis); - const render = esResponse => { - controller.render(esResponse, vis.params); - }; - - return { el, render }; - }; - }) - ); - - it('renders html value from field formatter', () => { - const { el, render } = setup(); - - const ip = '235.195.237.208'; - render({ - columns: [{ id: 'col-0', name: 'ip' }], - rows: [{ 'col-0': ip }], - }); - - const $link = $(el) - .find('a[href]') - .filter(function() { - return this.href.includes('ip.info'); - }); - - expect($link).to.have.length(1); - expect($link.text()).to.be(`ip[${ip}]`); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js deleted file mode 100644 index 367c5f4699937..0000000000000 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis_controller.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { MetricVisComponent } from '../components/metric_vis_controller'; - -describe('metric_vis - controller', function() { - const vis = { - params: { - metric: { - colorSchema: 'Green to Red', - colorsRange: [{ from: 0, to: 1000 }], - style: {}, - }, - dimensions: { - metrics: [{ accessor: 0 }], - bucket: null, - }, - }, - }; - - let metricController; - - beforeEach(() => { - metricController = new MetricVisComponent({ vis: vis, visParams: vis.params }); - }); - - it('should set the metric label and value', function() { - const metrics = metricController._processTableGroups({ - columns: [{ id: 'col-0', name: 'Count' }], - rows: [{ 'col-0': 4301021 }], - }); - - expect(metrics.length).to.be(1); - expect(metrics[0].label).to.be('Count'); - expect(metrics[0].value).to.be('4301021'); - }); - - it('should support multi-value metrics', function() { - vis.params.dimensions.metrics.push({ accessor: 1 }); - const metrics = metricController._processTableGroups({ - columns: [ - { id: 'col-0', name: '1st percentile of bytes' }, - { id: 'col-1', name: '99th percentile of bytes' }, - ], - rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], - }); - - expect(metrics.length).to.be(2); - expect(metrics[0].label).to.be('1st percentile of bytes'); - expect(metrics[0].value).to.be('182'); - expect(metrics[1].label).to.be('99th percentile of bytes'); - expect(metrics[1].value).to.be('445842.4634666484'); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap b/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap new file mode 100644 index 0000000000000..d84424cc6179a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/__snapshots__/metric_vis_component.test.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MetricVisComponent should render correct structure for single metric 1`] = ` +
+ 4301021", + } + } + showLabel={true} + /> +
+`; + +exports[`MetricVisComponent should render correct structure for multi-value metrics 1`] = ` +
+ 182", + } + } + showLabel={true} + /> + 445842.4634666484", + } + } + showLabel={true} + /> +
+`; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx new file mode 100644 index 0000000000000..901273ccbeb95 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.test.tsx @@ -0,0 +1,93 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { MetricVisComponent } from './metric_vis_component'; +import { Vis } from '../legacy_imports'; + +jest.mock('ui/new_platform'); + +type Props = MetricVisComponent['props']; + +const baseVisData = { + columns: [{ id: 'col-0', name: 'Count' }], + rows: [{ 'col-0': 4301021 }], +} as any; + +describe('MetricVisComponent', function() { + const vis: Vis = { + params: { + metric: { + colorSchema: 'Green to Red', + colorsRange: [{ from: 0, to: 1000 }], + style: {}, + labels: { + show: true, + }, + }, + dimensions: { + metrics: [{ accessor: 0 }], + bucket: null, + }, + }, + } as any; + + const getComponent = (propOverrides: Partial = {} as Partial) => { + const props: Props = { + vis, + visParams: vis.params, + visData: baseVisData, + renderComplete: jest.fn(), + ...propOverrides, + }; + + return shallow(); + }; + + it('should render component', () => { + expect(getComponent().exists()).toBe(true); + }); + + it('should render correct structure for single metric', function() { + expect(getComponent()).toMatchSnapshot(); + }); + + it('should render correct structure for multi-value metrics', function() { + const component = getComponent({ + visData: { + columns: [ + { id: 'col-0', name: '1st percentile of bytes' }, + { id: 'col-1', name: '99th percentile of bytes' }, + ], + rows: [{ 'col-0': 182, 'col-1': 445842.4634666484 }], + }, + visParams: { + ...vis.params, + dimensions: { + ...vis.params.dimensions, + metrics: [{ accessor: 0 }, { accessor: 1 }], + }, + }, + } as any); + + expect(component).toMatchSnapshot(); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx similarity index 64% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx index ecaf6b5d70d36..f8398f5c83146 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_component.tsx @@ -19,20 +19,33 @@ import { last, findIndex, isNaN } from 'lodash'; import React, { Component } from 'react'; + import { isColorDark } from '@elastic/eui'; -import { getHeatmapColors } from 'ui/vislib/components/color/heatmap_color'; -import { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +import { getHeatmapColors, getFormat, Vis } from '../legacy_imports'; import { MetricVisValue } from './metric_vis_value'; +import { FieldFormat, ContentType } from '../../../../../plugins/data/public'; +import { Context } from '../metric_vis_fn'; +import { KibanaDatatable } from '../../../../../plugins/expressions/public'; +import { VisParams, MetricVisMetric } from '../types'; +import { SchemaConfig } from '../../../visualizations/public'; + +interface MetricVisComponentProps { + visParams: VisParams; + visData: Context; + vis: Vis; + renderComplete: () => void; +} -export class MetricVisComponent extends Component { - _getLabels() { +export class MetricVisComponent extends Component { + private getLabels() { const config = this.props.visParams.metric; const isPercentageMode = config.percentageMode; const colorsRange = config.colorsRange; const max = last(colorsRange).to; - const labels = []; - colorsRange.forEach(range => { + const labels: string[] = []; + + colorsRange.forEach((range: any) => { const from = isPercentageMode ? Math.round((100 * range.from) / max) : range.from; const to = isPercentageMode ? Math.round((100 * range.to) / max) : range.to; labels.push(`${from} - ${to}`); @@ -41,13 +54,13 @@ export class MetricVisComponent extends Component { return labels; } - _getColors() { + private getColors() { const config = this.props.visParams.metric; const invertColors = config.invertColors; const colorSchema = config.colorSchema; const colorsRange = config.colorsRange; - const labels = this._getLabels(); - const colors = {}; + const labels = this.getLabels(); + const colors: any = {}; for (let i = 0; i < labels.length; i += 1) { const divider = Math.max(colorsRange.length - 1, 1); const val = invertColors ? 1 - i / divider : i / divider; @@ -56,9 +69,9 @@ export class MetricVisComponent extends Component { return colors; } - _getBucket(val) { + private getBucket(val: number) { const config = this.props.visParams.metric; - let bucket = findIndex(config.colorsRange, range => { + let bucket = findIndex(config.colorsRange, (range: any) => { return range.from <= val && range.to > val; }); @@ -70,59 +83,65 @@ export class MetricVisComponent extends Component { return bucket; } - _getColor(val, labels, colors) { - const bucket = this._getBucket(val); + private getColor(val: number, labels: string[], colors: { [label: string]: string }) { + const bucket = this.getBucket(val); const label = labels[bucket]; return colors[label]; } - _needsLightText(bgColor) { - const color = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); - if (!color) { + private needsLightText(bgColor: string) { + const colors = /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.exec(bgColor); + if (!colors) { return false; } - return isColorDark(parseInt(color[1]), parseInt(color[2]), parseInt(color[3])); + + const [red, green, blue] = colors.slice(1).map(parseInt); + return isColorDark(red, green, blue); } - _getFormattedValue = (fieldFormatter, value, format = 'text') => { + private getFormattedValue = ( + fieldFormatter: FieldFormat, + value: any, + format: ContentType = 'text' + ) => { if (isNaN(value)) return '-'; return fieldFormatter.convert(value, format); }; - _processTableGroups(table) { + private processTableGroups(table: KibanaDatatable) { const config = this.props.visParams.metric; const dimensions = this.props.visParams.dimensions; const isPercentageMode = config.percentageMode; const min = config.colorsRange[0].from; const max = last(config.colorsRange).to; - const colors = this._getColors(); - const labels = this._getLabels(); - const metrics = []; + const colors = this.getColors(); + const labels = this.getLabels(); + const metrics: MetricVisMetric[] = []; - let bucketColumnId; - let bucketFormatter; + let bucketColumnId: string; + let bucketFormatter: FieldFormat; if (dimensions.bucket) { bucketColumnId = table.columns[dimensions.bucket.accessor].id; bucketFormatter = getFormat(dimensions.bucket.format); } - dimensions.metrics.forEach(metric => { + dimensions.metrics.forEach((metric: SchemaConfig) => { const columnIndex = metric.accessor; - const column = table.columns[columnIndex]; + const column = table?.columns[columnIndex]; const formatter = getFormat(metric.format); table.rows.forEach((row, rowIndex) => { let title = column.name; - let value = row[column.id]; - const color = this._getColor(value, labels, colors); + let value: any = row[column.id]; + const color = this.getColor(value, labels, colors); if (isPercentageMode) { value = (value - min) / (max - min); } - value = this._getFormattedValue(formatter, value, 'html'); + value = this.getFormattedValue(formatter, value, 'html'); if (bucketColumnId) { - const bucketValue = this._getFormattedValue(bucketFormatter, row[bucketColumnId]); + const bucketValue = this.getFormattedValue(bucketFormatter, row[bucketColumnId]); title = `${bucketValue} - ${title}`; } @@ -130,11 +149,11 @@ export class MetricVisComponent extends Component { metrics.push({ label: title, - value: value, - color: shouldColor && config.style.labelColor ? color : null, - bgColor: shouldColor && config.style.bgColor ? color : null, - lightText: shouldColor && config.style.bgColor && this._needsLightText(color), - rowIndex: rowIndex, + value, + color: shouldColor && config.style.labelColor ? color : undefined, + bgColor: shouldColor && config.style.bgColor ? color : undefined, + lightText: shouldColor && config.style.bgColor && this.needsLightText(color), + rowIndex, }); }); }); @@ -142,7 +161,7 @@ export class MetricVisComponent extends Component { return metrics; } - _filterBucket = metric => { + private filterBucket = (metric: MetricVisMetric) => { const dimensions = this.props.visParams.dimensions; if (!dimensions.bucket) { return; @@ -155,27 +174,18 @@ export class MetricVisComponent extends Component { }); }; - _renderMetric = (metric, index) => { + private renderMetric = (metric: MetricVisMetric, index: number) => { return ( ); }; - render() { - let metricsHtml; - if (this.props.visData) { - const metrics = this._processTableGroups(this.props.visData); - metricsHtml = metrics.map(this._renderMetric); - } - return
{metricsHtml}
; - } - componentDidMount() { this.props.renderComplete(); } @@ -183,4 +193,13 @@ export class MetricVisComponent extends Component { componentDidUpdate() { this.props.renderComplete(); } + + render() { + let metricsHtml; + if (this.props.visData) { + const metrics = this.processTableGroups(this.props.visData); + metricsHtml = metrics.map(this.renderMetric); + } + return
{metricsHtml}
; + } } diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 9649588976c0d..032f66d92624c 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -29,16 +29,17 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../legacy_imports'; import { ColorRanges, ColorSchemaOptions, SwitchOption, RangeOption, SetColorSchemaOptionsValue, -} from '../../../kbn_vislib_vis_types/public/components'; -import { ColorModes } from '../../../kbn_vislib_vis_types/public/utils/collections'; +} from '../../../vis_type_vislib/public/components'; +import { ColorModes } from '../../../vis_type_vislib/public/utils/collections'; import { MetricVisParam, VisParams } from '../types'; +import { SetColorRangeValue } from '../../../vis_type_vislib/public/components/common/color_ranges'; function MetricVisOptions({ stateParams, @@ -135,7 +136,7 @@ function MetricVisOptions({ diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx similarity index 76% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx index 2fc436cb829bd..2ee80e92f129b 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.test.tsx @@ -21,34 +21,32 @@ import { shallow } from 'enzyme'; import { MetricVisValue } from './metric_vis_value'; +const baseMetric = { label: 'Foo', value: 'foo' } as any; + describe('MetricVisValue', () => { it('should be wrapped in EuiKeyboardAccessible if having a click listener', () => { const component = shallow( - {}} /> + {}} /> ); expect(component.find('EuiKeyboardAccessible').exists()).toBe(true); }); it('should not be wrapped in EuiKeyboardAccessible without having a click listener', () => { - const component = shallow( - - ); + const component = shallow(); expect(component.find('EuiKeyboardAccessible').exists()).toBe(false); }); it('should add -isfilterable class if onFilter is provided', () => { const onFilter = jest.fn(); const component = shallow( - + ); component.simulate('click'); expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(1); }); it('should not add -isfilterable class if onFilter is not provided', () => { - const component = shallow( - - ); + const component = shallow(); component.simulate('click'); expect(component.find('.mtrVis__container-isfilterable')).toHaveLength(0); }); @@ -56,9 +54,9 @@ describe('MetricVisValue', () => { it('should call onFilter callback if provided', () => { const onFilter = jest.fn(); const component = shallow( - + ); component.find('.mtrVis__container-isfilterable').simulate('click'); - expect(onFilter).toHaveBeenCalledWith({ label: 'Foo', value: 'foo' }); + expect(onFilter).toHaveBeenCalledWith(baseMetric); }); }); diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx similarity index 72% rename from src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js rename to src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx index 0776dd13a9868..79876377c8e44 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.tsx @@ -17,26 +17,36 @@ * under the License. */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, KeyboardEvent } from 'react'; import classNames from 'classnames'; import { EuiKeyboardAccessible, keyCodes } from '@elastic/eui'; -class MetricVisValue extends Component { +import { MetricVisMetric } from '../types'; + +interface MetricVisValueProps { + metric: MetricVisMetric; + fontSize: number; + onFilter?: (metric: MetricVisMetric) => void; + showLabel?: boolean; +} + +export class MetricVisValue extends Component { onClick = () => { - this.props.onFilter(this.props.metric); + if (this.props.onFilter) { + this.props.onFilter(this.props.metric); + } }; - onKeyPress = e => { - if (e.keyCode === keyCodes.ENTER) { + onKeyPress = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ENTER) { this.onClick(); } }; render() { const { fontSize, metric, onFilter, showLabel } = this.props; - const hasFilter = !!onFilter; + const hasFilter = Boolean(onFilter); const metricValueStyle = { fontSize: `${fontSize}pt`, @@ -52,10 +62,10 @@ class MetricVisValue extends Component {
{showLabel &&
{metric.label}
}
@@ -81,12 +91,3 @@ class MetricVisValue extends Component { return metricComponent; } } - -MetricVisValue.propTypes = { - fontSize: PropTypes.number.isRequired, - metric: PropTypes.object.isRequired, - onFilter: PropTypes.func, - showLabel: PropTypes.bool, -}; - -export { MetricVisValue }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts new file mode 100644 index 0000000000000..93dfd76e16b16 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/legacy_imports.ts @@ -0,0 +1,27 @@ +/* + * 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 { Vis, VisParams } from 'ui/vis'; +export { vislibColorMaps, colorSchemas, ColorSchemas } from 'ui/color_maps'; +export { getHeatmapColors } from 'ui/color_maps'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +// @ts-ignore +export { Schemas } from 'ui/vis/editors/default/schemas'; +export { AggGroupNames } from 'ui/vis/editors/default'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index b64361f17c470..45110ca113003 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { vislibColorMaps, ColorSchemas } from 'ui/vislib/components/color/colormaps'; +import { vislibColorMaps, ColorSchemas } from './legacy_imports'; import { ExpressionFunction, KibanaDatatable, @@ -27,16 +27,16 @@ import { Render, Style, } from '../../../../plugins/expressions/public'; -import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; +import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; import { visType, DimensionsVisParam, VisParams } from './types'; -type Context = KibanaDatatable; +export type Context = KibanaDatatable; const name = 'metricVis'; interface Arguments { - percentage: boolean; - colorScheme: ColorSchemas; + percentageMode: boolean; + colorSchema: ColorSchemas; colorMode: ColorModes; useRanges: boolean; invertColors: boolean; @@ -73,19 +73,19 @@ export const createMetricVisFn = (): ExpressionFunction< defaultMessage: 'Metric visualization', }), args: { - percentage: { + percentageMode: { types: ['boolean'], default: false, - help: i18n.translate('visTypeMetric.function.percentage.help', { + help: i18n.translate('visTypeMetric.function.percentageMode.help', { defaultMessage: 'Shows metric in percentage mode. Requires colorRange to be set.', }), }, - colorScheme: { + colorSchema: { types: ['string'], default: '"Green to Red"', options: Object.values(vislibColorMaps).map((value: any) => value.id), - help: i18n.translate('visTypeMetric.function.colorScheme.help', { - defaultMessage: 'Color scheme to use', + help: i18n.translate('visTypeMetric.function.colorSchema.help', { + defaultMessage: 'Color schema to use', }), }, colorMode: { @@ -174,8 +174,8 @@ export const createMetricVisFn = (): ExpressionFunction< dimensions.bucket = args.bucket; } - if (args.percentage && (!args.colorRange || args.colorRange.length === 0)) { - throw new Error('colorRange must be provided when using percentage'); + if (args.percentageMode && (!args.colorRange || args.colorRange.length === 0)) { + throw new Error('colorRange must be provided when using percentageMode'); } const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10); @@ -188,9 +188,9 @@ export const createMetricVisFn = (): ExpressionFunction< visType, visConfig: { metric: { - percentageMode: args.percentage, + percentageMode: args.percentageMode, useRanges: args.useRanges, - colorSchema: args.colorScheme, + colorSchema: args.colorSchema, metricColorMode: args.colorMode, colorsRange: args.colorRange, labels: { diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts new file mode 100644 index 0000000000000..649959054416c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import $ from 'jquery'; + +import { npStart } from 'ui/new_platform'; +// @ts-ignore +import getStubIndexPattern from 'fixtures/stubbed_logstash_index_pattern'; + +import { Vis } from '../../visualizations/public'; +import { UrlFormat } from '../../../../plugins/data/public'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; +import { metricVisTypeDefinition } from './metric_vis_type'; + +jest.mock('ui/new_platform'); + +describe('metric_vis - createMetricVisTypeDefinition', () => { + let vis: Vis; + + beforeAll(() => { + visualizationsSetup.types.createReactVisualization(metricVisTypeDefinition); + (npStart.plugins.data.fieldFormats.getType as jest.Mock).mockImplementation(() => { + return UrlFormat; + }); + }); + + const setup = () => { + const stubIndexPattern = getStubIndexPattern(); + + stubIndexPattern.stubSetFieldFormat('ip', 'url', { + urlTemplate: 'http://ip.info?address={{value}}', + labelTemplate: 'ip[{{value}}]', + }); + + // TODO: remove when Vis is converted to typescript. Only importing Vis as type + // @ts-ignore + vis = new Vis(stubIndexPattern, { + type: 'metric', + aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }], + }); + + vis.params.dimensions = { + metrics: [ + { + accessor: 0, + format: { + id: 'url', + params: { + urlTemplate: 'http://ip.info?address={{value}}', + labelTemplate: 'ip[{{value}}]', + }, + }, + }, + ], + }; + + const el = document.createElement('div'); + const metricVisType = visualizationsStart.types.get('metric'); + const Controller = metricVisType.visualization; + const controller = new Controller(el, vis); + const render = (esResponse: any) => { + controller.render(esResponse, vis.params); + }; + + return { el, render }; + }; + + it('renders html value from field formatter', () => { + const { el, render } = setup(); + + const ip = '235.195.237.208'; + render({ + columns: [{ id: 'col-0', name: 'ip' }], + rows: [{ 'col-0': ip }], + }); + + const links = $(el) + .find('a[href]') + .filter(function() { + // @ts-ignore + return this.href.includes('ip.info'); + }); + + expect(links.length).toBe(1); + expect(links.text()).toBe(`ip[${ip}]`); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index ceab5dafe1f06..0d9019ee0579c 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -19,19 +19,12 @@ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { Schemas } from 'ui/vis/editors/default/schemas'; - -import { AggGroupNames } from 'ui/vis/editors/default'; -import { colorSchemas, ColorSchemas } from 'ui/vislib/components/color/colormaps'; - -// @ts-ignore -import { MetricVisComponent } from './components/metric_vis_controller'; - +import { MetricVisComponent } from './components/metric_vis_component'; import { MetricVisOptions } from './components/metric_vis_options'; -import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; +import { Schemas, AggGroupNames, colorSchemas, ColorSchemas } from './legacy_imports'; +import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; -export const metricVisDefinition = { +export const metricVisTypeDefinition = { name: 'metric', title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }), icon: 'visMetric', diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index f5c152ce888c0..413f846d78991 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -22,7 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; -import { metricVisDefinition } from './metric_vis_type'; +import { metricVisTypeDefinition } from './metric_vis_type'; /** @internal */ export interface MetricVisPluginSetupDependencies { @@ -40,7 +40,7 @@ export class MetricVisPlugin implements Plugin { public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) { expressions.registerFunction(createMetricVisFn); - visualizations.types.createReactVisualization(metricVisDefinition); + visualizations.types.createReactVisualization(metricVisTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index ce0e78140a86a..71c1c12b4f8f0 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -17,17 +17,17 @@ * under the License. */ -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; -import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; +import { ColorSchemas } from './legacy_imports'; +import { Range } from '../../../../plugins/expressions/public'; import { SchemaConfig } from '../../visualizations/public'; -import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; -import { Labels, Style } from '../../kbn_vislib_vis_types/public/types'; +import { ColorModes } from '../../vis_type_vislib/public/utils/collections'; +import { Labels, Style } from '../../vis_type_vislib/public/types'; export const visType = 'metric'; export interface DimensionsVisParam { metrics: SchemaConfig[]; - bucket?: SchemaConfig[]; + bucket?: SchemaConfig; } export interface MetricVisParam { @@ -35,7 +35,7 @@ export interface MetricVisParam { useRanges: boolean; colorSchema: ColorSchemas; metricColorMode: ColorModes; - colorsRange: RangeValues[]; + colorsRange: Range[]; labels: Labels; invertColors: boolean; style: Style; @@ -48,3 +48,12 @@ export interface VisParams { metric: MetricVisParam; type: typeof visType; } + +export interface MetricVisMetric { + value: any; + label: string; + color?: string; + bgColor?: string; + lightText: boolean; + rowIndex: number; +} diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index 4d69af59b0c99..33d7480de5a8e 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -28,13 +28,12 @@ import { NumberInputOption, SwitchOption, SelectOption, -} from '../../../kbn_vislib_vis_types/public/components/common'; +} from '../../../vis_type_vislib/public/components/common'; import { TableVisParams } from '../types'; import { totalAggregations, isAggConfigNumeric } from './utils'; function TableOptions({ aggs, - aggsLabels, stateParams, setValidity, setValue, @@ -51,7 +50,7 @@ function TableOptions({ .filter(col => isAggConfigNumeric(get(col, 'aggConfig.type.name'), stateParams.dimensions)) .map(({ name }) => ({ value: name, text: name })), ], - [aggs, aggsLabels, stateParams.percentageCol, stateParams.dimensions] + [aggs, stateParams.percentageCol, stateParams.dimensions] ); const isPerPageValid = stateParams.perPage === '' || stateParams.perPage > 0; diff --git a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts index 7adaa21cac593..a792fc98842f1 100644 --- a/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts +++ b/src/legacy/core_plugins/vis_type_table/public/vis_controller.ts @@ -19,6 +19,7 @@ import angular, { IModule, auto, IRootScopeService, IScope, ICompileService } from 'angular'; import $ from 'jquery'; +import { isEqual } from 'lodash'; import { Vis, VisParams } from '../../visualizations/public'; import { npStart } from './legacy_imports'; @@ -75,6 +76,11 @@ export class TableVisualizationController { this.$scope.vis = this.vis; this.$scope.visState = this.vis.getState(); this.$scope.esResponse = esResponse; + + if (!isEqual(this.$scope.visParams, visParams)) { + this.vis.emit('updateEditorStateParams', visParams); + } + this.$scope.visParams = visParams; this.$scope.renderComplete = resolve; this.$scope.renderFailed = reject; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index 9e6a2d1a24a85..c500b5d888b05 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { ValidatedDualRange } from 'ui/validated_range'; import { VisOptionsProps } from 'ui/vis/editors/default'; -import { SelectOption, SwitchOption } from '../../../kbn_vislib_vis_types/public/components'; +import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public/components'; import { TagCloudVisParams } from '../types'; function TagCloudOptions({ stateParams, setValue, vis }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts similarity index 78% rename from src/legacy/core_plugins/timelion/common/lib/calculate_interval.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts index 7c6b3c2816e67..328c634ea5153 100644 --- a/src/legacy/core_plugins/timelion/common/lib/calculate_interval.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/calculate_interval.ts @@ -17,11 +17,11 @@ * under the License. */ -import toMS from '../../server/lib/to_milliseconds.js'; +import { toMS } from './to_milliseconds'; // Totally cribbed this from Kibana 3. // I bet there's something similar in the Kibana 4 code. Somewhere. Somehow. -function roundInterval(interval) { +function roundInterval(interval: number) { switch (true) { case interval <= 500: // <= 0.5s return '100ms'; @@ -58,9 +58,24 @@ function roundInterval(interval) { } } -export function calculateInterval(from, to, size, interval, min) { - if (interval !== 'auto') return interval; - const dateMathInterval = roundInterval((to - from) / size); - if (toMS(dateMathInterval) < toMS(min)) return min; +export function calculateInterval( + from: number, + to: number, + size: number, + interval: string, + min: string +) { + if (interval !== 'auto') { + return interval; + } + + const dateMathInterval: string = roundInterval((to - from) / size); + const dateMathIntervalMs = toMS(dateMathInterval); + const minMs = toMS(min); + + if (dateMathIntervalMs !== undefined && minMs !== undefined && dateMathIntervalMs < minMs) { + return min; + } + return dateMathInterval; } diff --git a/src/legacy/core_plugins/timelion/common/lib/index.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts similarity index 95% rename from src/legacy/core_plugins/timelion/common/lib/index.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts index 927331043f0b3..1901b8224f607 100644 --- a/src/legacy/core_plugins/timelion/common/lib/index.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/index.ts @@ -18,4 +18,6 @@ */ export { calculateInterval } from './calculate_interval'; +export { toMS } from './to_milliseconds'; + export const DEFAULT_TIME_FORMAT = 'MMMM Do YYYY, HH:mm:ss.SSS'; diff --git a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts similarity index 55% rename from src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js rename to src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts index 0d62d848daba5..f6fcb08b48b25 100644 --- a/src/legacy/core_plugins/timelion/server/lib/to_milliseconds.js +++ b/src/legacy/core_plugins/vis_type_timelion/common/lib/to_milliseconds.ts @@ -17,42 +17,45 @@ * under the License. */ -import _ from 'lodash'; -import moment from 'moment'; +import { keys } from 'lodash'; +import moment, { unitOfTime } from 'moment'; + +type Units = unitOfTime.Base | unitOfTime._quarter; +type Values = { [key in Units]: number }; // map of moment's short/long unit ids and elasticsearch's long unit ids // to their value in milliseconds -const vals = _.transform( - [ - ['ms', 'milliseconds', 'millisecond'], - ['s', 'seconds', 'second', 'sec'], - ['m', 'minutes', 'minute', 'min'], - ['h', 'hours', 'hour'], - ['d', 'days', 'day'], - ['w', 'weeks', 'week'], - ['M', 'months', 'month'], - ['quarter'], - ['y', 'years', 'year'], - ], - function(vals, units) { - const normal = moment.normalizeUnits(units[0]); - const val = moment.duration(1, normal).asMilliseconds(); - [].concat(normal, units).forEach(function(unit) { - vals[unit] = val; - }); - }, - {} -); +const unitMappings = [ + ['ms', 'milliseconds', 'millisecond'], + ['s', 'seconds', 'second', 'sec'], + ['m', 'minutes', 'minute', 'min'], + ['h', 'hours', 'hour'], + ['d', 'days', 'day'], + ['w', 'weeks', 'week'], + ['M', 'months', 'month'], + ['quarter'], + ['y', 'years', 'year'], +] as Units[][]; + +const vals = {} as Values; +unitMappings.forEach(units => { + const normal = moment.normalizeUnits(units[0]) as Units; + const val = moment.duration(1, normal).asMilliseconds(); + ([] as Units[]).concat(normal, units).forEach((unit: Units) => { + vals[unit] = val; + }); +}); + // match any key from the vals object preceded by an optional number -const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + _.keys(vals).join('|') + ')$'); +const parseRE = new RegExp('^(\\d+(?:\\.\\d*)?)?\\s*(' + keys(vals).join('|') + ')$'); -export default function(expr) { +export function toMS(expr: string) { const match = expr.match(parseRE); if (match) { if (match[2] === 'M' && match[1] !== '1') { throw new Error('Invalid interval. 1M is only valid monthly interval.'); } - return parseFloat(match[1] || 1) * vals[match[2]]; + return parseFloat(match[1] || '1') * vals[match[2] as Units]; } } diff --git a/src/legacy/core_plugins/timelion/common/types.ts b/src/legacy/core_plugins/vis_type_timelion/common/types.ts similarity index 100% rename from src/legacy/core_plugins/timelion/common/types.ts rename to src/legacy/core_plugins/vis_type_timelion/common/types.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/index.ts b/src/legacy/core_plugins/vis_type_timelion/index.ts new file mode 100644 index 0000000000000..4664bebb4f38a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/index.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; + +const timelionVisPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'timelion_vis', + require: ['kibana', 'elasticsearch', 'visualizations', 'data'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + }); + +// eslint-disable-next-line import/no-default-export +export default timelionVisPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_timelion/package.json b/src/legacy/core_plugins/vis_type_timelion/package.json new file mode 100644 index 0000000000000..9b09f98ce6caf --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/package.json @@ -0,0 +1,4 @@ +{ + "name": "timelion_vis", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_editor.scss diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss b/src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/vis/_timelion_vis.scss rename to src/legacy/core_plugins/vis_type_timelion/public/_timelion_vis.scss diff --git a/src/legacy/core_plugins/timelion/public/chain.peg b/src/legacy/core_plugins/vis_type_timelion/public/chain.peg similarity index 100% rename from src/legacy/core_plugins/timelion/public/chain.peg rename to src/legacy/core_plugins/vis_type_timelion/public/chain.peg diff --git a/src/legacy/core_plugins/timelion/public/components/_index.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss similarity index 67% rename from src/legacy/core_plugins/timelion/public/components/_index.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss index f2458a367e176..1d887f43ff9a1 100644 --- a/src/legacy/core_plugins/timelion/public/components/_index.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_index.scss @@ -1 +1,2 @@ +@import './panel'; @import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss similarity index 97% rename from src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss index 39713cd05ab37..c4d591bc82cad 100644 --- a/src/legacy/core_plugins/timelion/public/directives/chart/_chart.scss +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/_panel.scss @@ -33,6 +33,7 @@ .ngLegendValue { color: $euiTextColor; + cursor: pointer; &:focus, &:hover { diff --git a/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss b/src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss similarity index 100% rename from src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss rename to src/legacy/core_plugins/vis_type_timelion/public/components/_timelion_expression_input.scss diff --git a/src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx similarity index 70% rename from src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx index d214abecb9c0c..a8b03bdbc8b7e 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options_react_wrapper.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/chart.tsx @@ -18,14 +18,22 @@ */ import React from 'react'; -import { VisOptionsProps } from './vis_options_props'; -interface VisOptionsReactWrapperProps extends VisOptionsProps { - component: React.ComponentType; +import { Sheet } from '../helpers/timelion_request_handler'; +import { Panel } from './panel'; + +interface ChartComponentProp { + interval: string; + renderComplete(): void; + seriesList: Sheet; } -function VisOptionsReactWrapper({ component: Component, ...rest }: VisOptionsReactWrapperProps) { - return ; +function ChartComponent(props: ChartComponentProp) { + if (!props.seriesList) { + return null; + } + + return ; } -export { VisOptionsReactWrapper }; +export { ChartComponent }; diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts similarity index 96% rename from src/legacy/core_plugins/timelion/public/components/index.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/index.ts index 8d7d32a3ba262..c70caab8dd70c 100644 --- a/src/legacy/core_plugins/timelion/public/components/index.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/index.ts @@ -19,3 +19,4 @@ export * from './timelion_expression_input'; export * from './timelion_interval'; +export * from './timelion_vis'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx new file mode 100644 index 0000000000000..6095ba28443b8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/panel.tsx @@ -0,0 +1,386 @@ +/* + * 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, { useState, useEffect, useMemo, useCallback } from 'react'; +import $ from 'jquery'; +import moment from 'moment-timezone'; +import { debounce, compact, get, each, cloneDeep, last, map } from 'lodash'; + +import { useKibana } from '../../../../../plugins/kibana_react/public'; +import '../flot'; +import { DEFAULT_TIME_FORMAT } from '../../common/lib'; + +import { + buildSeriesData, + buildOptions, + SERIES_ID_ATTR, + colors, + Axis, +} from '../helpers/panel_utils'; +import { Series, Sheet } from '../helpers/timelion_request_handler'; +import { tickFormatters } from '../helpers/tick_formatters'; +import { generateTicksProvider } from '../helpers/tick_generator'; +import { TimelionVisDependencies } from '../plugin'; + +interface PanelProps { + interval: string; + seriesList: Sheet; + renderComplete(): void; +} + +interface Position { + x: number; + x1: number; + y: number; + y1: number; + pageX: number; + pageY: number; +} + +interface Range { + to: number; + from: number; +} + +interface Ranges { + xaxis: Range; + yaxis: Range; +} + +const DEBOUNCE_DELAY = 50; +// ensure legend is the same height with or without a caption so legend items do not move around +const emptyCaption = '
'; + +function Panel({ interval, seriesList, renderComplete }: PanelProps) { + const kibana = useKibana(); + const [chart, setChart] = useState(() => cloneDeep(seriesList.list)); + const [canvasElem, setCanvasElem] = useState(); + const [chartElem, setChartElem] = useState(); + + const [originalColorMap, setOriginalColorMap] = useState(() => new Map()); + + const [highlightedSeries, setHighlightedSeries] = useState(null); + const [focusedSeries, setFocusedSeries] = useState(); + const [plot, setPlot] = useState(); + + // Used to toggle the series, and for displaying values on hover + const [legendValueNumbers, setLegendValueNumbers] = useState(); + const [legendCaption, setLegendCaption] = useState(); + + const canvasRef = useCallback(node => { + if (node !== null) { + setCanvasElem(node); + } + }, []); + + const elementRef = useCallback(node => { + if (node !== null) { + setChartElem(node); + } + }, []); + + useEffect( + () => () => { + $(chartElem) + .off('plotselected') + .off('plothover') + .off('mouseleave'); + }, + [chartElem] + ); + + const highlightSeries = useCallback( + debounce(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + if (highlightedSeries === id) { + return; + } + + setHighlightedSeries(id); + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + series.color = + seriesIndex === id + ? originalColorMap.get(series) // color it like it was + : 'rgba(128,128,128,0.1)'; // mark as grey + + return series; + }) + ); + }, DEBOUNCE_DELAY), + [originalColorMap, highlightedSeries] + ); + + const focusSeries = useCallback( + (event: JQuery.TriggeredEvent) => { + const id = Number(event.currentTarget.getAttribute(SERIES_ID_ATTR)); + setFocusedSeries(id); + highlightSeries(event); + }, + [highlightSeries] + ); + + const toggleSeries = useCallback(({ currentTarget }: JQuery.TriggeredEvent) => { + const id = Number(currentTarget.getAttribute(SERIES_ID_ATTR)); + + setChart(chartState => + chartState.map((series: Series, seriesIndex: number) => { + if (seriesIndex === id) { + series._hide = !series._hide; + } + return series; + }) + ); + }, []); + + const updateCaption = useCallback( + (plotData: any) => { + if (get(plotData, '[0]._global.legend.showTime', true)) { + const caption = $(''); + caption.html(emptyCaption); + setLegendCaption(caption); + + const canvasNode = $(canvasElem); + canvasNode.find('div.legend table').append(caption); + setLegendValueNumbers(canvasNode.find('.ngLegendValueNumber')); + + const legend = $(canvasElem).find('.ngLegendValue'); + if (legend) { + legend.click(toggleSeries); + legend.focus(focusSeries); + legend.mouseover(highlightSeries); + } + + // legend has been re-created. Apply focus on legend element when previously set + if (focusedSeries || focusedSeries === 0) { + canvasNode + .find('div.legend table .legendLabel>span') + .get(focusedSeries) + .focus(); + } + } + }, + [focusedSeries, canvasElem, toggleSeries, focusSeries, highlightSeries] + ); + + const updatePlot = useCallback( + (chartValue: Series[], grid?: boolean) => { + if (canvasElem && canvasElem.clientWidth > 0 && canvasElem.clientHeight > 0) { + const options = buildOptions( + interval, + kibana.services.timefilter, + kibana.services.uiSettings, + chartElem && chartElem.clientWidth, + grid + ); + const updatedSeries = buildSeriesData(chartValue, options); + + if (options.yaxes) { + options.yaxes.forEach((yaxis: Axis) => { + if (yaxis && yaxis.units) { + const formatters = tickFormatters(); + yaxis.tickFormatter = formatters[yaxis.units.type as keyof typeof formatters]; + const byteModes = ['bytes', 'bytes/s']; + if (byteModes.includes(yaxis.units.type)) { + yaxis.tickGenerator = generateTicksProvider(); + } + } + }); + } + + const newPlot = $.plot(canvasElem, updatedSeries, options); + setPlot(newPlot); + renderComplete(); + + updateCaption(newPlot.getData()); + } + }, + [canvasElem, chartElem, renderComplete, kibana.services, interval, updateCaption] + ); + + useEffect(() => { + updatePlot(chart, seriesList.render && seriesList.render.grid); + }, [chart, updatePlot, seriesList.render]); + + useEffect(() => { + const colorsSet: Array<[Series, string]> = []; + const newChart = seriesList.list.map((series: Series, seriesIndex: number) => { + const newSeries = { ...series }; + if (!newSeries.color) { + const colorIndex = seriesIndex % colors.length; + newSeries.color = colors[colorIndex]; + } + colorsSet.push([newSeries, newSeries.color]); + return newSeries; + }); + setChart(newChart); + setOriginalColorMap(new Map(colorsSet)); + }, [seriesList.list]); + + const unhighlightSeries = useCallback(() => { + if (highlightedSeries === null) { + return; + } + + setHighlightedSeries(null); + setFocusedSeries(null); + + setChart(chartState => + chartState.map((series: Series) => { + series.color = originalColorMap.get(series); // reset the colors + return series; + }) + ); + }, [originalColorMap, highlightedSeries]); + + // Shamelessly borrowed from the flotCrosshairs example + const setLegendNumbers = useCallback( + (pos: Position) => { + unhighlightSeries(); + + const axes = plot.getAxes(); + if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max) { + return; + } + + const dataset = plot.getData(); + if (legendCaption) { + legendCaption.text( + moment(pos.x).format(get(dataset, '[0]._global.legend.timeFormat', DEFAULT_TIME_FORMAT)) + ); + } + for (let i = 0; i < dataset.length; ++i) { + const series = dataset[i]; + const useNearestPoint = series.lines.show && !series.lines.steps; + const precision = get(series, '_meta.precision', 2); + + if (series._hide) { + continue; + } + + const currentPoint = series.data.find((point: [number, number], index: number) => { + if (index + 1 === series.data.length) { + return true; + } + if (useNearestPoint) { + return pos.x - point[0] < series.data[index + 1][0] - pos.x; + } else { + return pos.x < series.data[index + 1][0]; + } + }); + + const y = currentPoint[1]; + + if (y != null && legendValueNumbers) { + let label = y.toFixed(precision); + if (series.yaxis.tickFormatter) { + label = series.yaxis.tickFormatter(Number(label), series.yaxis); + } + legendValueNumbers.eq(i).text(`(${label})`); + } else { + legendValueNumbers.eq(i).empty(); + } + } + }, + [plot, legendValueNumbers, unhighlightSeries, legendCaption] + ); + + const debouncedSetLegendNumbers = useCallback( + debounce(setLegendNumbers, DEBOUNCE_DELAY, { + maxWait: DEBOUNCE_DELAY, + leading: true, + trailing: false, + }), + [setLegendNumbers] + ); + + const clearLegendNumbers = useCallback(() => { + if (legendCaption) { + legendCaption.html(emptyCaption); + } + each(legendValueNumbers, (num: Node) => { + $(num).empty(); + }); + }, [legendCaption, legendValueNumbers]); + + const plotHoverHandler = useCallback( + (event: JQuery.TriggeredEvent, pos: Position) => { + if (!plot) { + return; + } + plot.setCrosshair(pos); + debouncedSetLegendNumbers(pos); + }, + [plot, debouncedSetLegendNumbers] + ); + const mouseLeaveHandler = useCallback(() => { + if (!plot) { + return; + } + plot.clearCrosshair(); + clearLegendNumbers(); + }, [plot, clearLegendNumbers]); + + const plotSelectedHandler = useCallback( + (event: JQuery.TriggeredEvent, ranges: Ranges) => { + kibana.services.timefilter.setTime({ + from: moment(ranges.xaxis.from), + to: moment(ranges.xaxis.to), + }); + }, + [kibana.services.timefilter] + ); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plotselected') + .on('plotselected', plotSelectedHandler); + } + }, [chartElem, plotSelectedHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('mouseleave') + .on('mouseleave', mouseLeaveHandler); + } + }, [chartElem, mouseLeaveHandler]); + + useEffect(() => { + if (chartElem) { + $(chartElem) + .off('plothover') + .on('plothover', plotHoverHandler); + } + }, [chartElem, plotHoverHandler]); + + const title: string = useMemo(() => last(compact(map(seriesList.list, '_title'))) || '', [ + seriesList.list, + ]); + + return ( +
+
{title}
+
+
+ ); +} + +export { Panel }; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx index c695d09ca822b..fa79e4eb6871a 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input.tsx @@ -25,7 +25,7 @@ import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; import { suggest, getSuggestion } from './timelion_expression_input_helpers'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { getArgValueSuggestions } from '../services/arg_value_suggestions'; +import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; const LANGUAGE_ID = 'timelion_expression'; monacoEditor.languages.register({ id: LANGUAGE_ID }); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts index fc90c276eeca2..5aa05fb16466b 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.ts @@ -26,7 +26,7 @@ import grammar from 'raw-loader!../chain.peg'; import { i18n } from '@kbn/i18n'; import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; -import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions'; +import { ArgValueSuggestions, FunctionArg, Location } from '../helpers/arg_value_suggestions'; const Parser = PEG.generate(grammar); diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx similarity index 98% rename from src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx index 6294e51e54788..4bfa5d424ed85 100644 --- a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -21,8 +21,8 @@ import React, { useMemo, useCallback } from 'react'; import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; import { isValidEsInterval } from '../../../../core_plugins/data/common'; +import { useValidation } from '../legacy_imports'; const intervalOptions = [ { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx new file mode 100644 index 0000000000000..ae55e11380b78 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_vis.tsx @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { IUiSettingsClient } from 'kibana/public'; +import { Vis } from '../legacy_imports'; +import { ChartComponent } from './chart'; +import { VisParams } from '../timelion_vis_fn'; +import { TimelionSuccessResponse } from '../helpers/timelion_request_handler'; + +export interface TimelionVisComponentProp { + config: IUiSettingsClient; + renderComplete(): void; + updateStatus: object; + vis: Vis; + visData: TimelionSuccessResponse; + visParams: VisParams; +} + +function TimelionVisComponent(props: TimelionVisComponentProp) { + return ( +
+ +
+ ); +} + +export { TimelionVisComponent }; diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/flot.js b/src/legacy/core_plugins/vis_type_timelion/public/flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/panels/timechart/flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/flot.js diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts diff --git a/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts new file mode 100644 index 0000000000000..db29d9112be8e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/panel_utils.ts @@ -0,0 +1,187 @@ +/* + * 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 { cloneDeep, defaults, merge, compact } from 'lodash'; +import moment, { Moment } from 'moment-timezone'; + +import { TimefilterContract } from 'src/plugins/data/public'; +import { IUiSettingsClient } from 'kibana/public'; + +import { calculateInterval } from '../../common/lib'; +import { xaxisFormatterProvider } from './xaxis_formatter'; +import { Series } from './timelion_request_handler'; + +export interface Axis { + delta?: number; + max?: number; + min?: number; + mode: string; + options?: { + units: { prefix: string; suffix: string }; + }; + tickSize?: number; + ticks: number; + tickLength: number; + timezone: string; + tickDecimals?: number; + tickFormatter: ((val: number) => string) | ((val: number, axis: Axis) => string); + tickGenerator?(axis: Axis): number[]; + units?: { type: string }; +} + +interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} + +const colors = [ + '#01A4A4', + '#C66', + '#D0D102', + '#616161', + '#00A1CB', + '#32742C', + '#F18D05', + '#113F8C', + '#61AE24', + '#D70060', +]; + +const SERIES_ID_ATTR = 'data-series-id'; + +function buildSeriesData(chart: Series[], options: jquery.flot.plotOptions) { + const seriesData = chart.map((series: Series, seriesIndex: number) => { + const newSeries: Series = cloneDeep( + defaults(series, { + shadowSize: 0, + lines: { + lineWidth: 3, + }, + }) + ); + + newSeries._id = seriesIndex; + + if (series.color) { + const span = document.createElement('span'); + span.style.color = series.color; + newSeries.color = span.style.color; + } + + if (series._hide) { + newSeries.data = []; + newSeries.stack = false; + newSeries.label = `(hidden) ${series.label}`; + } + + if (series._global) { + merge(options, series._global, (objVal, srcVal) => { + // This is kind of gross, it means that you can't replace a global value with a null + // best you can do is an empty string. Deal with it. + if (objVal == null) { + return srcVal; + } + if (srcVal == null) { + return objVal; + } + }); + } + + return newSeries; + }); + + return compact(seriesData); +} + +function buildOptions( + intervalValue: string, + timefilter: TimefilterContract, + uiSettings: IUiSettingsClient, + clientWidth = 0, + showGrid?: boolean +) { + // Get the X-axis tick format + const time: TimeRangeBounds = timefilter.getBounds(); + const interval = calculateInterval( + (time.min && time.min.valueOf()) || 0, + (time.max && time.max.valueOf()) || 0, + uiSettings.get('timelion:target_buckets') || 200, + intervalValue, + uiSettings.get('timelion:min_interval') || '1ms' + ); + const format = xaxisFormatterProvider(uiSettings)(interval); + + const tickLetterWidth = 7; + const tickPadding = 45; + + const options = { + xaxis: { + mode: 'time', + tickLength: 5, + timezone: 'browser', + // Calculate how many ticks can fit on the axis + ticks: Math.floor(clientWidth / (format.length * tickLetterWidth + tickPadding)), + // Use moment to format ticks so we get timezone correction + tickFormatter: (val: number) => moment(val).format(format), + }, + selection: { + mode: 'x', + color: '#ccc', + }, + crosshair: { + mode: 'x', + color: '#C66', + lineWidth: 2, + }, + colors, + grid: { + show: showGrid, + borderWidth: 0, + borderColor: null, + margin: 10, + hoverable: true, + autoHighlight: false, + }, + legend: { + backgroundColor: 'rgb(255,255,255,0)', + position: 'nw', + labelBoxBorderColor: 'rgb(255,255,255,0)', + labelFormatter(label: string, series: { _id: number }) { + const wrapperSpan = document.createElement('span'); + const labelSpan = document.createElement('span'); + const numberSpan = document.createElement('span'); + + wrapperSpan.setAttribute('class', 'ngLegendValue'); + wrapperSpan.setAttribute(SERIES_ID_ATTR, `${series._id}`); + + labelSpan.appendChild(document.createTextNode(label)); + numberSpan.setAttribute('class', 'ngLegendValueNumber'); + + wrapperSpan.appendChild(labelSpan); + wrapperSpan.appendChild(numberSpan); + + return wrapperSpan.outerHTML; + }, + }, + } as jquery.flot.plotOptions & { yaxes?: Axis[] }; + + return options; +} + +export { buildSeriesData, buildOptions, SERIES_ID_ATTR, colors }; diff --git a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts similarity index 100% rename from src/legacy/core_plugins/timelion/public/services/plugin_services.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/plugin_services.ts diff --git a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts similarity index 56% rename from src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts index 40840c4cd2610..01734f2f5888a 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/services/tick_formatters.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.test.ts @@ -17,124 +17,123 @@ * under the License. */ -import expect from '@kbn/expect'; -import { tickFormatters } from '../../services/tick_formatters'; +import { tickFormatters } from './tick_formatters'; describe('Tick Formatters', function() { - let formatters; + let formatters: any; beforeEach(function() { formatters = tickFormatters(); }); describe('Bits mode', function() { - let bitFormatter; + let bitFormatter: any; beforeEach(function() { bitFormatter = formatters.bits; }); it('is a function', function() { - expect(bitFormatter).to.be.a('function'); + expect(bitFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitFormatter(7)).to.equal('7b'); - expect(bitFormatter(4 * 1000)).to.equal('4kb'); - expect(bitFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb'); - expect(bitFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb'); + expect(bitFormatter(7)).toEqual('7b'); + expect(bitFormatter(4 * 1000)).toEqual('4kb'); + expect(bitFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb'); + expect(bitFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb'); }); it('formats negative values with b/kb/mb/gb', () => { - expect(bitFormatter(-7)).to.equal('-7b'); - expect(bitFormatter(-4 * 1000)).to.equal('-4kb'); - expect(bitFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb'); - expect(bitFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb'); + expect(bitFormatter(-7)).toEqual('-7b'); + expect(bitFormatter(-4 * 1000)).toEqual('-4kb'); + expect(bitFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb'); + expect(bitFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb'); }); }); describe('Bits/s mode', function() { - let bitsFormatter; + let bitsFormatter: any; beforeEach(function() { bitsFormatter = formatters['bits/s']; }); it('is a function', function() { - expect(bitsFormatter).to.be.a('function'); + expect(bitsFormatter).toEqual(expect.any(Function)); }); it('formats with b/kb/mb/gb', function() { - expect(bitsFormatter(7)).to.equal('7b/s'); - expect(bitsFormatter(4 * 1000)).to.equal('4kb/s'); - expect(bitsFormatter(4.1 * 1000 * 1000)).to.equal('4.1mb/s'); - expect(bitsFormatter(3 * 1000 * 1000 * 1000)).to.equal('3gb/s'); + expect(bitsFormatter(7)).toEqual('7b/s'); + expect(bitsFormatter(4 * 1000)).toEqual('4kb/s'); + expect(bitsFormatter(4.1 * 1000 * 1000)).toEqual('4.1mb/s'); + expect(bitsFormatter(3 * 1000 * 1000 * 1000)).toEqual('3gb/s'); }); it('formats negative values with b/kb/mb/gb', function() { - expect(bitsFormatter(-7)).to.equal('-7b/s'); - expect(bitsFormatter(-4 * 1000)).to.equal('-4kb/s'); - expect(bitsFormatter(-4.1 * 1000 * 1000)).to.equal('-4.1mb/s'); - expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).to.equal('-3gb/s'); + expect(bitsFormatter(-7)).toEqual('-7b/s'); + expect(bitsFormatter(-4 * 1000)).toEqual('-4kb/s'); + expect(bitsFormatter(-4.1 * 1000 * 1000)).toEqual('-4.1mb/s'); + expect(bitsFormatter(-3 * 1000 * 1000 * 1000)).toEqual('-3gb/s'); }); }); describe('Bytes mode', function() { - let byteFormatter; + let byteFormatter: any; beforeEach(function() { byteFormatter = formatters.bytes; }); it('is a function', function() { - expect(byteFormatter).to.be.a('function'); + expect(byteFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(byteFormatter(10)).to.equal('10B'); - expect(byteFormatter(10 * 1024)).to.equal('10KB'); - expect(byteFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB'); - expect(byteFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB'); + expect(byteFormatter(10)).toEqual('10B'); + expect(byteFormatter(10 * 1024)).toEqual('10KB'); + expect(byteFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB'); + expect(byteFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(byteFormatter(-10)).to.equal('-10B'); - expect(byteFormatter(-10 * 1024)).to.equal('-10KB'); - expect(byteFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB'); - expect(byteFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB'); + expect(byteFormatter(-10)).toEqual('-10B'); + expect(byteFormatter(-10 * 1024)).toEqual('-10KB'); + expect(byteFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB'); + expect(byteFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB'); }); }); describe('Bytes/s mode', function() { - let bytesFormatter; + let bytesFormatter: any; beforeEach(function() { bytesFormatter = formatters['bytes/s']; }); it('is a function', function() { - expect(bytesFormatter).to.be.a('function'); + expect(bytesFormatter).toEqual(expect.any(Function)); }); it('formats with B/KB/MB/GB', function() { - expect(bytesFormatter(10)).to.equal('10B/s'); - expect(bytesFormatter(10 * 1024)).to.equal('10KB/s'); - expect(bytesFormatter(10.2 * 1024 * 1024)).to.equal('10.2MB/s'); - expect(bytesFormatter(3 * 1024 * 1024 * 1024)).to.equal('3GB/s'); + expect(bytesFormatter(10)).toEqual('10B/s'); + expect(bytesFormatter(10 * 1024)).toEqual('10KB/s'); + expect(bytesFormatter(10.2 * 1024 * 1024)).toEqual('10.2MB/s'); + expect(bytesFormatter(3 * 1024 * 1024 * 1024)).toEqual('3GB/s'); }); it('formats negative values with B/KB/MB/GB', function() { - expect(bytesFormatter(-10)).to.equal('-10B/s'); - expect(bytesFormatter(-10 * 1024)).to.equal('-10KB/s'); - expect(bytesFormatter(-10.2 * 1024 * 1024)).to.equal('-10.2MB/s'); - expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).to.equal('-3GB/s'); + expect(bytesFormatter(-10)).toEqual('-10B/s'); + expect(bytesFormatter(-10 * 1024)).toEqual('-10KB/s'); + expect(bytesFormatter(-10.2 * 1024 * 1024)).toEqual('-10.2MB/s'); + expect(bytesFormatter(-3 * 1024 * 1024 * 1024)).toEqual('-3GB/s'); }); }); describe('Currency mode', function() { - let currencyFormatter; + let currencyFormatter: any; beforeEach(function() { currencyFormatter = formatters.currency; }); it('is a function', function() { - expect(currencyFormatter).to.be.a('function'); + expect(currencyFormatter).toEqual(expect.any(Function)); }); it('formats with $ by default', function() { @@ -143,7 +142,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(currencyFormatter(10.2, axis)).to.equal('$10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('$10.20'); }); it('accepts currency in ISO 4217', function() { @@ -155,18 +154,18 @@ describe('Tick Formatters', function() { }, }; - expect(currencyFormatter(10.2, axis)).to.equal('CN¥10.20'); + expect(currencyFormatter(10.2, axis)).toEqual('CN¥10.20'); }); }); describe('Percent mode', function() { - let percentFormatter; + let percentFormatter: any; beforeEach(function() { percentFormatter = formatters.percent; }); it('is a function', function() { - expect(percentFormatter).to.be.a('function'); + expect(percentFormatter).toEqual(expect.any(Function)); }); it('formats with %', function() { @@ -175,7 +174,7 @@ describe('Tick Formatters', function() { units: {}, }, }; - expect(percentFormatter(0.1234, axis)).to.equal('12%'); + expect(percentFormatter(0.1234, axis)).toEqual('12%'); }); it('formats with % with decimal precision', function() { @@ -189,18 +188,18 @@ describe('Tick Formatters', function() { }, }, }; - expect(percentFormatter(0.12345, axis)).to.equal('12.345%'); + expect(percentFormatter(0.12345, axis)).toEqual('12.345%'); }); }); describe('Custom mode', function() { - let customFormatter; + let customFormatter: any; beforeEach(function() { customFormatter = formatters.custom; }); it('is a function', function() { - expect(customFormatter).to.be.a('function'); + expect(customFormatter).toEqual(expect.any(Function)); }); it('accepts prefix and suffix', function() { @@ -214,7 +213,7 @@ describe('Tick Formatters', function() { tickDecimals: 1, }; - expect(customFormatter(10.2, axis)).to.equal('prefix10.2suffix'); + expect(customFormatter(10.2, axis)).toEqual('prefix10.2suffix'); }); it('correctly renders small values', function() { @@ -228,7 +227,7 @@ describe('Tick Formatters', function() { tickDecimals: 3, }; - expect(customFormatter(0.00499999999999999, axis)).to.equal('prefix0.005suffix'); + expect(customFormatter(0.00499999999999999, axis)).toEqual('prefix0.005suffix'); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts similarity index 79% rename from src/legacy/core_plugins/timelion/public/services/tick_formatters.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts index 2c78d2423cc06..c80f9c3ed5f4b 100644 --- a/src/legacy/core_plugins/timelion/public/services/tick_formatters.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_formatters.ts @@ -17,9 +17,11 @@ * under the License. */ -import _ from 'lodash'; +import { get } from 'lodash'; -function baseTickFormatter(value: any, axis: any) { +import { Axis } from './panel_utils'; + +function baseTickFormatter(value: number, axis: Axis) { const factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; const formatted = '' + Math.round(value * factor) / factor; @@ -40,8 +42,8 @@ function baseTickFormatter(value: any, axis: any) { return formatted; } -function unitFormatter(divisor: any, units: any) { - return (val: any) => { +function unitFormatter(divisor: number, units: string[]) { + return (val: number) => { let index = 0; const isNegative = val < 0; val = Math.abs(val); @@ -55,20 +57,20 @@ function unitFormatter(divisor: any, units: any) { } export function tickFormatters() { - const formatters = { + return { bits: unitFormatter(1000, ['b', 'kb', 'mb', 'gb', 'tb', 'pb']), 'bits/s': unitFormatter(1000, ['b/s', 'kb/s', 'mb/s', 'gb/s', 'tb/s', 'pb/s']), bytes: unitFormatter(1024, ['B', 'KB', 'MB', 'GB', 'TB', 'PB']), 'bytes/s': unitFormatter(1024, ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']), - currency(val: any, axis: any) { + currency(val: number, axis: Axis) { return val.toLocaleString('en', { style: 'currency', - currency: axis.options.units.prefix || 'USD', + currency: (axis && axis.options && axis.options.units.prefix) || 'USD', }); }, - percent(val: any, axis: any) { + percent(val: number, axis: Axis) { let precision = - _.get(axis, 'tickDecimals', 0) - _.get(axis, 'options.units.tickDecimalsShift', 0); + get(axis, 'tickDecimals', 0) - get(axis, 'options.units.tickDecimalsShift', 0); // toFixed only accepts values between 0 and 20 if (precision < 0) { precision = 0; @@ -78,13 +80,11 @@ export function tickFormatters() { return (val * 100).toFixed(precision) + '%'; }, - custom(val: any, axis: any) { + custom(val: number, axis: Axis) { const formattedVal = baseTickFormatter(val, axis); - const prefix = axis.options.units.prefix; - const suffix = axis.options.units.suffix; + const prefix = axis && axis.options && axis.options.units.prefix; + const suffix = axis && axis.options && axis.options.units.suffix; return prefix + formattedVal + suffix; }, }; - - return formatters; } diff --git a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts similarity index 72% rename from src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts index e42657374af4c..d1d959dee9501 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/_tick_generator.js +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.test.ts @@ -17,11 +17,10 @@ * under the License. */ -import expect from '@kbn/expect'; -import { generateTicksProvider } from '../panels/timechart/tick_generator'; +import { generateTicksProvider } from './tick_generator'; describe('Tick Generator', function() { - let generateTicks; + let generateTicks: any; beforeEach(function() { generateTicks = generateTicksProvider(); @@ -29,7 +28,7 @@ describe('Tick Generator', function() { describe('generateTicksProvider()', function() { it('should return a function', function() { - expect(generateTicks).to.be.a('function'); + expect(generateTicks).toEqual(expect.any(Function)); }); }); @@ -58,14 +57,14 @@ describe('Tick Generator', function() { let n = 1; while (Math.pow(2, n) < axis.delta) n++; const expectedDelta = Math.pow(2, n); - const expectedNr = parseInt((axis.max - axis.min) / expectedDelta) + 2; - expect(ticks instanceof Array).to.be(true); - expect(ticks.length).to.be(expectedNr); - expect(ticks[0]).to.equal(axis.min); - expect(ticks[parseInt(ticks.length / 2)]).to.equal( - axis.min + expectedDelta * parseInt(ticks.length / 2) + const expectedNr = Math.floor((axis.max - axis.min) / expectedDelta) + 2; + expect(ticks instanceof Array).toBeTruthy(); + expect(ticks.length).toBe(expectedNr); + expect(ticks[0]).toEqual(axis.min); + expect(ticks[Math.floor(ticks.length / 2)]).toEqual( + axis.min + expectedDelta * Math.floor(ticks.length / 2) ); - expect(ticks[ticks.length - 1]).to.equal(axis.min + expectedDelta * (ticks.length - 1)); + expect(ticks[ticks.length - 1]).toEqual(axis.min + expectedDelta * (ticks.length - 1)); }); }); }); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts similarity index 82% rename from src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts index f7d696a0316db..6321ad01418ac 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/tick_generator.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/tick_generator.ts @@ -17,15 +17,17 @@ * under the License. */ +import { Axis } from './panel_utils'; + export function generateTicksProvider() { - function floorInBase(n: any, base: any) { + function floorInBase(n: number, base: number) { return base * Math.floor(n / base); } - function generateTicks(axis: any) { + function generateTicks(axis: Axis) { const returnTicks = []; let tickSize = 2; - let delta = axis.delta; + let delta = axis.delta || 0; let steps = 0; let tickVal; let tickCount = 0; @@ -46,16 +48,14 @@ export function generateTicksProvider() { axis.tickSize = tickSize * Math.pow(1024, steps); // Calculate the new ticks - const tickMin = floorInBase(axis.min, axis.tickSize); + const tickMin = floorInBase(axis.min || 0, axis.tickSize); do { tickVal = tickMin + tickCount++ * axis.tickSize; returnTicks.push(tickVal); - } while (tickVal < axis.max); + } while (tickVal < (axis.max || 0)); return returnTicks; } - return function(axis: any) { - return generateTicks(axis); - }; + return (axis: Axis) => generateTicks(axis); } diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts similarity index 83% rename from src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts index 14cd3d0083e6a..de066b474d987 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/timelion_request_handler.ts @@ -17,13 +17,11 @@ * under the License. */ -// @ts-ignore -import { timezoneProvider } from 'ui/vis/lib/timezone'; import { KIBANA_CONTEXT_NAME } from 'src/plugins/expressions/public'; -import { VisParams } from 'ui/vis'; import { i18n } from '@kbn/i18n'; -import { TimelionVisualizationDependencies } from '../plugin'; import { TimeRange, esFilters, esQuery, Query } from '../../../../../plugins/data/public'; +import { timezoneProvider, VisParams } from '../legacy_imports'; +import { TimelionVisDependencies } from '../plugin'; interface Stats { cacheCount: number; @@ -33,9 +31,25 @@ interface Stats { sheetTime: number; } -interface Sheet { - list: Array>; - render: Record; +export interface Series { + _global?: boolean; + _hide?: boolean; + _id?: number; + _title?: string; + color?: string; + data: Array>; + fit: string; + label: string; + split: string; + stack?: boolean; + type: string; +} + +export interface Sheet { + list: Series[]; + render?: { + grid?: boolean; + }; type: string; } @@ -46,8 +60,11 @@ export interface TimelionSuccessResponse { type: KIBANA_CONTEXT_NAME; } -export function getTimelionRequestHandler(dependencies: TimelionVisualizationDependencies) { - const { uiSettings, http, timefilter } = dependencies; +export function getTimelionRequestHandler({ + uiSettings, + http, + timefilter, +}: TimelionVisDependencies) { const timezone = timezoneProvider(uiSettings)(); return async function({ diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts similarity index 86% rename from src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts rename to src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts index db3408dae33db..5350b1cb26957 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/xaxis_formatter.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/helpers/xaxis_formatter.ts @@ -20,12 +20,13 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { IUiSettingsClient } from 'kibana/public'; -export function xaxisFormatterProvider(config: any) { +export function xaxisFormatterProvider(config: IUiSettingsClient) { function getFormat(esInterval: any) { const parts = esInterval.match(/(\d+)(ms|s|m|h|d|w|M|y|)/); - if (parts == null || parts[1] == null || parts[2] == null) { + if (parts === null || parts[1] === null || parts[2] === null) { throw new Error( i18n.translate('timelion.panels.timechart.unknownIntervalErrorMessage', { defaultMessage: 'Unknown interval', @@ -48,7 +49,5 @@ export function xaxisFormatterProvider(config: any) { return config.get('dateFormat'); } - return function(esInterval: any) { - return getFormat(esInterval); - }; + return (esInterval: any) => getFormat(esInterval); } diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.scss b/src/legacy/core_plugins/vis_type_timelion/public/index.scss new file mode 100644 index 0000000000000..313f14a8acf69 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/index.scss @@ -0,0 +1,5 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './timelion_vis'; +@import './timelion_editor'; +@import './components/index'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/index.ts b/src/legacy/core_plugins/vis_type_timelion/public/index.ts new file mode 100644 index 0000000000000..98cc35877094e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/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. + */ + +import { PluginInitializerContext } from '../../../../core/public'; +import { TimelionVisPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts new file mode 100644 index 0000000000000..9935f3d92f6bd --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'kibana/public'; + +import { npSetup, npStart } from './legacy_imports'; + +import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; +import { TimelionVisSetupDependencies } from './plugin'; +import { plugin } from '.'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + data: npSetup.plugins.data, + visualizations: visualizationsSetup, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.ts new file mode 100644 index 0000000000000..8d1156862d27e --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/legacy_imports.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. + */ + +export { npSetup, npStart } from 'ui/new_platform'; +export { PluginsStart } from 'ui/new_platform/new_platform'; + +// @ts-ignore +export { DefaultEditorSize } from 'ui/vis/editor_size'; +// @ts-ignore +export { timezoneProvider } from 'ui/vis/lib/timezone'; +export { VisParams, Vis } from 'ui/vis'; +export { VisOptionsProps } from 'ui/vis/editors/default'; +export { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts new file mode 100644 index 0000000000000..69a2ad3c1351a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timelion/public/plugin.ts @@ -0,0 +1,76 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, + IUiSettingsClient, + HttpSetup, +} from 'kibana/public'; +import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; +import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; + +import { PluginsStart } from './legacy_imports'; +import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; + +import { getTimelionVisualizationConfig } from './timelion_vis_fn'; +import { getTimelionVisDefinition } from './timelion_vis_type'; +import { setIndexPatterns, setSavedObjectsClient } from './helpers/plugin_services'; + +type TimelionVisCoreSetup = CoreSetup; + +/** @internal */ +export interface TimelionVisDependencies extends Partial { + uiSettings: IUiSettingsClient; + http: HttpSetup; + timefilter: TimefilterContract; +} + +/** @internal */ +export interface TimelionVisSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + data: DataPublicPluginSetup; +} + +/** @internal */ +export class TimelionVisPlugin implements Plugin { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: TimelionVisCoreSetup, + { expressions, visualizations, data }: TimelionVisSetupDependencies + ) { + const dependencies: TimelionVisDependencies = { + uiSettings: core.uiSettings, + http: core.http, + timefilter: data.query.timefilter.timefilter, + }; + + expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); + visualizations.types.createReactVisualization(getTimelionVisDefinition(dependencies)); + } + + public start(core: CoreStart, plugins: PluginsStart) { + setIndexPatterns(plugins.data.indexPatterns); + setSavedObjectsClient(core.savedObjects.client); + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx similarity index 89% rename from src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index 527fcc3bc6ce8..be6829a76ac58 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,9 +20,9 @@ import React, { useCallback } from 'react'; import { EuiPanel } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { VisParams } from '../timelion_vis_fn'; -import { TimelionInterval, TimelionExpressionInput } from '../components'; +import { VisOptionsProps } from './legacy_imports'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionInterval, TimelionExpressionInput } from './components'; function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts similarity index 91% rename from src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts index 206f9f5d8368d..8a517b6cecbc7 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_fn.ts @@ -20,9 +20,9 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { ExpressionFunction, KibanaContext, Render } from 'src/plugins/expressions/public'; -import { getTimelionRequestHandler } from './vis/timelion_request_handler'; -import { TimelionVisualizationDependencies } from './plugin'; -import { TIMELION_VIS_NAME } from './vis'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TIMELION_VIS_NAME } from './timelion_vis_type'; +import { TimelionVisDependencies } from './plugin'; const name = 'timelion_vis'; @@ -42,7 +42,7 @@ export type VisParams = Arguments; type Return = Promise>; export const getTimelionVisualizationConfig = ( - dependencies: TimelionVisualizationDependencies + dependencies: TimelionVisDependencies ): ExpressionFunction => ({ name, type: 'render', diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx similarity index 73% rename from src/legacy/core_plugins/timelion/public/vis/index.tsx rename to src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 1edcb0a5ce71c..51540eea0223c 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -19,21 +19,18 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { DefaultEditorSize } from 'ui/vis/editor_size'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import visConfigTemplate from './timelion_vis.html'; -import { TimelionVisualizationDependencies } from '../plugin'; -// @ts-ignore -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; + +import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; +import { DefaultEditorSize, VisOptionsProps } from './legacy_imports'; +import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; +import { TimelionVisComponent, TimelionVisComponentProp } from './components'; import { TimelionOptions } from './timelion_options'; -import { VisParams } from '../timelion_vis_fn'; +import { VisParams } from './timelion_vis_fn'; +import { TimelionVisDependencies } from './plugin'; export const TIMELION_VIS_NAME = 'timelion'; -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { +export function getTimelionVisDefinition(dependencies: TimelionVisDependencies) { const { http, uiSettings } = dependencies; const timelionRequestHandler = getTimelionRequestHandler(dependencies); @@ -46,13 +43,16 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe description: i18n.translate('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - visualization: AngularVisController, visConfig: { defaults: { expression: '.es(*)', interval: 'auto', }, - template: visConfigTemplate, + component: (props: TimelionVisComponentProp) => ( + + + + ), }, editorConfig: { optionsTemplate: (props: VisOptionsProps) => ( diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.axislabels.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.axislabels.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.crosshair.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.crosshair.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.selection.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.selection.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.stack.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.stack.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.symbol.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.symbol.js diff --git a/src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js b/src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js similarity index 100% rename from src/legacy/core_plugins/timelion/public/webpackShims/jquery.flot.time.js rename to src/legacy/core_plugins/vis_type_timelion/public/webpackShims/jquery.flot.time.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js b/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js deleted file mode 100644 index f78f0a5660419..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/agg_lookup.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { expect } from 'chai'; -import { createOptions, isBasicAgg } from '../agg_lookup'; - -describe('aggLookup', () => { - describe('isBasicAgg(metric)', () => { - it('returns true for a basic metric (count)', () => { - expect(isBasicAgg({ type: 'count' })).to.equal(true); - }); - it('returns false for a pipeline metric (derivative)', () => { - expect(isBasicAgg({ type: 'derivative' })).to.equal(false); - }); - }); - - describe('createOptions(type, siblings)', () => { - it('returns options for all aggs', () => { - const options = createOptions(); - expect(options).to.have.length(30); - options.forEach(option => { - expect(option).to.have.property('label'); - expect(option).to.have.property('value'); - expect(option).to.have.property('disabled'); - }); - }); - - it('returns options for basic', () => { - const options = createOptions('basic'); - expect(options).to.have.length(15); - expect(options.every(opt => isBasicAgg({ type: opt.value }))).to.equal(true); - }); - - it('returns options for pipeline', () => { - const options = createOptions('pipeline'); - expect(options).to.have.length(15); - expect(options.every(opt => !isBasicAgg({ type: opt.value }))).to.equal(true); - }); - - it('returns options for all if given unknown key', () => { - const options = createOptions('foo'); - expect(options).to.have.length(30); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js index 0f6eba4add596..4dfdc83dcfabb 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js +++ b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.js @@ -127,25 +127,3 @@ const byType = { export function isBasicAgg(item) { return _.includes(Object.keys(byType.basic), item.type); } - -export function createOptions(type = '_all', siblings = []) { - let aggs = byType[type]; - if (!aggs) aggs = byType._all; - let enablePipelines = siblings.some(isBasicAgg); - if (siblings.length <= 1) enablePipelines = false; - return _(aggs) - .map((label, value) => { - const disabled = _.includes(pipeline, value) ? !enablePipelines : false; - return { - label: disabled - ? i18n.translate('visTypeTimeseries.aggLookup.addPipelineAggDescription', { - defaultMessage: '{label} (use the "+" button to add this pipeline agg)', - values: { label }, - }) - : label, - value, - disabled, - }; - }) - .value(); -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js new file mode 100644 index 0000000000000..a7c5d362e669c --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timeseries/common/agg_lookup.test.js @@ -0,0 +1,31 @@ +/* + * 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 { isBasicAgg } from './agg_lookup'; + +describe('aggLookup', () => { + describe('isBasicAgg(metric)', () => { + test('returns true for a basic metric (count)', () => { + expect(isBasicAgg({ type: 'count' })).toEqual(true); + }); + test('returns false for a pipeline metric (derivative)', () => { + expect(isBasicAgg({ type: 'derivative' })).toEqual(false); + }); + }); +}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js b/src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js similarity index 59% rename from src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js rename to src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js index 955c052a3906e..a5af6d114c894 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/common/__tests__/calculate_label.js +++ b/src/legacy/core_plugins/vis_type_timeseries/common/calculate_label.test.js @@ -17,51 +17,50 @@ * under the License. */ -import { expect } from 'chai'; -import { calculateLabel } from '../calculate_label'; +import { calculateLabel } from './calculate_label'; describe('calculateLabel(metric, metrics)', () => { - it('returns "Unknown" for empty metric', () => { - expect(calculateLabel()).to.equal('Unknown'); + test('returns "Unknown" for empty metric', () => { + expect(calculateLabel()).toEqual('Unknown'); }); - it('returns the metric.alias if set', () => { - expect(calculateLabel({ alias: 'Example' })).to.equal('Example'); + test('returns the metric.alias if set', () => { + expect(calculateLabel({ alias: 'Example' })).toEqual('Example'); }); - it('returns "Count" for a count metric', () => { - expect(calculateLabel({ type: 'count' })).to.equal('Count'); + test('returns "Count" for a count metric', () => { + expect(calculateLabel({ type: 'count' })).toEqual('Count'); }); - it('returns "Calculation" for a bucket script metric', () => { - expect(calculateLabel({ type: 'calculation' })).to.equal('Bucket Script'); + test('returns "Calculation" for a bucket script metric', () => { + expect(calculateLabel({ type: 'calculation' })).toEqual('Bucket Script'); }); - it('returns formated label for series_agg', () => { + test('returns formated label for series_agg', () => { const label = calculateLabel({ type: 'series_agg', function: 'max' }); - expect(label).to.equal('Series Agg (max)'); + expect(label).toEqual('Series Agg (max)'); }); - it('returns formated label for basic aggs', () => { + test('returns formated label for basic aggs', () => { const label = calculateLabel({ type: 'avg', field: 'memory' }); - expect(label).to.equal('Average of memory'); + expect(label).toEqual('Average of memory'); }); - it('returns formated label for pipeline aggs', () => { + test('returns formated label for pipeline aggs', () => { const metric = { id: 2, type: 'derivative', field: 1 }; const metrics = [{ id: 1, type: 'max', field: 'network.out.bytes' }, metric]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Max of network.out.bytes'); + expect(label).toEqual('Derivative of Max of network.out.bytes'); }); - it('returns formated label for derivative of percentile', () => { + test('returns formated label for derivative of percentile', () => { const metric = { id: 2, type: 'derivative', field: '1[50.0]' }; const metrics = [{ id: 1, type: 'percentile', field: 'network.out.bytes' }, metric]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Percentile of network.out.bytes (50.0)'); + expect(label).toEqual('Derivative of Percentile of network.out.bytes (50.0)'); }); - it('returns formated label for pipeline aggs (deep)', () => { + test('returns formated label for pipeline aggs (deep)', () => { const metric = { id: 3, type: 'derivative', field: 2 }; const metrics = [ { id: 1, type: 'max', field: 'network.out.bytes' }, @@ -69,16 +68,16 @@ describe('calculateLabel(metric, metrics)', () => { metric, ]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Moving Average of Max of network.out.bytes'); + expect(label).toEqual('Derivative of Moving Average of Max of network.out.bytes'); }); - it('returns formated label for pipeline aggs uses alias for field metric', () => { + test('returns formated label for pipeline aggs uses alias for field metric', () => { const metric = { id: 2, type: 'derivative', field: 1 }; const metrics = [ { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, metric, ]; const label = calculateLabel(metric, metrics); - expect(label).to.equal('Derivative of Outbound Traffic'); + expect(label).toEqual('Derivative of Outbound Traffic'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js index 40807399a35a2..4f343022c4b0f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/calculate_siblings.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/calculate_siblings.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { calculateSiblings } from '../calculate_siblings'; -import { expect } from 'chai'; +import { calculateSiblings } from './calculate_siblings'; describe('calculateSiblings(metrics, metric)', () => { - it('should return all siblings', () => { + test('should return all siblings', () => { const metrics = [ { id: 1, type: 'max', field: 'network.bytes' }, { id: 2, type: 'derivative', field: 1 }, @@ -30,7 +29,7 @@ describe('calculateSiblings(metrics, metric)', () => { { id: 5, type: 'count' }, ]; const siblings = calculateSiblings(metrics, { id: 2 }); - expect(siblings).to.eql([ + expect(siblings).toEqual([ { id: 1, type: 'max', field: 'network.bytes' }, { id: 5, type: 'count' }, ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js index 5f97f559aa4bd..c76943cc8d6d7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/collection_actions.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/collection_actions.test.js @@ -17,37 +17,35 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { handleChange, handleAdd, handleDelete } from '../collection_actions'; +import { handleChange, handleAdd, handleDelete } from './collection_actions'; describe('collection actions', () => { - it('handleChange() calls props.onChange() with updated collection', () => { - const fn = sinon.spy(); + test('handleChange() calls props.onChange() with updated collection', () => { + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleChange.call(null, props, { id: 1, title: 'bar' }); - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [{ id: 1, title: 'bar' }], }); }); - it('handleAdd() calls props.onChange() with update collection', () => { - const newItemFn = sinon.stub().returns({ id: 2, title: 'example' }); - const fn = sinon.spy(); + test('handleAdd() calls props.onChange() with update collection', () => { + const newItemFn = jest.fn(() => ({ id: 2, title: 'example' })); + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleAdd.call(null, props, newItemFn); - expect(fn.calledOnce).to.equal(true); - expect(newItemFn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(newItemFn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [ { id: 1, title: 'foo' }, { id: 2, title: 'example' }, @@ -55,16 +53,16 @@ describe('collection actions', () => { }); }); - it('handleDelete() calls props.onChange() with update collection', () => { - const fn = sinon.spy(); + test('handleDelete() calls props.onChange() with update collection', () => { + const fn = jest.fn(); const props = { model: { test: [{ id: 1, title: 'foo' }] }, name: 'test', onChange: fn, }; handleDelete.call(null, props, { id: 1 }); - expect(fn.calledOnce).to.equal(true); - expect(fn.firstCall.args[0]).to.eql({ + expect(fn.mock.calls.length).toEqual(1); + expect(fn.mock.calls[0][0]).toEqual({ test: [], }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js index 40e3b302c1cf6..689b62962256a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/convert_series_to_vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/convert_series_to_vars.test.js @@ -18,5 +18,5 @@ */ describe('convertSeriesToVars(series, model)', () => { - it('returns and object', () => {}); + test('returns and object', () => {}); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js index fdbef9eac2d02..e24fe1f1d88d3 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_number_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_number_handler.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createNumberHandler } from '../create_number_handler'; +import { createNumberHandler } from './create_number_handler'; describe('createNumberHandler()', () => { let handleChange; @@ -27,17 +25,17 @@ describe('createNumberHandler()', () => { let event; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createNumberHandler(handleChange); - event = { preventDefault: sinon.spy(), target: { value: '1' } }; + event = { preventDefault: jest.fn(), target: { value: '1' } }; const fn = changeHandler('test'); fn(event); }); - it('calls handleChange() function with partial', () => { - expect(event.preventDefault.calledOnce).to.equal(true); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(event.preventDefault.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 1, }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js similarity index 77% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js index 7bc9ae5de0162..a8d5351341c17 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_select_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_select_handler.test.js @@ -17,24 +17,22 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createSelectHandler } from '../create_select_handler'; +import { createSelectHandler } from './create_select_handler'; describe('createSelectHandler()', () => { let handleChange; let changeHandler; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createSelectHandler(handleChange); const fn = changeHandler('test'); fn([{ value: 'foo' }]); }); - it('calls handleChange() function with partial', () => { - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 'foo', }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js index 689fc5c4d7df0..a8a5377b4b084 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/create_text_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/create_text_handler.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { createTextHandler } from '../create_text_handler'; +import { createTextHandler } from './create_text_handler'; describe('createTextHandler()', () => { let handleChange; @@ -27,17 +25,17 @@ describe('createTextHandler()', () => { let event; beforeEach(() => { - handleChange = sinon.spy(); + handleChange = jest.fn(); changeHandler = createTextHandler(handleChange); - event = { preventDefault: sinon.spy(), target: { value: 'foo' } }; + event = { preventDefault: jest.fn(), target: { value: 'foo' } }; const fn = changeHandler('test'); fn(event); }); - it('calls handleChange() function with partial', () => { - expect(event.preventDefault.calledOnce).to.equal(true); - expect(handleChange.calledOnce).to.equal(true); - expect(handleChange.firstCall.args[0]).to.eql({ + test('calls handleChange() function with partial', () => { + expect(event.preventDefault.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls.length).toEqual(1); + expect(handleChange.mock.calls[0][0]).toEqual({ test: 'foo', }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js index 455a5b262c929..cfbd5ecb7bf65 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/get_axis_label_string.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/get_axis_label_string.test.js @@ -17,17 +17,18 @@ * under the License. */ -import { expect } from 'chai'; -import { getAxisLabelString } from '../get_axis_label_string'; +import { getAxisLabelString } from './get_axis_label_string'; + +jest.mock('ui/new_platform'); describe('getAxisLabelString(interval)', () => { - it('should return a valid label for 10 seconds', () => { - expect(getAxisLabelString(10000)).to.equal('per 10 seconds'); + test('should return a valid label for 10 seconds', () => { + expect(getAxisLabelString(10000)).toEqual('per 10 seconds'); }); - it('should return a valid label for 2 minutes', () => { - expect(getAxisLabelString(120000)).to.equal('per 2 minutes'); + test('should return a valid label for 2 minutes', () => { + expect(getAxisLabelString(120000)).toEqual('per 2 minutes'); }); - it('should return a valid label for 2 hour', () => { - expect(getAxisLabelString(7200000)).to.equal('per 2 hours'); + test('should return a valid label for 2 hour', () => { + expect(getAxisLabelString(7200000)).toEqual('per 2 hours'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js similarity index 64% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js index b629e284a0fe4..7c646c7dde2e8 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/re_id_series.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/re_id_series.test.js @@ -18,24 +18,24 @@ */ import uuid from 'uuid'; -import { expect } from 'chai'; -import { reIdSeries } from '../re_id_series'; + +import { reIdSeries } from './re_id_series'; describe('reIdSeries()', () => { - it('reassign ids for series with just basic metrics', () => { + test('reassign ids for series with just basic metrics', () => { const series = { id: uuid.v1(), metrics: [{ id: uuid.v1() }, { id: uuid.v1() }], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); newSeries.metrics.forEach((val, key) => { - expect(val.id).to.not.equal(series.metrics[key].id); + expect(val.id).not.toEqual(series.metrics[key].id); }); }); - it('reassign ids for series with just basic metrics and group by', () => { + test('reassign ids for series with just basic metrics and group by', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), @@ -43,27 +43,27 @@ describe('reIdSeries()', () => { terms_order_by: firstMetricId, }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); newSeries.metrics.forEach((val, key) => { - expect(val.id).to.not.equal(series.metrics[key].id); + expect(val.id).not.toEqual(series.metrics[key].id); }); - expect(newSeries.terms_order_by).to.equal(newSeries.metrics[0].id); + expect(newSeries.terms_order_by).toEqual(newSeries.metrics[0].id); }); - it('reassign ids for series with pipeline metrics', () => { + test('reassign ids for series with pipeline metrics', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), metrics: [{ id: firstMetricId }, { id: uuid.v1(), field: firstMetricId }], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); - expect(newSeries.metrics[0].id).to.equal(newSeries.metrics[1].field); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); + expect(newSeries.metrics[0].id).toEqual(newSeries.metrics[1].field); }); - it('reassign ids for series with calculation vars', () => { + test('reassign ids for series with calculation vars', () => { const firstMetricId = uuid.v1(); const series = { id: uuid.v1(), @@ -77,8 +77,8 @@ describe('reIdSeries()', () => { ], }; const newSeries = reIdSeries(series); - expect(newSeries).to.not.equal(series); - expect(newSeries.id).to.not.equal(series.id); - expect(newSeries.metrics[1].variables[0].field).to.equal(newSeries.metrics[0].id); + expect(newSeries).not.toEqual(series); + expect(newSeries.id).not.toEqual(series.id); + expect(newSeries.metrics[1].variables[0].field).toEqual(newSeries.metrics[0].id); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js similarity index 74% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js index 0ac7427f6facc..517958692dd2c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/replace_vars.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/replace_vars.test.js @@ -17,26 +17,25 @@ * under the License. */ -import { expect } from 'chai'; -import { replaceVars } from '../replace_vars'; +import { replaceVars } from './replace_vars'; describe('replaceVars(str, args, vars)', () => { - it('replaces vars with values', () => { + test('replaces vars with values', () => { const vars = { total: 100 }; const args = { host: 'test-01' }; const template = '# {{args.host}} {{total}}'; - expect(replaceVars(template, args, vars)).to.equal('# test-01 100'); + expect(replaceVars(template, args, vars)).toEqual('# test-01 100'); }); - it('replaces args override vars', () => { + test('replaces args override vars', () => { const vars = { total: 100, args: { test: 'foo-01' } }; const args = { test: 'bar-01' }; const template = '# {{args.test}} {{total}}'; - expect(replaceVars(template, args, vars)).to.equal('# bar-01 100'); + expect(replaceVars(template, args, vars)).toEqual('# bar-01 100'); }); - it('returns original string if error', () => { + test('returns original string if error', () => { const vars = { total: 100 }; const args = { host: 'test-01' }; const template = '# {{args.host}} {{total'; - expect(replaceVars(template, args, vars)).to.equal('# {{args.host}} {{total'); + expect(replaceVars(template, args, vars)).toEqual('# {{args.host}} {{total'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js similarity index 50% rename from src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js rename to src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js index f31af4e846305..76d3cff17343e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/__tests__/tick_formatter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/lib/tick_formatter.test.js @@ -17,64 +17,93 @@ * under the License. */ -import { expect } from 'chai'; -import { createTickFormatter } from '../tick_formatter'; +import { npStart } from 'ui/new_platform'; +import { createTickFormatter } from './tick_formatter'; +import { getFieldFormatsRegistry } from '../../../../../../test_utils/public/stub_field_formats'; + +const mockUiSettings = { + get: item => { + return mockUiSettings[item]; + }, + getUpdate$: () => ({ + subscribe: jest.fn(), + }), + 'query:allowLeadingWildcards': true, + 'query:queryString:options': {}, + 'courier:ignoreFilterIfFieldNotInIndex': true, + 'dateFormat:tz': 'Browser', + 'format:defaultTypeMap': {}, +}; + +const mockCore = { + chrome: {}, + uiSettings: mockUiSettings, + http: { + basePath: { + get: jest.fn(), + }, + }, +}; describe('createTickFormatter(format, template)', () => { - it('returns a number with two decimal place by default', () => { + npStart.plugins.data = { + fieldFormats: getFieldFormatsRegistry(mockCore), + }; + + test('returns a number with two decimal place by default', () => { const fn = createTickFormatter(); - expect(fn(1.5556)).to.equal('1.56'); + expect(fn(1.5556)).toEqual('1.56'); }); - it('returns a percent with percent formatter', () => { + test('returns a percent with percent formatter', () => { const config = { 'format:percent:defaultPattern': '0.[00]%', }; const fn = createTickFormatter('percent', null, key => config[key]); - expect(fn(0.5556)).to.equal('55.56%'); + expect(fn(0.5556)).toEqual('55.56%'); }); - it('returns a byte formatted string with byte formatter', () => { + test('returns a byte formatted string with byte formatter', () => { const config = { 'format:bytes:defaultPattern': '0.0b', }; const fn = createTickFormatter('bytes', null, key => config[key]); - expect(fn(1500 ^ 10)).to.equal('1.5KB'); + expect(fn(1500 ^ 10)).toEqual('1.5KB'); }); - it('returns a custom formatted string with custom formatter', () => { + test('returns a custom formatted string with custom formatter', () => { const fn = createTickFormatter('0.0a'); - expect(fn(1500)).to.equal('1.5k'); + expect(fn(1500)).toEqual('1.5k'); }); - it('returns a located string with custom locale setting', () => { + test('returns a located string with custom locale setting', () => { const config = { 'format:number:defaultLocale': 'fr', }; const fn = createTickFormatter('0,0.0', null, key => config[key]); - expect(fn(1500)).to.equal('1 500,0'); + expect(fn(1500)).toEqual('1 500,0'); }); - it('returns a custom formatted string with custom formatter and template', () => { + test('returns a custom formatted string with custom formatter and template', () => { const fn = createTickFormatter('0.0a', '{{value}}/s'); - expect(fn(1500)).to.equal('1.5k/s'); + expect(fn(1500)).toEqual('1.5k/s'); }); - it('returns "foo" if passed a string', () => { + test('returns "foo" if passed a string', () => { const fn = createTickFormatter(); - expect(fn('foo')).to.equal('foo'); + expect(fn('foo')).toEqual('foo'); }); - it('returns value if passed a bad formatter', () => { + test('returns value if passed a bad formatter', () => { const fn = createTickFormatter('102'); - expect(fn(100)).to.equal('100'); + expect(fn(100)).toEqual('100'); }); - it('returns formatted value if passed a bad template', () => { + test('returns formatted value if passed a bad template', () => { const config = { 'format:number:defaultPattern': '0,0.[00]', }; const fn = createTickFormatter('number', '{{value', key => config[key]); - expect(fn(1.5556)).to.equal('1.56'); + expect(fn(1.5556)).toEqual('1.56'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixture.json b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixture.json deleted file mode 100644 index 178704a36372d..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixture.json +++ /dev/null @@ -1,984 +0,0 @@ -{ - "_shards": { - "failed": 0, - "successful": 5, - "total": 5 - }, - "aggregations": { - "c9b5f9c0-e403-11e6-be91-6f7688e9fac7": { - "doc_count": 128145, - "timeseries": { - "buckets": [ - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057 - }, - "doc_count": 368, - "key": 1485549090000, - "key_as_string": "2017-01-27T20:31:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.07466666666666667 - }, - "doc_count": 1106, - "key": 1485549120000, - "key_as_string": "2017-01-27T20:32:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.08033333333333335 - }, - "doc_count": 1107, - "key": 1485549150000, - "key_as_string": "2017-01-27T20:32:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.066 - }, - "doc_count": 1109, - "key": 1485549180000, - "key_as_string": "2017-01-27T20:33:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.05366666666666667 - }, - "doc_count": 1093, - "key": 1485549210000, - "key_as_string": "2017-01-27T20:33:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1086, - "key": 1485549240000, - "key_as_string": "2017-01-27T20:34:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1086, - "key": 1485549270000, - "key_as_string": "2017-01-27T20:34:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1090, - "key": 1485549300000, - "key_as_string": "2017-01-27T20:35:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1085, - "key": 1485549330000, - "key_as_string": "2017-01-27T20:35:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1082, - "key": 1485549360000, - "key_as_string": "2017-01-27T20:36:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1080, - "key": 1485549390000, - "key_as_string": "2017-01-27T20:36:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1082, - "key": 1485549420000, - "key_as_string": "2017-01-27T20:37:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1079, - "key": 1485549450000, - "key_as_string": "2017-01-27T20:37:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1080, - "key": 1485549480000, - "key_as_string": "2017-01-27T20:38:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1080, - "key": 1485549510000, - "key_as_string": "2017-01-27T20:38:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1082, - "key": 1485549540000, - "key_as_string": "2017-01-27T20:39:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.042666666666666665 - }, - "doc_count": 1079, - "key": 1485549570000, - "key_as_string": "2017-01-27T20:39:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485549600000, - "key_as_string": "2017-01-27T20:40:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1075, - "key": 1485549630000, - "key_as_string": "2017-01-27T20:40:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485549660000, - "key_as_string": "2017-01-27T20:41:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1076, - "key": 1485549690000, - "key_as_string": "2017-01-27T20:41:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485549720000, - "key_as_string": "2017-01-27T20:42:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485549750000, - "key_as_string": "2017-01-27T20:42:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1067, - "key": 1485549780000, - "key_as_string": "2017-01-27T20:43:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1065, - "key": 1485549810000, - "key_as_string": "2017-01-27T20:43:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1065, - "key": 1485549840000, - "key_as_string": "2017-01-27T20:44:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036333333333333336 - }, - "doc_count": 1062, - "key": 1485549870000, - "key_as_string": "2017-01-27T20:44:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1063, - "key": 1485549900000, - "key_as_string": "2017-01-27T20:45:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1065, - "key": 1485549930000, - "key_as_string": "2017-01-27T20:45:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1065, - "key": 1485549960000, - "key_as_string": "2017-01-27T20:46:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1069, - "key": 1485549990000, - "key_as_string": "2017-01-27T20:46:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550020000, - "key_as_string": "2017-01-27T20:47:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550050000, - "key_as_string": "2017-01-27T20:47:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550080000, - "key_as_string": "2017-01-27T20:48:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1074, - "key": 1485550110000, - "key_as_string": "2017-01-27T20:48:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1074, - "key": 1485550140000, - "key_as_string": "2017-01-27T20:49:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1074, - "key": 1485550170000, - "key_as_string": "2017-01-27T20:49:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485550200000, - "key_as_string": "2017-01-27T20:50:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485550230000, - "key_as_string": "2017-01-27T20:50:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1074, - "key": 1485550260000, - "key_as_string": "2017-01-27T20:51:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.031 - }, - "doc_count": 1074, - "key": 1485550290000, - "key_as_string": "2017-01-27T20:51:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485550320000, - "key_as_string": "2017-01-27T20:52:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1073, - "key": 1485550350000, - "key_as_string": "2017-01-27T20:52:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550380000, - "key_as_string": "2017-01-27T20:53:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485550410000, - "key_as_string": "2017-01-27T20:53:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1069, - "key": 1485550440000, - "key_as_string": "2017-01-27T20:54:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1069, - "key": 1485550470000, - "key_as_string": "2017-01-27T20:54:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1068, - "key": 1485550500000, - "key_as_string": "2017-01-27T20:55:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1067, - "key": 1485550530000, - "key_as_string": "2017-01-27T20:55:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1065, - "key": 1485550560000, - "key_as_string": "2017-01-27T20:56:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1069, - "key": 1485550590000, - "key_as_string": "2017-01-27T20:56:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1068, - "key": 1485550620000, - "key_as_string": "2017-01-27T20:57:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550650000, - "key_as_string": "2017-01-27T20:57:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1068, - "key": 1485550680000, - "key_as_string": "2017-01-27T20:58:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1071, - "key": 1485550710000, - "key_as_string": "2017-01-27T20:58:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1074, - "key": 1485550740000, - "key_as_string": "2017-01-27T20:59:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1074, - "key": 1485550770000, - "key_as_string": "2017-01-27T20:59:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1074, - "key": 1485550800000, - "key_as_string": "2017-01-27T21:00:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1076, - "key": 1485550830000, - "key_as_string": "2017-01-27T21:00:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1078, - "key": 1485550860000, - "key_as_string": "2017-01-27T21:01:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1077, - "key": 1485550890000, - "key_as_string": "2017-01-27T21:01:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550920000, - "key_as_string": "2017-01-27T21:02:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550950000, - "key_as_string": "2017-01-27T21:02:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1073, - "key": 1485550980000, - "key_as_string": "2017-01-27T21:03:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551010000, - "key_as_string": "2017-01-27T21:03:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1069, - "key": 1485551040000, - "key_as_string": "2017-01-27T21:04:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1068, - "key": 1485551070000, - "key_as_string": "2017-01-27T21:04:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1075, - "key": 1485551100000, - "key_as_string": "2017-01-27T21:05:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485551130000, - "key_as_string": "2017-01-27T21:05:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485551160000, - "key_as_string": "2017-01-27T21:06:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551190000, - "key_as_string": "2017-01-27T21:06:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1075, - "key": 1485551220000, - "key_as_string": "2017-01-27T21:07:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1071, - "key": 1485551250000, - "key_as_string": "2017-01-27T21:07:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04733333333333334 - }, - "doc_count": 1081, - "key": 1485551280000, - "key_as_string": "2017-01-27T21:08:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.044333333333333336 - }, - "doc_count": 1078, - "key": 1485551310000, - "key_as_string": "2017-01-27T21:08:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1079, - "key": 1485551340000, - "key_as_string": "2017-01-27T21:09:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1077, - "key": 1485551370000, - "key_as_string": "2017-01-27T21:09:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1077, - "key": 1485551400000, - "key_as_string": "2017-01-27T21:10:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1075, - "key": 1485551430000, - "key_as_string": "2017-01-27T21:10:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038000000000000006 - }, - "doc_count": 1078, - "key": 1485551460000, - "key_as_string": "2017-01-27T21:11:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037 - }, - "doc_count": 1074, - "key": 1485551490000, - "key_as_string": "2017-01-27T21:11:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036666666666666674 - }, - "doc_count": 1074, - "key": 1485551520000, - "key_as_string": "2017-01-27T21:12:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1076, - "key": 1485551550000, - "key_as_string": "2017-01-27T21:12:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03733333333333333 - }, - "doc_count": 1075, - "key": 1485551580000, - "key_as_string": "2017-01-27T21:13:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1077, - "key": 1485551610000, - "key_as_string": "2017-01-27T21:13:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1080, - "key": 1485551640000, - "key_as_string": "2017-01-27T21:14:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057666666666666665 - }, - "doc_count": 1080, - "key": 1485551670000, - "key_as_string": "2017-01-27T21:14:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.045000000000000005 - }, - "doc_count": 1080, - "key": 1485551700000, - "key_as_string": "2017-01-27T21:15:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1080, - "key": 1485551730000, - "key_as_string": "2017-01-27T21:15:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551760000, - "key_as_string": "2017-01-27T21:16:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038 - }, - "doc_count": 1080, - "key": 1485551790000, - "key_as_string": "2017-01-27T21:16:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551820000, - "key_as_string": "2017-01-27T21:17:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1080, - "key": 1485551850000, - "key_as_string": "2017-01-27T21:17:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1080, - "key": 1485551880000, - "key_as_string": "2017-01-27T21:18:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1080, - "key": 1485551910000, - "key_as_string": "2017-01-27T21:18:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03766666666666666 - }, - "doc_count": 1080, - "key": 1485551940000, - "key_as_string": "2017-01-27T21:19:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485551970000, - "key_as_string": "2017-01-27T21:19:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1077, - "key": 1485552000000, - "key_as_string": "2017-01-27T21:20:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1077, - "key": 1485552030000, - "key_as_string": "2017-01-27T21:20:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.029666666666666664 - }, - "doc_count": 1077, - "key": 1485552060000, - "key_as_string": "2017-01-27T21:21:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.02766666666666667 - }, - "doc_count": 1077, - "key": 1485552090000, - "key_as_string": "2017-01-27T21:21:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.028 - }, - "doc_count": 1077, - "key": 1485552120000, - "key_as_string": "2017-01-27T21:22:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.030666666666666665 - }, - "doc_count": 1077, - "key": 1485552150000, - "key_as_string": "2017-01-27T21:22:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1083, - "key": 1485552180000, - "key_as_string": "2017-01-27T21:23:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1083, - "key": 1485552210000, - "key_as_string": "2017-01-27T21:23:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.041 - }, - "doc_count": 1082, - "key": 1485552240000, - "key_as_string": "2017-01-27T21:24:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1087, - "key": 1485552270000, - "key_as_string": "2017-01-27T21:24:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1083, - "key": 1485552300000, - "key_as_string": "2017-01-27T21:25:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03933333333333333 - }, - "doc_count": 1083, - "key": 1485552330000, - "key_as_string": "2017-01-27T21:25:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1083, - "key": 1485552360000, - "key_as_string": "2017-01-27T21:26:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1083, - "key": 1485552390000, - "key_as_string": "2017-01-27T21:26:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1082, - "key": 1485552420000, - "key_as_string": "2017-01-27T21:27:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333334 - }, - "doc_count": 1083, - "key": 1485552450000, - "key_as_string": "2017-01-27T21:27:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1083, - "key": 1485552480000, - "key_as_string": "2017-01-27T21:28:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1084, - "key": 1485552510000, - "key_as_string": "2017-01-27T21:28:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1083, - "key": 1485552540000, - "key_as_string": "2017-01-27T21:29:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1083, - "key": 1485552570000, - "key_as_string": "2017-01-27T21:29:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04466666666666667 - }, - "doc_count": 1083, - "key": 1485552600000, - "key_as_string": "2017-01-27T21:30:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1083, - "key": 1485552630000, - "key_as_string": "2017-01-27T21:30:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.0505 - }, - "doc_count": 722, - "key": 1485552660000, - "key_as_string": "2017-01-27T21:31:00.000Z" - } - ] - } - } - }, - "hits": { - "hits": [], - "max_score": 0, - "total": 128145 - }, - "status": 200, - "timed_out": false, - "took": 28 -} diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixtures/std_metric_fixture.json b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixtures/std_metric_fixture.json deleted file mode 100644 index d587e48b31881..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/fixtures/std_metric_fixture.json +++ /dev/null @@ -1,985 +0,0 @@ -{ - "_shards": { - "failed": 0, - "successful": 5, - "total": 5 - }, - "aggregations": { - "c9b5f9c0-e403-11e6-be91-6f7688e9fac7": { - "doc_count": 128145, - "timeseries": { - "buckets": [ - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057 - }, - "doc_count": 368, - "key": 1485549090000, - "key_as_string": "2017-01-27T20:31:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.07466666666666667 - }, - "doc_count": 1106, - "key": 1485549120000, - "key_as_string": "2017-01-27T20:32:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.08033333333333335 - }, - "doc_count": 1107, - "key": 1485549150000, - "key_as_string": "2017-01-27T20:32:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.066 - }, - "doc_count": 1109, - "key": 1485549180000, - "key_as_string": "2017-01-27T20:33:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.05366666666666667 - }, - "doc_count": 1093, - "key": 1485549210000, - "key_as_string": "2017-01-27T20:33:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1086, - "key": 1485549240000, - "key_as_string": "2017-01-27T20:34:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1086, - "key": 1485549270000, - "key_as_string": "2017-01-27T20:34:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1090, - "key": 1485549300000, - "key_as_string": "2017-01-27T20:35:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1085, - "key": 1485549330000, - "key_as_string": "2017-01-27T20:35:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1082, - "key": 1485549360000, - "key_as_string": "2017-01-27T20:36:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1080, - "key": 1485549390000, - "key_as_string": "2017-01-27T20:36:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1082, - "key": 1485549420000, - "key_as_string": "2017-01-27T20:37:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1079, - "key": 1485549450000, - "key_as_string": "2017-01-27T20:37:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1080, - "key": 1485549480000, - "key_as_string": "2017-01-27T20:38:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1080, - "key": 1485549510000, - "key_as_string": "2017-01-27T20:38:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1082, - "key": 1485549540000, - "key_as_string": "2017-01-27T20:39:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.042666666666666665 - }, - "doc_count": 1079, - "key": 1485549570000, - "key_as_string": "2017-01-27T20:39:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485549600000, - "key_as_string": "2017-01-27T20:40:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1075, - "key": 1485549630000, - "key_as_string": "2017-01-27T20:40:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485549660000, - "key_as_string": "2017-01-27T20:41:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1076, - "key": 1485549690000, - "key_as_string": "2017-01-27T20:41:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485549720000, - "key_as_string": "2017-01-27T20:42:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485549750000, - "key_as_string": "2017-01-27T20:42:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1067, - "key": 1485549780000, - "key_as_string": "2017-01-27T20:43:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036000000000000004 - }, - "doc_count": 1065, - "key": 1485549810000, - "key_as_string": "2017-01-27T20:43:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1065, - "key": 1485549840000, - "key_as_string": "2017-01-27T20:44:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036333333333333336 - }, - "doc_count": 1062, - "key": 1485549870000, - "key_as_string": "2017-01-27T20:44:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1063, - "key": 1485549900000, - "key_as_string": "2017-01-27T20:45:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1065, - "key": 1485549930000, - "key_as_string": "2017-01-27T20:45:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1065, - "key": 1485549960000, - "key_as_string": "2017-01-27T20:46:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1069, - "key": 1485549990000, - "key_as_string": "2017-01-27T20:46:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550020000, - "key_as_string": "2017-01-27T20:47:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550050000, - "key_as_string": "2017-01-27T20:47:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1068, - "key": 1485550080000, - "key_as_string": "2017-01-27T20:48:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1074, - "key": 1485550110000, - "key_as_string": "2017-01-27T20:48:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1074, - "key": 1485550140000, - "key_as_string": "2017-01-27T20:49:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1074, - "key": 1485550170000, - "key_as_string": "2017-01-27T20:49:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485550200000, - "key_as_string": "2017-01-27T20:50:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1077, - "key": 1485550230000, - "key_as_string": "2017-01-27T20:50:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1074, - "key": 1485550260000, - "key_as_string": "2017-01-27T20:51:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.031 - }, - "doc_count": 1074, - "key": 1485550290000, - "key_as_string": "2017-01-27T20:51:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1072, - "key": 1485550320000, - "key_as_string": "2017-01-27T20:52:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.033 - }, - "doc_count": 1073, - "key": 1485550350000, - "key_as_string": "2017-01-27T20:52:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550380000, - "key_as_string": "2017-01-27T20:53:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485550410000, - "key_as_string": "2017-01-27T20:53:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1069, - "key": 1485550440000, - "key_as_string": "2017-01-27T20:54:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1069, - "key": 1485550470000, - "key_as_string": "2017-01-27T20:54:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1068, - "key": 1485550500000, - "key_as_string": "2017-01-27T20:55:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1067, - "key": 1485550530000, - "key_as_string": "2017-01-27T20:55:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1065, - "key": 1485550560000, - "key_as_string": "2017-01-27T20:56:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1069, - "key": 1485550590000, - "key_as_string": "2017-01-27T20:56:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03166666666666667 - }, - "doc_count": 1068, - "key": 1485550620000, - "key_as_string": "2017-01-27T20:57:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1068, - "key": 1485550650000, - "key_as_string": "2017-01-27T20:57:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1068, - "key": 1485550680000, - "key_as_string": "2017-01-27T20:58:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1071, - "key": 1485550710000, - "key_as_string": "2017-01-27T20:58:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1074, - "key": 1485550740000, - "key_as_string": "2017-01-27T20:59:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1074, - "key": 1485550770000, - "key_as_string": "2017-01-27T20:59:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1074, - "key": 1485550800000, - "key_as_string": "2017-01-27T21:00:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.032 - }, - "doc_count": 1076, - "key": 1485550830000, - "key_as_string": "2017-01-27T21:00:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1078, - "key": 1485550860000, - "key_as_string": "2017-01-27T21:01:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1077, - "key": 1485550890000, - "key_as_string": "2017-01-27T21:01:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550920000, - "key_as_string": "2017-01-27T21:02:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1071, - "key": 1485550950000, - "key_as_string": "2017-01-27T21:02:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1073, - "key": 1485550980000, - "key_as_string": "2017-01-27T21:03:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551010000, - "key_as_string": "2017-01-27T21:03:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03466666666666667 - }, - "doc_count": 1069, - "key": 1485551040000, - "key_as_string": "2017-01-27T21:04:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1068, - "key": 1485551070000, - "key_as_string": "2017-01-27T21:04:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1075, - "key": 1485551100000, - "key_as_string": "2017-01-27T21:05:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1074, - "key": 1485551130000, - "key_as_string": "2017-01-27T21:05:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1073, - "key": 1485551160000, - "key_as_string": "2017-01-27T21:06:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1071, - "key": 1485551190000, - "key_as_string": "2017-01-27T21:06:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1075, - "key": 1485551220000, - "key_as_string": "2017-01-27T21:07:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03133333333333333 - }, - "doc_count": 1071, - "key": 1485551250000, - "key_as_string": "2017-01-27T21:07:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04733333333333334 - }, - "doc_count": 1081, - "key": 1485551280000, - "key_as_string": "2017-01-27T21:08:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.044333333333333336 - }, - "doc_count": 1078, - "key": 1485551310000, - "key_as_string": "2017-01-27T21:08:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037000000000000005 - }, - "doc_count": 1079, - "key": 1485551340000, - "key_as_string": "2017-01-27T21:09:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1077, - "key": 1485551370000, - "key_as_string": "2017-01-27T21:09:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1077, - "key": 1485551400000, - "key_as_string": "2017-01-27T21:10:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1075, - "key": 1485551430000, - "key_as_string": "2017-01-27T21:10:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038000000000000006 - }, - "doc_count": 1078, - "key": 1485551460000, - "key_as_string": "2017-01-27T21:11:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037 - }, - "doc_count": 1074, - "key": 1485551490000, - "key_as_string": "2017-01-27T21:11:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.036666666666666674 - }, - "doc_count": 1074, - "key": 1485551520000, - "key_as_string": "2017-01-27T21:12:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1076, - "key": 1485551550000, - "key_as_string": "2017-01-27T21:12:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03733333333333333 - }, - "doc_count": 1075, - "key": 1485551580000, - "key_as_string": "2017-01-27T21:13:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04533333333333334 - }, - "doc_count": 1077, - "key": 1485551610000, - "key_as_string": "2017-01-27T21:13:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1080, - "key": 1485551640000, - "key_as_string": "2017-01-27T21:14:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.057666666666666665 - }, - "doc_count": 1080, - "key": 1485551670000, - "key_as_string": "2017-01-27T21:14:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.045000000000000005 - }, - "doc_count": 1080, - "key": 1485551700000, - "key_as_string": "2017-01-27T21:15:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1080, - "key": 1485551730000, - "key_as_string": "2017-01-27T21:15:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551760000, - "key_as_string": "2017-01-27T21:16:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.038 - }, - "doc_count": 1080, - "key": 1485551790000, - "key_as_string": "2017-01-27T21:16:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1080, - "key": 1485551820000, - "key_as_string": "2017-01-27T21:17:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1080, - "key": 1485551850000, - "key_as_string": "2017-01-27T21:17:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03266666666666667 - }, - "doc_count": 1080, - "key": 1485551880000, - "key_as_string": "2017-01-27T21:18:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1080, - "key": 1485551910000, - "key_as_string": "2017-01-27T21:18:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03766666666666666 - }, - "doc_count": 1080, - "key": 1485551940000, - "key_as_string": "2017-01-27T21:19:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034 - }, - "doc_count": 1076, - "key": 1485551970000, - "key_as_string": "2017-01-27T21:19:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.034333333333333334 - }, - "doc_count": 1077, - "key": 1485552000000, - "key_as_string": "2017-01-27T21:20:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03366666666666667 - }, - "doc_count": 1077, - "key": 1485552030000, - "key_as_string": "2017-01-27T21:20:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.029666666666666664 - }, - "doc_count": 1077, - "key": 1485552060000, - "key_as_string": "2017-01-27T21:21:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.02766666666666667 - }, - "doc_count": 1077, - "key": 1485552090000, - "key_as_string": "2017-01-27T21:21:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.028 - }, - "doc_count": 1077, - "key": 1485552120000, - "key_as_string": "2017-01-27T21:22:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.030666666666666665 - }, - "doc_count": 1077, - "key": 1485552150000, - "key_as_string": "2017-01-27T21:22:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03833333333333334 - }, - "doc_count": 1083, - "key": 1485552180000, - "key_as_string": "2017-01-27T21:23:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04966666666666667 - }, - "doc_count": 1083, - "key": 1485552210000, - "key_as_string": "2017-01-27T21:23:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.041 - }, - "doc_count": 1082, - "key": 1485552240000, - "key_as_string": "2017-01-27T21:24:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037333333333333336 - }, - "doc_count": 1087, - "key": 1485552270000, - "key_as_string": "2017-01-27T21:24:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.039 - }, - "doc_count": 1083, - "key": 1485552300000, - "key_as_string": "2017-01-27T21:25:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03933333333333333 - }, - "doc_count": 1083, - "key": 1485552330000, - "key_as_string": "2017-01-27T21:25:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.037666666666666675 - }, - "doc_count": 1083, - "key": 1485552360000, - "key_as_string": "2017-01-27T21:26:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1083, - "key": 1485552390000, - "key_as_string": "2017-01-27T21:26:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03866666666666666 - }, - "doc_count": 1082, - "key": 1485552420000, - "key_as_string": "2017-01-27T21:27:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333334 - }, - "doc_count": 1083, - "key": 1485552450000, - "key_as_string": "2017-01-27T21:27:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04 - }, - "doc_count": 1083, - "key": 1485552480000, - "key_as_string": "2017-01-27T21:28:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04033333333333333 - }, - "doc_count": 1084, - "key": 1485552510000, - "key_as_string": "2017-01-27T21:28:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03333333333333333 - }, - "doc_count": 1083, - "key": 1485552540000, - "key_as_string": "2017-01-27T21:29:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03566666666666667 - }, - "doc_count": 1083, - "key": 1485552570000, - "key_as_string": "2017-01-27T21:29:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.04466666666666667 - }, - "doc_count": 1083, - "key": 1485552600000, - "key_as_string": "2017-01-27T21:30:00.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.03233333333333333 - }, - "doc_count": 1083, - "key": 1485552630000, - "key_as_string": "2017-01-27T21:30:30.000Z" - }, - { - "c9b5f9c1-e403-11e6-be91-6f7688e9fac7": { - "value": 0.0505 - }, - "doc_count": 722, - "key": 1485552660000, - "key_as_string": "2017-01-27T21:31:00.000Z" - } - ] - } - } - }, - "hits": { - "hits": [], - "max_score": 0, - "total": 128145 - }, - "status": 200, - "timed_out": false, - "took": 28 -} - diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/bucket_transform.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/bucket_transform.js deleted file mode 100644 index 1414435017c86..0000000000000 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/bucket_transform.js +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { expect } from 'chai'; -import { bucketTransform } from '../../helpers/bucket_transform'; - -describe('bucketTransform', () => { - describe('count', () => { - it('returns count agg', () => { - const metric = { id: 'test', type: 'count' }; - const fn = bucketTransform.count; - expect(fn(metric)).to.eql({ - bucket_script: { - buckets_path: { count: '_count' }, - script: { source: 'count * 1', lang: 'expression' }, - gap_policy: 'skip', - }, - }); - }); - }); - - describe('std metric', () => { - ['avg', 'max', 'min', 'sum', 'cardinality', 'value_count'].forEach(type => { - it(`returns ${type} agg`, () => { - const metric = { id: 'test', type: type, field: 'cpu.pct' }; - const fn = bucketTransform[type]; - const result = {}; - result[type] = { field: 'cpu.pct' }; - expect(fn(metric)).to.eql(result); - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.avg({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.avg({ id: 'test', type: 'avg' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('extended stats', () => { - ['std_deviation', 'variance', 'sum_of_squares'].forEach(type => { - it(`returns ${type} agg`, () => { - const fn = bucketTransform[type]; - const metric = { id: 'test', type: type, field: 'cpu.pct' }; - expect(fn(metric)).to.eql({ extended_stats: { field: 'cpu.pct' } }); - }); - }); - - it('returns std_deviation agg with sigma', () => { - const fn = bucketTransform.std_deviation; - const metric = { - id: 'test', - type: 'std_deviation', - field: 'cpu.pct', - sigma: 2, - }; - expect(fn(metric)).to.eql({ - extended_stats: { field: 'cpu.pct', sigma: 2 }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.std_deviation({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.std_deviation({ id: 'test', type: 'avg' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('percentiles', () => { - it('returns percentiles agg', () => { - const metric = { - id: 'test', - type: 'percentile', - field: 'cpu.pct', - percentiles: [ - { value: 50, mode: 'line' }, - { value: 10, mode: 'band', percentile: 90 }, - ], - }; - const fn = bucketTransform.percentile; - expect(fn(metric)).to.eql({ - percentiles: { - field: 'cpu.pct', - percents: [50, 10, 90], - }, - }); - }); - - it('define a default 0 value if it was not provided', () => { - const metric = { - id: 'test', - type: 'percentile', - field: 'cpu.pct', - percentiles: [ - { value: 50, mode: 'line' }, - { mode: 'line' }, - { value: undefined, mode: 'line' }, - { value: '', mode: 'line' }, - { value: null, mode: 'line' }, - { value: 0, mode: 'line' }, - ], - }; - expect(bucketTransform.percentile(metric)).to.eql({ - percentiles: { - field: 'cpu.pct', - percents: [50, 0, 0, 0, 0, 0], - }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => - bucketTransform.percentile({ - id: 'test', - field: 'cpu.pct', - percentiles: [{ value: 50, mode: 'line' }], - }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => - bucketTransform.percentile({ - id: 'test', - type: 'avg', - percentiles: [{ value: 50, mode: 'line' }], - }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - - it('throws error if percentiles is missing', () => { - const run = () => - bucketTransform.percentile({ - id: 'test', - type: 'avg', - field: 'cpu.pct', - }); - expect(run).to.throw(Error, 'Metric missing percentiles'); - }); - }); - - describe('derivative', () => { - it('returns derivative agg with defaults', () => { - const metric = { - id: '2', - type: 'derivative', - field: '1', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.derivative; - expect(fn(metric, metrics, '10s')).is.eql({ - derivative: { - buckets_path: '1', - gap_policy: 'skip', - unit: '10s', - }, - }); - }); - - it('returns derivative agg with unit', () => { - const metric = { - id: '2', - type: 'derivative', - field: '1', - unit: '1s', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.derivative; - expect(fn(metric, metrics, '10s')).is.eql({ - derivative: { - buckets_path: '1', - gap_policy: 'skip', - unit: '1s', - }, - }); - }); - - it('returns derivative agg with gap_policy', () => { - const metric = { - id: '2', - type: 'derivative', - field: '1', - gap_policy: 'zero_fill', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.derivative; - expect(fn(metric, metrics, '10s')).is.eql({ - derivative: { - buckets_path: '1', - gap_policy: 'zero_fill', - unit: '10s', - }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.derivative({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.derivative({ id: 'test', type: 'derivative' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('serial_diff', () => { - it('returns serial_diff agg with defaults', () => { - const metric = { - id: '2', - type: 'serial_diff', - field: '1', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.serial_diff; - expect(fn(metric, metrics)).is.eql({ - serial_diff: { - buckets_path: '1', - gap_policy: 'skip', - lag: 1, - }, - }); - }); - - it('returns serial_diff agg with lag', () => { - const metric = { - id: '2', - type: 'serial_diff', - field: '1', - lag: 10, - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.serial_diff; - expect(fn(metric, metrics)).is.eql({ - serial_diff: { - buckets_path: '1', - gap_policy: 'skip', - lag: 10, - }, - }); - }); - - it('returns serial_diff agg with gap_policy', () => { - const metric = { - id: '2', - type: 'serial_diff', - field: '1', - gap_policy: 'zero_fill', - }; - const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.serial_diff; - expect(fn(metric, metrics)).is.eql({ - serial_diff: { - buckets_path: '1', - gap_policy: 'zero_fill', - lag: 1, - }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.serial_diff({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.serial_diff({ id: 'test', type: 'serial_diff' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('cumulative_sum', () => { - it('returns cumulative_sum agg', () => { - const metric = { id: '2', type: 'cumulative_sum', field: '1' }; - const metrics = [{ id: '1', type: 'sum', field: 'cpu.pct' }, metric]; - const fn = bucketTransform.cumulative_sum; - expect(fn(metric, metrics, '10s')).is.eql({ - cumulative_sum: { buckets_path: '1' }, - }); - }); - - it('throws error if type is missing', () => { - const run = () => bucketTransform.cumulative_sum({ id: 'test', field: 'cpu.pct' }); - expect(run).to.throw(Error, 'Metric missing type'); - }); - - it('throws error if field is missing', () => { - const run = () => bucketTransform.cumulative_sum({ id: 'test', type: 'cumulative_sum' }); - expect(run).to.throw(Error, 'Metric missing field'); - }); - }); - - describe('calculation', () => { - it('returns calculation(bucket_script)', () => { - const metric = { - id: '2', - type: 'calculation', - script: 'params.idle != null ? 1 - params.idle : 0', - variables: [{ name: 'idle', field: '1' }], - }; - const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; - const fn = bucketTransform.calculation; - expect(fn(metric, metrics, '10s')).is.eql({ - bucket_script: { - buckets_path: { - idle: '1', - }, - gap_policy: 'skip', - script: { - source: 'params.idle != null ? 1 - params.idle : 0', - lang: 'painless', - params: { - _interval: 10000, - }, - }, - }, - }); - }); - - it('throws error if variables is missing', () => { - const run = () => - bucketTransform.calculation({ - id: 'test', - type: 'calculation', - script: 'params.idle != null ? 1 - params.idle : null', - }); - expect(run).to.throw(Error, 'Metric missing variables'); - }); - - it('throws error if script is missing', () => { - const run = () => - bucketTransform.calculation({ - id: 'test', - type: 'calculation', - variables: [{ field: '1', name: 'idle' }], - }); - expect(run).to.throw(Error, 'Metric missing script'); - }); - }); - - describe('positive_only', () => { - it('returns bucket_script', () => { - const metric = { - id: '2', - type: 'positive_only', - field: '1', - }; - const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; - const fn = bucketTransform.positive_only; - expect(fn(metric, metrics, '10s')).is.eql({ - bucket_script: { - buckets_path: { - value: '1', - }, - gap_policy: 'skip', - script: { - source: 'params.value > 0.0 ? params.value : 0.0', - lang: 'painless', - }, - }, - }); - }); - }); -}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/build_processor_function.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.test.ts similarity index 58% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/build_processor_function.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.test.ts index 10ad9e467610e..cf5244f8e5a7d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/build_processor_function.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.test.ts @@ -17,43 +17,41 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { buildProcessorFunction } from '../build_processor_function'; +import { buildProcessorFunction } from './build_processor_function'; describe('buildProcessorFunction(chain, ...args)', () => { const req = {}; const panel = {}; const series = {}; - it('should call each processor', () => { - const first = sinon.spy(() => next => doc => next(doc)); - const second = sinon.spy(() => next => doc => next(doc)); + test('should call each processor', () => { + const first = jest.fn(() => (next: any) => (doc: any) => next(doc)); + const second = jest.fn(() => (next: any) => (doc: any) => next(doc)); buildProcessorFunction([first, second], req, panel, series); - expect(first.calledOnce).to.equal(true); - expect(second.calledOnce).to.equal(true); + expect(first.mock.calls.length).toEqual(1); + expect(second.mock.calls.length).toEqual(1); }); - it('should chain each processor', () => { - const first = sinon.spy(next => doc => next(doc)); - const second = sinon.spy(next => doc => next(doc)); + test('should chain each processor', () => { + const first = jest.fn(() => (next: any) => (doc: any) => next(doc)); + const second = jest.fn(() => (next: any) => (doc: any) => next(doc)); buildProcessorFunction([() => first, () => second], req, panel, series); - expect(first.calledOnce).to.equal(true); - expect(second.calledOnce).to.equal(true); + expect(first.mock.calls.length).toEqual(1); + expect(second.mock.calls.length).toEqual(1); }); - it('should next of each processor', () => { - const first = sinon.spy(); - const second = sinon.spy(); + test('should next of each processor', () => { + const first = jest.fn(); + const second = jest.fn(); const fn = buildProcessorFunction( [ - () => next => doc => { + () => (next: any) => (doc: any) => { first(); next(doc); }, - () => next => doc => { + () => (next: any) => (doc: any) => { second(); next(doc); }, @@ -63,7 +61,7 @@ describe('buildProcessorFunction(chain, ...args)', () => { series ); fn({}); - expect(first.calledOnce).to.equal(true); - expect(second.calledOnce).to.equal(true); + expect(first.mock.calls.length).toEqual(1); + expect(second.mock.calls.length).toEqual(1); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.ts similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.ts index 5896a089bafe2..b898f4dbf7a5e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/build_processor_function.ts @@ -17,11 +17,9 @@ * under the License. */ -export function buildProcessorFunction(chain, ...args) { +export function buildProcessorFunction(chain: any[], ...args: any) { return chain.reduceRight( - (next, fn) => { - return fn(...args)(next); - }, - doc => doc + (next, fn) => fn(...args)(next), + (doc: any) => doc ); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/get_interval_and_timefield.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js similarity index 79% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/get_interval_and_timefield.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js index dc2c2b2cfb1f2..f3e15f2fc65b6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/get_interval_and_timefield.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js @@ -17,27 +17,26 @@ * under the License. */ -import { expect } from 'chai'; -import { getIntervalAndTimefield } from '../get_interval_and_timefield'; +import { getIntervalAndTimefield } from './get_interval_and_timefield'; describe('getIntervalAndTimefield(panel, series)', () => { - it('returns the panel interval and timefield', () => { + test('returns the panel interval and timefield', () => { const panel = { time_field: '@timestamp', interval: 'auto' }; const series = {}; - expect(getIntervalAndTimefield(panel, series)).to.eql({ + expect(getIntervalAndTimefield(panel, series)).toEqual({ timeField: '@timestamp', interval: 'auto', }); }); - it('returns the series interval and timefield', () => { + test('returns the series interval and timefield', () => { const panel = { time_field: '@timestamp', interval: 'auto' }; const series = { override_index_pattern: true, series_interval: '1m', series_time_field: 'time', }; - expect(getIntervalAndTimefield(panel, series)).to.eql({ + expect(getIntervalAndTimefield(panel, series)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js index 3def18997863e..db0e8fa3d6bb9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/bucket_transform.test.js @@ -94,4 +94,371 @@ describe('src/legacy/core_plugins/metrics/server/lib/vis_data/helpers/bucket_tra }); }); }); + + describe('bucketTransform additional', () => { + describe('count', () => { + test('returns count agg', () => { + const metric = { id: 'test', type: 'count' }; + const fn = bucketTransform.count; + expect(fn(metric)).toEqual({ + bucket_script: { + buckets_path: { count: '_count' }, + script: { source: 'count * 1', lang: 'expression' }, + gap_policy: 'skip', + }, + }); + }); + }); + + describe('std metric', () => { + ['avg', 'max', 'min', 'sum', 'cardinality', 'value_count'].forEach(type => { + test(`returns ${type} agg`, () => { + const metric = { id: 'test', type: type, field: 'cpu.pct' }; + const fn = bucketTransform[type]; + const result = {}; + result[type] = { field: 'cpu.pct' }; + expect(fn(metric)).toEqual(result); + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.avg({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.avg({ id: 'test', type: 'avg' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('extended stats', () => { + ['std_deviation', 'variance', 'sum_of_squares'].forEach(type => { + test(`returns ${type} agg`, () => { + const fn = bucketTransform[type]; + const metric = { id: 'test', type: type, field: 'cpu.pct' }; + expect(fn(metric)).toEqual({ extended_stats: { field: 'cpu.pct' } }); + }); + }); + + test('returns std_deviation agg with sigma', () => { + const fn = bucketTransform.std_deviation; + const metric = { + id: 'test', + type: 'std_deviation', + field: 'cpu.pct', + sigma: 2, + }; + expect(fn(metric)).toEqual({ + extended_stats: { field: 'cpu.pct', sigma: 2 }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.std_deviation({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.std_deviation({ id: 'test', type: 'avg' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('percentiles', () => { + test('returns percentiles agg', () => { + const metric = { + id: 'test', + type: 'percentile', + field: 'cpu.pct', + percentiles: [ + { value: 50, mode: 'line' }, + { value: 10, mode: 'band', percentile: 90 }, + ], + }; + const fn = bucketTransform.percentile; + expect(fn(metric)).toEqual({ + percentiles: { + field: 'cpu.pct', + percents: [50, 10, 90], + }, + }); + }); + + test('define a default 0 value if it was not provided', () => { + const metric = { + id: 'test', + type: 'percentile', + field: 'cpu.pct', + percentiles: [ + { value: 50, mode: 'line' }, + { mode: 'line' }, + { value: undefined, mode: 'line' }, + { value: '', mode: 'line' }, + { value: null, mode: 'line' }, + { value: 0, mode: 'line' }, + ], + }; + expect(bucketTransform.percentile(metric)).toEqual({ + percentiles: { + field: 'cpu.pct', + percents: [50, 0, 0, 0, 0, 0], + }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => + bucketTransform.percentile({ + id: 'test', + field: 'cpu.pct', + percentiles: [{ value: 50, mode: 'line' }], + }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => + bucketTransform.percentile({ + id: 'test', + type: 'avg', + percentiles: [{ value: 50, mode: 'line' }], + }); + expect(run).toThrow(new Error('Metric missing field')); + }); + + test('throws error if percentiles is missing', () => { + const run = () => + bucketTransform.percentile({ + id: 'test', + type: 'avg', + field: 'cpu.pct', + }); + expect(run).toThrow(new Error('Metric missing percentiles')); + }); + }); + + describe('derivative', () => { + test('returns derivative agg with defaults', () => { + const metric = { + id: '2', + type: 'derivative', + field: '1', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.derivative; + expect(fn(metric, metrics, '10s')).toEqual({ + derivative: { + buckets_path: '1', + gap_policy: 'skip', + unit: '10s', + }, + }); + }); + + test('returns derivative agg with unit', () => { + const metric = { + id: '2', + type: 'derivative', + field: '1', + unit: '1s', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.derivative; + expect(fn(metric, metrics, '10s')).toEqual({ + derivative: { + buckets_path: '1', + gap_policy: 'skip', + unit: '1s', + }, + }); + }); + + test('returns derivative agg with gap_policy', () => { + const metric = { + id: '2', + type: 'derivative', + field: '1', + gap_policy: 'zero_fill', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.derivative; + expect(fn(metric, metrics, '10s')).toEqual({ + derivative: { + buckets_path: '1', + gap_policy: 'zero_fill', + unit: '10s', + }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.derivative({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.derivative({ id: 'test', type: 'derivative' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('serial_diff', () => { + test('returns serial_diff agg with defaults', () => { + const metric = { + id: '2', + type: 'serial_diff', + field: '1', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.serial_diff; + expect(fn(metric, metrics)).toEqual({ + serial_diff: { + buckets_path: '1', + gap_policy: 'skip', + lag: 1, + }, + }); + }); + + test('returns serial_diff agg with lag', () => { + const metric = { + id: '2', + type: 'serial_diff', + field: '1', + lag: 10, + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.serial_diff; + expect(fn(metric, metrics)).toEqual({ + serial_diff: { + buckets_path: '1', + gap_policy: 'skip', + lag: 10, + }, + }); + }); + + test('returns serial_diff agg with gap_policy', () => { + const metric = { + id: '2', + type: 'serial_diff', + field: '1', + gap_policy: 'zero_fill', + }; + const metrics = [{ id: '1', type: 'max', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.serial_diff; + expect(fn(metric, metrics)).toEqual({ + serial_diff: { + buckets_path: '1', + gap_policy: 'zero_fill', + lag: 1, + }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.serial_diff({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.serial_diff({ id: 'test', type: 'serial_diff' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('cumulative_sum', () => { + test('returns cumulative_sum agg', () => { + const metric = { id: '2', type: 'cumulative_sum', field: '1' }; + const metrics = [{ id: '1', type: 'sum', field: 'cpu.pct' }, metric]; + const fn = bucketTransform.cumulative_sum; + expect(fn(metric, metrics, '10s')).toEqual({ + cumulative_sum: { buckets_path: '1' }, + }); + }); + + test('throws error if type is missing', () => { + const run = () => bucketTransform.cumulative_sum({ id: 'test', field: 'cpu.pct' }); + expect(run).toThrow(new Error('Metric missing type')); + }); + + test('throws error if field is missing', () => { + const run = () => bucketTransform.cumulative_sum({ id: 'test', type: 'cumulative_sum' }); + expect(run).toThrow(new Error('Metric missing field')); + }); + }); + + describe('calculation', () => { + test('returns calculation(bucket_script)', () => { + const metric = { + id: '2', + type: 'calculation', + script: 'params.idle != null ? 1 - params.idle : 0', + variables: [{ name: 'idle', field: '1' }], + }; + const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; + const fn = bucketTransform.calculation; + expect(fn(metric, metrics, '10s')).toEqual({ + bucket_script: { + buckets_path: { + idle: '1', + }, + gap_policy: 'skip', + script: { + source: 'params.idle != null ? 1 - params.idle : 0', + lang: 'painless', + params: { + _interval: 10000, + }, + }, + }, + }); + }); + + test('throws error if variables is missing', () => { + const run = () => + bucketTransform.calculation({ + id: 'test', + type: 'calculation', + script: 'params.idle != null ? 1 - params.idle : null', + }); + expect(run).toThrow(new Error('Metric missing variables')); + }); + + test('throws error if script is missing', () => { + const run = () => + bucketTransform.calculation({ + id: 'test', + type: 'calculation', + variables: [{ field: '1', name: 'idle' }], + }); + expect(run).toThrow(new Error('Metric missing script')); + }); + }); + + describe('positive_only', () => { + test('returns bucket_script', () => { + const metric = { + id: '2', + type: 'positive_only', + field: '1', + }; + const metrics = [{ id: '1', type: 'avg', field: 'cpu.idle.pct' }, metric]; + const fn = bucketTransform.positive_only; + expect(fn(metric, metrics, '10s')).toEqual({ + bucket_script: { + buckets_path: { + value: '1', + }, + gap_policy: 'skip', + script: { + source: 'params.value > 0.0 ? params.value : 0.0', + lang: 'painless', + }, + }, + }); + }); + }); + }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_agg_value.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_agg_value.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js index 234953c8f4617..5f5e5ebafa560 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_agg_value.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_agg_value.test.js @@ -17,16 +17,15 @@ * under the License. */ -import { expect } from 'chai'; -import { getAggValue } from '../../helpers/get_agg_value'; +import { getAggValue } from './get_agg_value'; function testAgg(row, metric, expected) { let name = metric.type; if (metric.mode) name += `(${metric.mode})`; if (metric.percent) name += `(${metric.percent})`; - it(`it should return ${name}(${expected})`, () => { + test(`it should return ${name}(${expected})`, () => { const value = getAggValue(row, metric); - expect(value).to.eql(expected); + expect(value).toEqual(expected); }); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_bucket_size.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.test.js similarity index 54% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_bucket_size.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.test.js index e62cfff1083cf..99bef2de6b72d 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_bucket_size.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { getBucketSize } from '../../helpers/get_bucket_size'; +import { getBucketSize } from './get_bucket_size'; describe('getBucketSize', () => { const req = { @@ -30,39 +29,39 @@ describe('getBucketSize', () => { }, }; - it('returns auto calculated buckets', () => { + test('returns auto calculated buckets', () => { const result = getBucketSize(req, 'auto'); - expect(result).to.have.property('bucketSize', 30); - expect(result).to.have.property('intervalString', '30s'); + expect(result).toHaveProperty('bucketSize', 30); + expect(result).toHaveProperty('intervalString', '30s'); }); - it('returns overridden buckets (1s)', () => { + test('returns overridden buckets (1s)', () => { const result = getBucketSize(req, '1s'); - expect(result).to.have.property('bucketSize', 1); - expect(result).to.have.property('intervalString', '1s'); + expect(result).toHaveProperty('bucketSize', 1); + expect(result).toHaveProperty('intervalString', '1s'); }); - it('returns overridden buckets (10m)', () => { + test('returns overridden buckets (10m)', () => { const result = getBucketSize(req, '10m'); - expect(result).to.have.property('bucketSize', 600); - expect(result).to.have.property('intervalString', '10m'); + expect(result).toHaveProperty('bucketSize', 600); + expect(result).toHaveProperty('intervalString', '10m'); }); - it('returns overridden buckets (1d)', () => { + test('returns overridden buckets (1d)', () => { const result = getBucketSize(req, '1d'); - expect(result).to.have.property('bucketSize', 86400); - expect(result).to.have.property('intervalString', '1d'); + expect(result).toHaveProperty('bucketSize', 86400); + expect(result).toHaveProperty('intervalString', '1d'); }); - it('returns overridden buckets (>=2d)', () => { + test('returns overridden buckets (>=2d)', () => { const result = getBucketSize(req, '>=2d'); - expect(result).to.have.property('bucketSize', 86400 * 2); - expect(result).to.have.property('intervalString', '2d'); + expect(result).toHaveProperty('bucketSize', 86400 * 2); + expect(result).toHaveProperty('intervalString', '2d'); }); - it('returns overridden buckets (>=10s)', () => { + test('returns overridden buckets (>=10s)', () => { const result = getBucketSize(req, '>=10s'); - expect(result).to.have.property('bucketSize', 30); - expect(result).to.have.property('intervalString', '30s'); + expect(result).toHaveProperty('bucketSize', 30); + expect(result).toHaveProperty('intervalString', '30s'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_buckets_path.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_buckets_path.test.js similarity index 53% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_buckets_path.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_buckets_path.test.js index f5c31fa8ba7ce..0a0e18a696588 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_buckets_path.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_buckets_path.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { getBucketsPath } from '../../helpers/get_buckets_path'; +import { getBucketsPath } from './get_buckets_path'; describe('getBucketsPath', () => { const metrics = [ @@ -33,43 +32,43 @@ describe('getBucketsPath', () => { { id: 9, type: 'max' }, ]; - it('return path for derivative', () => { - expect(getBucketsPath(1, metrics)).to.equal('1[normalized_value]'); + test('return path for derivative', () => { + expect(getBucketsPath(1, metrics)).toEqual('1[normalized_value]'); }); - it('return path for percentile(50)', () => { - expect(getBucketsPath(2, metrics)).to.equal('2[50.0]'); + test('return path for percentile(50)', () => { + expect(getBucketsPath(2, metrics)).toEqual('2[50.0]'); }); - it('return path for percentile(20.0)', () => { - expect(getBucketsPath(3, metrics)).to.equal('3[20.0]'); + test('return path for percentile(20.0)', () => { + expect(getBucketsPath(3, metrics)).toEqual('3[20.0]'); }); - it('return path for percentile(10.0) with alt id', () => { - expect(getBucketsPath('3[10.0]', metrics)).to.equal('3[10.0]'); + test('return path for percentile(10.0) with alt id', () => { + expect(getBucketsPath('3[10.0]', metrics)).toEqual('3[10.0]'); }); - it('return path for std_deviation(raw)', () => { - expect(getBucketsPath(4, metrics)).to.equal('4[std_deviation]'); + test('return path for std_deviation(raw)', () => { + expect(getBucketsPath(4, metrics)).toEqual('4[std_deviation]'); }); - it('return path for std_deviation(upper)', () => { - expect(getBucketsPath(5, metrics)).to.equal('5[std_upper]'); + test('return path for std_deviation(upper)', () => { + expect(getBucketsPath(5, metrics)).toEqual('5[std_upper]'); }); - it('return path for std_deviation(lower)', () => { - expect(getBucketsPath(6, metrics)).to.equal('6[std_lower]'); + test('return path for std_deviation(lower)', () => { + expect(getBucketsPath(6, metrics)).toEqual('6[std_lower]'); }); - it('return path for sum_of_squares', () => { - expect(getBucketsPath(7, metrics)).to.equal('7[sum_of_squares]'); + test('return path for sum_of_squares', () => { + expect(getBucketsPath(7, metrics)).toEqual('7[sum_of_squares]'); }); - it('return path for variance', () => { - expect(getBucketsPath(8, metrics)).to.equal('8[variance]'); + test('return path for variance', () => { + expect(getBucketsPath(8, metrics)).toEqual('8[variance]'); }); - it('return path for basic metric', () => { - expect(getBucketsPath(9, metrics)).to.equal('9'); + test('return path for basic metric', () => { + expect(getBucketsPath(9, metrics)).toEqual('9'); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_default_decoration.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_default_decoration.test.js similarity index 51% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_default_decoration.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_default_decoration.test.js index 5cc94dda6d21a..2529c8c649485 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_default_decoration.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_default_decoration.test.js @@ -17,44 +17,43 @@ * under the License. */ -import { expect } from 'chai'; -import { getDefaultDecoration } from '../../helpers/get_default_decoration'; +import { getDefaultDecoration } from './get_default_decoration'; describe('getDefaultDecoration', () => { describe('stack option', () => { - it('should set a stack option to none', () => { + test('should set a stack option to none', () => { const series = { id: 'test_id', stacked: 'none', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'none'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'none'); }); - it('should set a stack option to stacked/percent', () => { + test('should set a stack option to stacked/percent', () => { const series = { stacked: 'stacked', id: 'test_id', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'stacked'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'stacked'); series.stacked = 'percent'; - expect(getDefaultDecoration(series)).to.have.property('stack', 'percent'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'percent'); }); - it('should set a stack option to stacked_within_series', () => { + test('should set a stack option to stacked_within_series', () => { const series = { stacked: 'stacked_within_series', id: 'test_id', }; - expect(getDefaultDecoration(series)).to.have.property('stack', 'stacked_within_series'); + expect(getDefaultDecoration(series)).toHaveProperty('stack', 'stacked_within_series'); }); }); describe('lines', () => { - it('return decoration for lines', () => { + test('return decoration for lines', () => { const series = { point_size: 10, chart_type: 'line', @@ -62,29 +61,29 @@ describe('getDefaultDecoration', () => { fill: 1, }; const result = getDefaultDecoration(series); - expect(result.lines).to.have.property('show', true); - expect(result.lines).to.have.property('fill', 1); - expect(result.lines).to.have.property('lineWidth', 10); - expect(result.points).to.have.property('show', true); - expect(result.points).to.have.property('radius', 1); - expect(result.points).to.have.property('lineWidth', 10); - expect(result.bars).to.have.property('show', false); - expect(result.bars).to.have.property('fill', 1); - expect(result.bars).to.have.property('lineWidth', 10); + expect(result.lines).toHaveProperty('show', true); + expect(result.lines).toHaveProperty('fill', 1); + expect(result.lines).toHaveProperty('lineWidth', 10); + expect(result.points).toHaveProperty('show', true); + expect(result.points).toHaveProperty('radius', 1); + expect(result.points).toHaveProperty('lineWidth', 10); + expect(result.bars).toHaveProperty('show', false); + expect(result.bars).toHaveProperty('fill', 1); + expect(result.bars).toHaveProperty('lineWidth', 10); }); - it('return decoration for lines without points', () => { + test('return decoration for lines without points', () => { const series = { chart_type: 'line', line_width: 10, fill: 1, }; const result = getDefaultDecoration(series); - expect(result.points).to.have.property('show', true); - expect(result.points).to.have.property('lineWidth', 10); + expect(result.points).toHaveProperty('show', true); + expect(result.points).toHaveProperty('lineWidth', 10); }); - it('return decoration for lines with points set to zero (off)', () => { + test('return decoration for lines with points set to zero (off)', () => { const series = { chart_type: 'line', line_width: 10, @@ -92,32 +91,32 @@ describe('getDefaultDecoration', () => { point_size: 0, }; const result = getDefaultDecoration(series); - expect(result.points).to.have.property('show', false); + expect(result.points).toHaveProperty('show', false); }); - it('return decoration for lines (off)', () => { + test('return decoration for lines (off)', () => { const series = { chart_type: 'line', line_width: 0, }; const result = getDefaultDecoration(series); - expect(result.lines).to.have.property('show', false); + expect(result.lines).toHaveProperty('show', false); }); }); describe('bars', () => { - it('return decoration for bars', () => { + test('return decoration for bars', () => { const series = { chart_type: 'bar', line_width: 10, fill: 1, }; const result = getDefaultDecoration(series); - expect(result.lines).to.have.property('show', false); - expect(result.points).to.have.property('show', false); - expect(result.bars).to.have.property('show', true); - expect(result.bars).to.have.property('fill', 1); - expect(result.bars).to.have.property('lineWidth', 10); + expect(result.lines).toHaveProperty('show', false); + expect(result.points).toHaveProperty('show', false); + expect(result.bars).toHaveProperty('show', true); + expect(result.bars).toHaveProperty('fill', 1); + expect(result.bars).toHaveProperty('lineWidth', 10); }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.test.js similarity index 82% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.test.js index b19f6a3241597..13f62739a5485 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_es_shard_timeout.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_es_shard_timeout.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { expect } from 'chai'; -import { getEsShardTimeout } from '../../helpers/get_es_shard_timeout'; +import { getEsShardTimeout } from './get_es_shard_timeout'; describe('getEsShardTimeout', () => { - it('should return the elasticsearch.shardTimeout', async () => { + test('should return the elasticsearch.shardTimeout', async () => { const req = { getEsShardTimeout: async () => { return 12345; @@ -30,6 +29,6 @@ describe('getEsShardTimeout', () => { const timeout = await getEsShardTimeout(req); - expect(timeout).to.equal(12345); + expect(timeout).toEqual(12345); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_last_metric.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_last_metric.test.js similarity index 76% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_last_metric.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_last_metric.test.js index be99f81dfccc4..42e6cc2c01836 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_last_metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_last_metric.test.js @@ -17,26 +17,25 @@ * under the License. */ -import { expect } from 'chai'; -import { getLastMetric } from '../../helpers/get_last_metric'; +import { getLastMetric } from './get_last_metric'; describe('getLastMetric(series)', () => { - it('returns the last metric', () => { + test('returns the last metric', () => { const series = { metrics: [ { id: 1, type: 'avg' }, { id: 2, type: 'moving_average' }, ], }; - expect(getLastMetric(series)).to.eql({ id: 2, type: 'moving_average' }); + expect(getLastMetric(series)).toEqual({ id: 2, type: 'moving_average' }); }); - it('returns the last metric that not a series_agg', () => { + test('returns the last metric that not a series_agg', () => { const series = { metrics: [ { id: 1, type: 'avg' }, { id: 2, type: 'series_agg' }, ], }; - expect(getLastMetric(series)).to.eql({ id: 1, type: 'avg' }); + expect(getLastMetric(series)).toEqual({ id: 1, type: 'avg' }); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_sibling_agg_value.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_sibling_agg_value.test.js similarity index 69% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_sibling_agg_value.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_sibling_agg_value.test.js index 68bfa77e25ea3..755568546949c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_sibling_agg_value.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_sibling_agg_value.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value'; +import { getSiblingAggValue } from './get_sibling_agg_value'; describe('getSiblingAggValue', () => { const row = { @@ -32,23 +31,23 @@ describe('getSiblingAggValue', () => { }, }; - it('returns the value for std_deviation_bounds.upper', () => { + test('returns the value for std_deviation_bounds.upper', () => { const metric = { id: 'test', type: 'std_deviation_bucket', mode: 'upper' }; - expect(getSiblingAggValue(row, metric)).to.equal(2); + expect(getSiblingAggValue(row, metric)).toEqual(2); }); - it('returns the value for std_deviation_bounds.lower', () => { + test('returns the value for std_deviation_bounds.lower', () => { const metric = { id: 'test', type: 'std_deviation_bucket', mode: 'lower' }; - expect(getSiblingAggValue(row, metric)).to.equal(1); + expect(getSiblingAggValue(row, metric)).toEqual(1); }); - it('returns the value for std_deviation', () => { + test('returns the value for std_deviation', () => { const metric = { id: 'test', type: 'std_deviation_bucket', mode: 'raw' }; - expect(getSiblingAggValue(row, metric)).to.equal(1.5); + expect(getSiblingAggValue(row, metric)).toEqual(1.5); }); - it('returns the value for basic (max)', () => { + test('returns the value for basic (max)', () => { const metric = { id: 'test', type: 'max_bucket' }; - expect(getSiblingAggValue(row, metric)).to.equal(3); + expect(getSiblingAggValue(row, metric)).toEqual(3); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_splits.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js similarity index 90% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_splits.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js index 1057248d2f362..0874d944033f5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_splits.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.test.js @@ -17,11 +17,10 @@ * under the License. */ -import { expect } from 'chai'; -import { getSplits } from '../../helpers/get_splits'; +import { getSplits } from './get_splits'; describe('getSplits(resp, panel, series)', () => { - it('should return a splits for everything/filter group bys', () => { + test('should return a splits for everything/filter group bys', () => { const resp = { aggregations: { SERIES: { @@ -41,7 +40,7 @@ describe('getSplits(resp, panel, series)', () => { { id: 'SIBAGG', type: 'avg_bucket', field: 'AVG' }, ], }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES', label: 'Overall Average of Average of cpu', @@ -53,7 +52,7 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - it('should return a splits for terms group bys for top_n', () => { + test('should return a splits for terms group bys for top_n', () => { const resp = { aggregations: { SERIES: { @@ -85,7 +84,7 @@ describe('getSplits(resp, panel, series)', () => { ], }; const panel = { type: 'top_n' }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES:example-01', key: 'example-01', @@ -107,7 +106,7 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - it('should return a splits for terms group bys', () => { + test('should return a splits for terms group bys', () => { const resp = { aggregations: { SERIES: { @@ -139,7 +138,7 @@ describe('getSplits(resp, panel, series)', () => { ], }; const panel = { type: 'timeseries' }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES:example-01', key: 'example-01', @@ -161,7 +160,7 @@ describe('getSplits(resp, panel, series)', () => { ]); }); - it('should return a splits for filters group bys', () => { + test('should return a splits for filters group bys', () => { const resp = { aggregations: { SERIES: { @@ -188,7 +187,7 @@ describe('getSplits(resp, panel, series)', () => { metrics: [{ id: 'COUNT', type: 'count' }], }; const panel = { type: 'timeseries' }; - expect(getSplits(resp, panel, series)).to.eql([ + expect(getSplits(resp, panel, series)).toEqual([ { id: 'SERIES:filter-1', key: 'filter-1', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_timerange.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.js similarity index 72% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_timerange.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.js index f79d1002b8546..1a1b12c651992 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/get_timerange.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.js @@ -17,12 +17,11 @@ * under the License. */ -import { expect } from 'chai'; -import { getTimerange } from '../../helpers/get_timerange'; +import { getTimerange } from './get_timerange'; import moment from 'moment'; describe('getTimerange(req)', () => { - it('should return a moment object for to and from', () => { + test('should return a moment object for to and from', () => { const req = { payload: { timerange: { @@ -32,9 +31,9 @@ describe('getTimerange(req)', () => { }, }; const { from, to } = getTimerange(req); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); - expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).to.equal(true); - expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).to.equal(true); + expect(moment.isMoment(from)).toEqual(true); + expect(moment.isMoment(to)).toEqual(true); + expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).toEqual(true); + expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).toEqual(true); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/map_bucket.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js similarity index 72% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/map_bucket.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js index e3223ef7b618f..0b007416681ee 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/map_bucket.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/map_bucket.test.js @@ -17,40 +17,39 @@ * under the License. */ -import { mapBucket } from '../../helpers/map_bucket'; -import { expect } from 'chai'; +import { mapBucket } from './map_bucket'; describe('mapBucket(metric)', () => { - it('returns bucket key and value for basic metric', () => { + test('returns bucket key and value for basic metric', () => { const metric = { id: 'AVG', type: 'avg' }; const bucket = { key: 1234, AVG: { value: 1 }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); - it('returns bucket key and value for std_deviation', () => { + test('returns bucket key and value for std_deviation', () => { const metric = { id: 'STDDEV', type: 'std_deviation' }; const bucket = { key: 1234, STDDEV: { std_deviation: 1 }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); - it('returns bucket key and value for percentiles', () => { + test('returns bucket key and value for percentiles', () => { const metric = { id: 'PCT', type: 'percentile', percent: 50 }; const bucket = { key: 1234, PCT: { values: { '50.0': 1 } }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); - it('returns bucket key and value for derivative', () => { + test('returns bucket key and value for derivative', () => { const metric = { id: 'DERV', type: 'derivative', field: 'io', unit: '1s' }; const bucket = { key: 1234, DERV: { value: 100, normalized_value: 1 }, }; - expect(mapBucket(metric)(bucket)).to.eql([1234, 1]); + expect(mapBucket(metric)(bucket)).toEqual([1234, 1]); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/parse_settings.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/parse_settings.test.js similarity index 70% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/parse_settings.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/parse_settings.test.js index bf16b6e22e8ef..eab06cdccfa66 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/helpers/parse_settings.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/helpers/parse_settings.test.js @@ -17,41 +17,40 @@ * under the License. */ -import { expect } from 'chai'; -import { parseSettings } from '../../helpers/parse_settings'; +import { parseSettings } from './parse_settings'; describe('parseSettings', () => { - it('returns the true for "true"', () => { + test('returns the true for "true"', () => { const settings = 'pad=true'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: true, }); }); - it('returns the false for "false"', () => { + test('returns the false for "false"', () => { const settings = 'pad=false'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: false, }); }); - it('returns the true for 1', () => { + test('returns the true for 1', () => { const settings = 'pad=1'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: true, }); }); - it('returns the false for 0', () => { + test('returns the false for 0', () => { const settings = 'pad=0'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ pad: false, }); }); - it('returns the settings as an object', () => { + test('returns the settings as an object', () => { const settings = 'alpha=0.9 beta=0.4 gamma=0.2 period=5 pad=false type=add'; - expect(parseSettings(settings)).to.eql({ + expect(parseSettings(settings)).toEqual({ alpha: 0.9, beta: 0.4, gamma: 0.2, diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/offset_time.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/offset_time.test.js similarity index 64% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/offset_time.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/offset_time.test.js index 55e43cf7bd8ce..b9b73002bafb3 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/__tests__/offset_time.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/offset_time.test.js @@ -17,12 +17,11 @@ * under the License. */ -import { expect } from 'chai'; import moment from 'moment'; -import { offsetTime } from '../offset_time'; +import { offsetTime } from './offset_time'; describe('offsetTime(req, by)', () => { - it('should return a moment object for to and from', () => { + test('should return a moment object for to and from', () => { const req = { payload: { timerange: { @@ -32,13 +31,13 @@ describe('offsetTime(req, by)', () => { }, }; const { from, to } = offsetTime(req, ''); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); - expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).to.equal(true); - expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).to.equal(true); + expect(moment.isMoment(from)).toBeTruthy(); + expect(moment.isMoment(to)).toBeTruthy(); + expect(moment.utc('2017-01-01T00:00:00Z').isSame(from)).toBeTruthy(); + expect(moment.utc('2017-01-01T01:00:00Z').isSame(to)).toBeTruthy(); }); - it('should return a moment object for to and from offset by 1 hour', () => { + test('should return a moment object for to and from offset by 1 hour', () => { const req = { payload: { timerange: { @@ -48,23 +47,23 @@ describe('offsetTime(req, by)', () => { }, }; const { from, to } = offsetTime(req, '1h'); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); + expect(moment.isMoment(from)).toBeTruthy(); + expect(moment.isMoment(to)).toBeTruthy(); expect( moment .utc('2017-01-01T00:00:00Z') .subtract(1, 'h') .isSame(from) - ).to.equal(true); + ).toBeTruthy(); expect( moment .utc('2017-01-01T01:00:00Z') .subtract(1, 'h') .isSame(to) - ).to.equal(true); + ).toBeTruthy(); }); - it('should return a moment object for to and from offset by -2 minute', () => { + test('should return a moment object for to and from offset by -2 minute', () => { const req = { payload: { timerange: { @@ -74,13 +73,13 @@ describe('offsetTime(req, by)', () => { }, }; const { from, to } = offsetTime(req, '-2m'); - expect(moment.isMoment(from)).to.equal(true); - expect(moment.isMoment(to)).to.equal(true); - expect(moment.utc('2017-01-10T01:02:00Z').isSame(from)).to.equal(true); - expect(moment.utc('2017-01-10T02:02:00Z').isSame(to)).to.equal(true); + expect(moment.isMoment(from)).toBeTruthy(); + expect(moment.isMoment(to)).toBeTruthy(); + expect(moment.utc('2017-01-10T01:02:00Z').isSame(from)).toBeTruthy(); + expect(moment.utc('2017-01-10T02:02:00Z').isSame(to)).toBeTruthy(); }); - it('should work when prefixing positive offsets with the plus sign', () => { + test('should work when prefixing positive offsets with the plus sign', () => { const req = { payload: { timerange: { @@ -92,7 +91,7 @@ describe('offsetTime(req, by)', () => { const { from: fromSigned, to: toSigned } = offsetTime(req, '+1m'); const { from, to } = offsetTime(req, '1m'); - expect(fromSigned.isSame(from)).to.equal(true); - expect(toSigned.isSame(to)).to.equal(true); + expect(fromSigned.isSame(from)).toBeTruthy(); + expect(toSigned.isSame(to)).toBeTruthy(); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js similarity index 85% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index 40eaba621aabb..7f309b44d13f4 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/date_histogram.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -17,10 +17,8 @@ * under the License. */ -import { expect } from 'chai'; -import sinon from 'sinon'; -import { DefaultSearchCapabilities } from '../../../../search_strategies/default_search_capabilities'; -import { dateHistogram } from '../date_histogram'; +import { DefaultSearchCapabilities } from '../../../search_strategies/default_search_capabilities'; +import { dateHistogram } from './date_histogram'; describe('dateHistogram(req, panel, series)', () => { let panel; @@ -54,18 +52,18 @@ describe('dateHistogram(req, panel, series)', () => { capabilities = new DefaultSearchCapabilities(req); }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns valid date histogram', () => { + test('returns valid date histogram', () => { const next = doc => doc; const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -93,13 +91,13 @@ describe('dateHistogram(req, panel, series)', () => { }); }); - it('returns valid date histogram (offset by 1h)', () => { + test('returns valid date histogram (offset by 1h)', () => { series.offset_time = '1h'; const next = doc => doc; const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -127,7 +125,7 @@ describe('dateHistogram(req, panel, series)', () => { }); }); - it('returns valid date histogram with overridden index pattern', () => { + test('returns valid date histogram with overridden index pattern', () => { series.override_index_pattern = 1; series.series_index_pattern = '*'; series.series_time_field = 'timestamp'; @@ -136,7 +134,7 @@ describe('dateHistogram(req, panel, series)', () => { const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -165,7 +163,7 @@ describe('dateHistogram(req, panel, series)', () => { }); describe('dateHistogram for entire time range mode', () => { - it('should ignore entire range mode for timeseries', () => { + test('should ignore entire range mode for timeseries', () => { panel.time_range_mode = 'entire_time_range'; panel.type = 'timeseries'; @@ -174,18 +172,18 @@ describe('dateHistogram(req, panel, series)', () => { {} ); - expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).to.eql(undefined); - expect(doc.aggs.test.aggs.timeseries.date_histogram).to.exist; + expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).toBeUndefined(); + expect(doc.aggs.test.aggs.timeseries.date_histogram).toBeDefined(); }); - it('should returns valid date histogram for entire range mode', () => { + test('should returns valid date histogram for entire range mode', () => { panel.time_range_mode = 'entire_time_range'; const next = doc => doc; const doc = dateHistogram(req, panel, series, config, indexPatternObject, capabilities)(next)( {} ); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/filter_ratios.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js similarity index 92% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/filter_ratios.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js index 0449ee440da81..f3d25a8e44ba7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/filter_ratios.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { ratios } from '../filter_ratios'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { ratios } from './filter_ratios'; describe('ratios(req, panel, series)', () => { let panel; @@ -55,16 +53,16 @@ describe('ratios(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); ratios(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns filter ratio aggs', () => { + test('returns filter ratio aggs', () => { const next = doc => doc; const doc = ratios(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { @@ -119,11 +117,11 @@ describe('ratios(req, panel, series)', () => { }); }); - it('returns empty object when field is not set', () => { + test('returns empty object when field is not set', () => { delete series.metrics[0].field; const next = doc => doc; const doc = ratios(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/metric_buckets.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/metric_buckets.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.test.js index 9393882f12b7e..eaf8ee1549f0b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/metric_buckets.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { metricBuckets } from '../metric_buckets'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { metricBuckets } from './metric_buckets'; describe('metricBuckets(req, panel, series)', () => { let panel; @@ -63,16 +61,16 @@ describe('metricBuckets(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); metricBuckets(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns metric aggs', () => { + test('returns metric aggs', () => { const next = doc => doc; const doc = metricBuckets(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/query.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js similarity index 91% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/query.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js index 2f2e5f46f834e..06da636f818d5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/query.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { query } from '../query'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { query } from './query'; describe('query(req, panel, series)', () => { let panel; @@ -47,16 +45,16 @@ describe('query(req, panel, series)', () => { series = { id: 'test' }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); query(req, panel, series, config)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns doc with query for timerange', () => { + test('returns doc with query for timerange', () => { const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -79,11 +77,11 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with query for timerange (offset by 1h)', () => { + test('returns doc with query for timerange (offset by 1h)', () => { series.offset_time = '1h'; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -106,7 +104,7 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with global query', () => { + test('returns doc with global query', () => { req.payload.filters = [ { bool: { @@ -122,7 +120,7 @@ describe('query(req, panel, series)', () => { ]; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -157,11 +155,11 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with series filter', () => { + test('returns doc with series filter', () => { series.filter = { query: 'host:web-server', language: 'lucene' }; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -198,7 +196,7 @@ describe('query(req, panel, series)', () => { }, }); }); - it('returns doc with panel filter and global', () => { + test('returns doc with panel filter and global', () => { req.payload.filters = [ { bool: { @@ -215,7 +213,7 @@ describe('query(req, panel, series)', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { @@ -265,7 +263,7 @@ describe('query(req, panel, series)', () => { }); }); - it('returns doc with panel filter (ignoring globals)', () => { + test('returns doc with panel filter (ignoring globals)', () => { req.payload.filters = [ { bool: { @@ -283,7 +281,7 @@ describe('query(req, panel, series)', () => { panel.ignore_global_filter = true; const next = doc => doc; const doc = query(req, panel, series, config)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/sibling_buckets.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.test.js similarity index 86% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/sibling_buckets.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.test.js index 0f02c755cabcd..8e1c9b0f9ecd9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/sibling_buckets.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { siblingBuckets } from '../sibling_buckets'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { siblingBuckets } from './sibling_buckets'; describe('siblingBuckets(req, panel, series)', () => { let panel; @@ -57,16 +55,16 @@ describe('siblingBuckets(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); siblingBuckets(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns sibling aggs', () => { + test('returns sibling aggs', () => { const next = doc => doc; const doc = siblingBuckets(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { aggs: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_everything.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_everything.test.js similarity index 83% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_everything.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_everything.test.js index 4e2775d3608e6..5c2468f6b7c35 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_everything.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_everything.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByEverything } from '../split_by_everything'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByEverything } from './split_by_everything'; describe('splitByEverything(req, panel, series)', () => { let panel; @@ -39,15 +37,15 @@ describe('splitByEverything(req, panel, series)', () => { }); it('calls next when finished', () => { - const next = sinon.spy(); + const next = jest.fn(); splitByEverything(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); it('returns a valid filter with match_all', () => { const next = doc => doc; const doc = splitByEverything(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { filter: { @@ -61,9 +59,9 @@ describe('splitByEverything(req, panel, series)', () => { it('calls next and does not add a filter', () => { series.split_mode = 'terms'; series.terms_field = 'host'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByEverything(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filter.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js similarity index 79% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filter.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js index 782310ba4d2bf..75ca782a6d7e7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filter.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByFilter } from '../split_by_filter'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByFilter } from './split_by_filter'; describe('splitByFilter(req, panel, series)', () => { let panel; @@ -42,16 +40,16 @@ describe('splitByFilter(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); splitByFilter(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns a valid filter with a query_string', () => { + test('returns a valid filter with a query_string', () => { const next = doc => doc; const doc = splitByFilter(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { filter: { @@ -73,11 +71,11 @@ describe('splitByFilter(req, panel, series)', () => { }); }); - it('calls next and does not add a filter', () => { + test('calls next and does not add a filter', () => { series.split_mode = 'terms'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByFilter(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filters.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js similarity index 85% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filters.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js index 018cb4b003ecf..6108b2bf2cf2b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_filters.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByFilters } from '../split_by_filters'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByFilters } from './split_by_filters'; describe('splitByFilters(req, panel, series)', () => { let panel; @@ -58,16 +56,16 @@ describe('splitByFilters(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); splitByFilters(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns a valid terms agg', () => { + test('returns a valid terms agg', () => { const next = doc => doc; const doc = splitByFilters(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { filters: { @@ -107,11 +105,11 @@ describe('splitByFilters(req, panel, series)', () => { }); }); - it('calls next and does not add a terms agg', () => { + test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByFilters(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_terms.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js similarity index 80% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_terms.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js index eb1a81baa7fc2..ff7139c380af9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/__tests__/split_by_terms.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { splitByTerms } from '../split_by_terms'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { splitByTerms } from './split_by_terms'; describe('splitByTerms(req, panel, series)', () => { let panel; @@ -46,16 +44,16 @@ describe('splitByTerms(req, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); splitByTerms(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('returns a valid terms agg', () => { + test('returns a valid terms agg', () => { const next = doc => doc; const doc = splitByTerms(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { terms: { @@ -70,12 +68,12 @@ describe('splitByTerms(req, panel, series)', () => { }); }); - it('returns a valid terms agg sort by terms', () => { + test('returns a valid terms agg sort by terms', () => { const next = doc => doc; series.terms_order_by = '_key'; series.terms_direction = 'asc'; const doc = splitByTerms(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { terms: { @@ -90,11 +88,11 @@ describe('splitByTerms(req, panel, series)', () => { }); }); - it('returns a valid terms agg with custom sort', () => { + test('returns a valid terms agg with custom sort', () => { series.terms_order_by = 'avgmetric'; const next = doc => doc; const doc = splitByTerms(req, panel, series)(next)({}); - expect(doc).to.eql({ + expect(doc).toEqual({ aggs: { test: { terms: { @@ -116,11 +114,11 @@ describe('splitByTerms(req, panel, series)', () => { }); }); - it('calls next and does not add a terms agg', () => { + test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; - const next = sinon.spy(doc => doc); + const next = jest.fn(doc => doc); const doc = splitByTerms(req, panel, series)(next)({}); - expect(next.calledOnce).to.equal(true); - expect(doc).to.eql({}); + expect(next.mock.calls.length).toEqual(1); + expect(doc).toEqual({}); }); }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/_series_agg.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js similarity index 68% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/_series_agg.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js index 582466f8eb9fd..22ea2c852e254 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/_series_agg.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/_series_agg.test.js @@ -17,8 +17,7 @@ * under the License. */ -import { expect } from 'chai'; -import { SeriesAgg as seriesAgg } from '../_series_agg'; +import { SeriesAgg as seriesAgg } from './_series_agg'; describe('seriesAgg', () => { const series = [ @@ -40,8 +39,8 @@ describe('seriesAgg', () => { ]; describe('basic', () => { - it('returns the series sum', () => { - expect(seriesAgg.sum(series)).to.eql([ + test('returns the series sum', () => { + expect(seriesAgg.sum(series)).toEqual([ [ [0, 8], [1, 4], @@ -50,8 +49,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series max', () => { - expect(seriesAgg.max(series)).to.eql([ + test('returns the series max', () => { + expect(seriesAgg.max(series)).toEqual([ [ [0, 4], [1, 2], @@ -60,8 +59,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series min', () => { - expect(seriesAgg.min(series)).to.eql([ + test('returns the series min', () => { + expect(seriesAgg.min(series)).toEqual([ [ [0, 2], [1, 1], @@ -70,8 +69,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series mean', () => { - expect(seriesAgg.mean(series)).to.eql([ + test('returns the series mean', () => { + expect(seriesAgg.mean(series)).toEqual([ [ [0, 8 / 3], [1, 4 / 3], @@ -82,8 +81,8 @@ describe('seriesAgg', () => { }); describe('overall', () => { - it('returns the series overall sum', () => { - expect(seriesAgg.overall_sum(series)).to.eql([ + test('returns the series overall sum', () => { + expect(seriesAgg.overall_sum(series)).toEqual([ [ [0, 21], [1, 21], @@ -92,8 +91,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series overall max', () => { - expect(seriesAgg.overall_max(series)).to.eql([ + test('returns the series overall max', () => { + expect(seriesAgg.overall_max(series)).toEqual([ [ [0, 4], [1, 4], @@ -102,8 +101,8 @@ describe('seriesAgg', () => { ]); }); - it('returns the series overall min', () => { - expect(seriesAgg.overall_min(series)).to.eql([ + test('returns the series overall min', () => { + expect(seriesAgg.overall_min(series)).toEqual([ [ [0, 1], [1, 1], @@ -112,9 +111,9 @@ describe('seriesAgg', () => { ]); }); - it('returns the series overall mean', () => { + test('returns the series overall mean', () => { const value = (8 + 4 + 9) / 3; - expect(seriesAgg.overall_avg(series)).to.eql([ + expect(seriesAgg.overall_avg(series)).toEqual([ [ [0, value], [1, value], @@ -125,8 +124,8 @@ describe('seriesAgg', () => { }); describe('cumulative sum', () => { - it('returns the series cumulative sum', () => { - expect(seriesAgg.cumulative_sum(series)).to.eql([ + test('returns the series cumulative sum', () => { + expect(seriesAgg.cumulative_sum(series)).toEqual([ [ [0, 8], [1, 12], diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js similarity index 57% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/percentile.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js index d40a75c903602..9cb08de8dad23 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/percentile.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { percentile } from '../percentile'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { percentile } from './percentile'; describe('percentile(resp, panel, series)', () => { let panel; @@ -82,72 +80,72 @@ describe('percentile(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); percentile(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = percentile(resp, panel, series)(next)([]); - expect(results).to.have.length(3); + expect(results).toHaveLength(3); - expect(results[0]).to.have.property('id', 'test:10-90'); - expect(results[0]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[0]).to.have.property('fillBetween', 'test:10-90:90'); - expect(results[0]).to.have.property('label', 'Percentile of cpu (10)'); - expect(results[0]).to.have.property('legend', false); - expect(results[0]).to.have.property('lines'); - expect(results[0].lines).to.eql({ + expect(results[0]).toHaveProperty('id', 'test:10-90'); + expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[0]).toHaveProperty('fillBetween', 'test:10-90:90'); + expect(results[0]).toHaveProperty('label', 'Percentile of cpu (10)'); + expect(results[0]).toHaveProperty('legend', false); + expect(results[0]).toHaveProperty('lines'); + expect(results[0].lines).toEqual({ fill: 0.2, lineWidth: 0, show: true, }); - expect(results[0]).to.have.property('points'); - expect(results[0].points).to.eql({ show: false }); - expect(results[0].data).to.eql([ + expect(results[0]).toHaveProperty('points'); + expect(results[0].points).toEqual({ show: false }); + expect(results[0].data).toEqual([ [1, 1], [2, 1.2], ]); - expect(results[1]).to.have.property('id', 'test:10-90:90'); - expect(results[1]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[1]).to.have.property('label', 'Percentile of cpu (10)'); - expect(results[1]).to.have.property('legend', false); - expect(results[1]).to.have.property('lines'); - expect(results[1].lines).to.eql({ + expect(results[1]).toHaveProperty('id', 'test:10-90:90'); + expect(results[1]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[1]).toHaveProperty('label', 'Percentile of cpu (10)'); + expect(results[1]).toHaveProperty('legend', false); + expect(results[1]).toHaveProperty('lines'); + expect(results[1].lines).toEqual({ fill: false, lineWidth: 0, show: true, }); - expect(results[1]).to.have.property('points'); - expect(results[1].points).to.eql({ show: false }); - expect(results[1].data).to.eql([ + expect(results[1]).toHaveProperty('points'); + expect(results[1].points).toEqual({ show: false }); + expect(results[1].data).toEqual([ [1, 5], [2, 5.3], ]); - expect(results[2]).to.have.property('id', 'test:50'); - expect(results[2]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[2]).to.have.property('label', 'Percentile of cpu (50)'); - expect(results[2]).to.have.property('stack', false); - expect(results[2]).to.have.property('lines'); - expect(results[2].lines).to.eql({ + expect(results[2]).toHaveProperty('id', 'test:50'); + expect(results[2]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[2]).toHaveProperty('label', 'Percentile of cpu (50)'); + expect(results[2]).toHaveProperty('stack', false); + expect(results[2]).toHaveProperty('lines'); + expect(results[2].lines).toEqual({ fill: 0, lineWidth: 1, show: true, steps: false, }); - expect(results[2]).to.have.property('bars'); - expect(results[2].bars).to.eql({ + expect(results[2]).toHaveProperty('bars'); + expect(results[2].bars).toEqual({ fill: 0, lineWidth: 1, show: false, }); - expect(results[2]).to.have.property('points'); - expect(results[2].points).to.eql({ show: true, lineWidth: 1, radius: 1 }); - expect(results[2].data).to.eql([ + expect(results[2]).toHaveProperty('points'); + expect(results[2].points).toEqual({ show: true, lineWidth: 1, radius: 1 }); + expect(results[2].data).toEqual([ [1, 2.5], [2, 2.7], ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/series_agg.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.test.js similarity index 88% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/series_agg.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.test.js index 2391cd3bc7698..3e09c51d9184f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/series_agg.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.test.js @@ -17,10 +17,8 @@ * under the License. */ -import { seriesAgg } from '../series_agg'; -import { stdMetric } from '../std_metric'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { seriesAgg } from './series_agg'; +import { stdMetric } from './std_metric'; describe('seriesAgg(resp, panel, series)', () => { let panel; @@ -93,18 +91,18 @@ describe('seriesAgg(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); seriesAgg(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = seriesAgg(resp, panel, series)(results => results); const results = stdMetric(resp, panel, series)(next)([]); - expect(results).to.have.length(1); + expect(results).toHaveLength(1); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test', color: '#F00', label: 'Total CPU', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_bands.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_bands.test.js similarity index 87% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_bands.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_bands.test.js index 488ae4a8351b7..77949ff94dc4c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_bands.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_bands.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { stdDeviationBands } from '../std_deviation_bands'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { stdDeviationBands } from './std_deviation_bands'; describe('stdDeviationBands(resp, panel, series)', () => { let panel; @@ -79,18 +77,18 @@ describe('stdDeviationBands(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdDeviationBands(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdDeviationBands(resp, panel, series)(next)([]); - expect(results).to.have.length(2); + expect(results).toHaveLength(2); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test:upper', label: 'Std. Deviation of cpu', color: 'rgb(255, 0, 0)', @@ -103,7 +101,7 @@ describe('stdDeviationBands(resp, panel, series)', () => { ], }); - expect(results[1]).to.eql({ + expect(results[1]).toEqual({ id: 'test:lower', color: 'rgb(255, 0, 0)', lines: { show: true, fill: false, lineWidth: 0 }, diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_sibling.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_sibling.test.js similarity index 87% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_sibling.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_sibling.test.js index 14c2c16ef728f..adc5a3a4a991b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_deviation_sibling.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_deviation_sibling.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { stdDeviationSibling } from '../std_deviation_sibling'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { stdDeviationSibling } from './std_deviation_sibling'; describe('stdDeviationSibling(resp, panel, series)', () => { let panel; @@ -79,18 +77,18 @@ describe('stdDeviationSibling(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdDeviationSibling(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdDeviationSibling(resp, panel, series)(next)([]); - expect(results).to.have.length(2); + expect(results).toHaveLength(2); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test:lower', color: 'rgb(255, 0, 0)', lines: { show: true, fill: false, lineWidth: 0 }, @@ -101,7 +99,7 @@ describe('stdDeviationSibling(resp, panel, series)', () => { ], }); - expect(results[1]).to.eql({ + expect(results[1]).toEqual({ id: 'test:upper', label: 'Overall Std. Deviation of Average of cpu', color: 'rgb(255, 0, 0)', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_metric.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.test.js similarity index 65% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_metric.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.test.js index 5039a406a6acc..6ddd6f907fa97 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_metric.test.js @@ -17,9 +17,7 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { stdMetric } from '../std_metric'; +import { stdMetric } from './std_metric'; describe('stdMetric(resp, panel, series)', () => { let panel; @@ -60,41 +58,41 @@ describe('stdMetric(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdMetric(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('calls next when finished (percentile)', () => { + test('calls next when finished (percentile)', () => { series.metrics[0].type = 'percentile'; - const next = sinon.spy(d => d); + const next = jest.fn(d => d); const results = stdMetric(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); - expect(results).to.have.length(0); + expect(next.mock.calls.length).toEqual(1); + expect(results).toHaveLength(0); }); - it('calls next when finished (std_deviation band)', () => { + test('calls next when finished (std_deviation band)', () => { series.metrics[0].type = 'std_deviation'; series.metrics[0].mode = 'band'; - const next = sinon.spy(d => d); + const next = jest.fn(d => d); const results = stdMetric(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); - expect(results).to.have.length(0); + expect(next.mock.calls.length).toEqual(1); + expect(results).toHaveLength(0); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdMetric(resp, panel, series)(next)([]); - expect(results).to.have.length(1); - expect(results[0]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[0]).to.have.property('id', 'test'); - expect(results[0]).to.have.property('label', 'Average of cpu'); - expect(results[0]).to.have.property('lines'); - expect(results[0]).to.have.property('stack'); - expect(results[0]).to.have.property('bars'); - expect(results[0]).to.have.property('points'); - expect(results[0].data).to.eql([ + expect(results).toHaveLength(1); + expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[0]).toHaveProperty('id', 'test'); + expect(results[0]).toHaveProperty('label', 'Average of cpu'); + expect(results[0]).toHaveProperty('lines'); + expect(results[0]).toHaveProperty('stack'); + expect(results[0]).toHaveProperty('bars'); + expect(results[0]).toHaveProperty('points'); + expect(results[0].data).toEqual([ [1, 1], [2, 2], ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_sibling.test.js similarity index 82% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_sibling.test.js index a243ad525332c..9ed60a6d16492 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/std_sibling.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/std_sibling.test.js @@ -17,9 +17,7 @@ * under the License. */ -import { stdSibling } from '../std_sibling'; -import { expect } from 'chai'; -import sinon from 'sinon'; +import { stdSibling } from './std_sibling'; describe('stdSibling(resp, panel, series)', () => { let panel; @@ -74,26 +72,26 @@ describe('stdSibling(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); stdSibling(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('calls next when std. deviation bands set', () => { + test('calls next when std. deviation bands set', () => { series.metrics[1].mode = 'band'; - const next = sinon.spy(results => results); + const next = jest.fn(results => results); const results = stdSibling(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); - expect(results).to.have.length(0); + expect(next.mock.calls.length).toEqual(1); + expect(results).toHaveLength(0); }); - it('creates a series', () => { + test('creates a series', () => { const next = results => results; const results = stdSibling(resp, panel, series)(next)([]); - expect(results).to.have.length(1); + expect(results).toHaveLength(1); - expect(results[0]).to.eql({ + expect(results[0]).toEqual({ id: 'test', label: 'Overall Std. Deviation of Average of cpu', color: 'rgb(255, 0, 0)', diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/time_shift.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/time_shift.test.js similarity index 71% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/time_shift.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/time_shift.test.js index 1e6da85f88164..7047c54fa1c0e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/__tests__/time_shift.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/time_shift.test.js @@ -17,10 +17,8 @@ * under the License. */ -import sinon from 'sinon'; -import { expect } from 'chai'; -import { timeShift } from '../time_shift'; -import { stdMetric } from '../std_metric'; +import { timeShift } from './time_shift'; +import { stdMetric } from './std_metric'; describe('timeShift(resp, panel, series)', () => { let panel; @@ -62,24 +60,24 @@ describe('timeShift(resp, panel, series)', () => { }; }); - it('calls next when finished', () => { - const next = sinon.spy(); + test('calls next when finished', () => { + const next = jest.fn(); timeShift(resp, panel, series)(next)([]); - expect(next.calledOnce).to.equal(true); + expect(next.mock.calls.length).toEqual(1); }); - it('creates a series', () => { + test('creates a series', () => { const next = timeShift(resp, panel, series)(results => results); const results = stdMetric(resp, panel, series)(next)([]); - expect(results).to.have.length(1); - expect(results[0]).to.have.property('color', 'rgb(255, 0, 0)'); - expect(results[0]).to.have.property('id', 'test'); - expect(results[0]).to.have.property('label', 'Average of cpu'); - expect(results[0]).to.have.property('lines'); - expect(results[0]).to.have.property('stack'); - expect(results[0]).to.have.property('bars'); - expect(results[0]).to.have.property('points'); - expect(results[0].data).to.eql([ + expect(results).toHaveLength(1); + expect(results[0]).toHaveProperty('color', 'rgb(255, 0, 0)'); + expect(results[0]).toHaveProperty('id', 'test'); + expect(results[0]).toHaveProperty('label', 'Average of cpu'); + expect(results[0]).toHaveProperty('lines'); + expect(results[0]).toHaveProperty('stack'); + expect(results[0]).toHaveProperty('bars'); + expect(results[0]).toHaveProperty('points'); + expect(results[0].data).toEqual([ [1483225200000 + 3600000, 1], [1483225210000 + 3600000, 2], ]); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/__tests__/build_request_body.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/__tests__/build_request_body.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts index f21bf3d197969..0c75e6ef1c5bd 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/__tests__/build_request_body.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts @@ -17,6 +17,8 @@ * under the License. */ +import { buildRequestBody } from './build_request_body'; + const body = JSON.parse(` { "filters": [ @@ -75,15 +77,11 @@ const body = JSON.parse(` } `); -import sinon from 'sinon'; -import { expect } from 'chai'; -import { buildRequestBody } from '../build_request_body'; - describe('buildRequestBody(req)', () => { - it('returns a valid body', () => { + test('returns a valid body', () => { const panel = body.panels[0]; const series = panel.series[0]; - const getValidTimeInterval = sinon.spy(() => '10s'); + const getValidTimeInterval = jest.fn(() => '10s'); const capabilities = { searchTimezone: 'UTC', getValidTimeInterval, @@ -102,7 +100,7 @@ describe('buildRequestBody(req)', () => { capabilities ); - expect(doc).to.eql({ + expect(doc).toEqual({ size: 0, query: { bool: { diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.js b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts similarity index 95% rename from src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.js rename to src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts index fe3137a8f86ba..85e1f8f7eb12b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts @@ -18,6 +18,7 @@ */ import { buildProcessorFunction } from '../build_processor_function'; +// @ts-ignore import { processors } from '../request_processors/series'; /** @@ -33,7 +34,7 @@ import { processors } from '../request_processors/series'; * ] * @returns {Object} doc - processed body */ -export function buildRequestBody(...args) { +export function buildRequestBody(...args: any[]) { const processor = buildProcessorFunction(processors, ...args); const doc = processor({}); return doc; 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 ebdc5a07af06d..fd7eb1ae7d878 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 3f247b57905d4..8f2d146287b08 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 c387c3ec789d3..82077a1096b99 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/_vega_editor.scss b/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss index f4276541d5d9e..709aaa2030f68 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss +++ b/src/legacy/core_plugins/vis_type_vega/public/_vega_editor.scss @@ -1,13 +1,6 @@ .visEditor--vega { .visEditorSidebar__config { padding: 0; - position: relative; - } - - .visEditorSidebar__options { - @include euiScrollBar; - flex-shrink: 1; - overflow-y: auto; } } @@ -22,8 +15,8 @@ .vgaEditor__aceEditorActions { position: absolute; z-index: $euiZLevel1; - top: 0; - // Adjust for possible scrollbars - right: $euiSize; + top: $euiSizeS; + // Adjust for sidebar collapse button + right: $euiSizeXXL; line-height: 1; } 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 c442f8f17884a..50bcff2469710 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: '#5BBAA0' }, + mark: { color: '#54B399' }, }, }) ); @@ -63,15 +63,15 @@ describe(`VegaParser._setDefaultColors`, () => { test({}, false, { config: { range: { category: { scheme: 'elastic' } }, - arc: { fill: '#5BBAA0' }, - area: { fill: '#5BBAA0' }, - line: { stroke: '#5BBAA0' }, - path: { stroke: '#5BBAA0' }, - rect: { fill: '#5BBAA0' }, - rule: { stroke: '#5BBAA0' }, - shape: { stroke: '#5BBAA0' }, - symbol: { fill: '#5BBAA0' }, - trail: { fill: '#5BBAA0' }, + 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 452397877a003..7c2638d1f5165 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/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index 9ab5f820cec31..81c98c6ddb96b 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -19,10 +19,8 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore -import { DefaultEditorSize } from 'ui/vis/editor_size'; -// @ts-ignore import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -import { Status } from '../../visualizations/public'; +import { Status, DefaultEditorSize } from '../../visualizations/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; diff --git a/src/legacy/core_plugins/vis_type_vislib/index.ts b/src/legacy/core_plugins/vis_type_vislib/index.ts new file mode 100644 index 0000000000000..8c24368f43ab1 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/index.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../types'; + +const kbnVislibVisTypesPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'vis_type_vislib', + require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'], + publicDir: resolve(__dirname, 'public'), + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + uiExports: { + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars: server => ({}), + }, + init: (server: Legacy.Server) => ({}), + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(true), + }).default(); + }, + } as Legacy.PluginSpecOptions); + +// eslint-disable-next-line import/no-default-export +export default kbnVislibVisTypesPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_vislib/package.json b/src/legacy/core_plugins/vis_type_vislib/package.json new file mode 100644 index 0000000000000..e30a9e2b35834 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/package.json @@ -0,0 +1,4 @@ +{ + "name": "vis_type_vislib", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/__snapshots__/pie_fn.test.js.snap b/src/legacy/core_plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/__snapshots__/pie_fn.test.js.snap rename to src/legacy/core_plugins/vis_type_vislib/public/__snapshots__/pie_fn.test.ts.snap diff --git a/src/legacy/core_plugins/vis_type_vislib/public/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/_index.scss new file mode 100644 index 0000000000000..64445648ba84a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/_index.scss @@ -0,0 +1 @@ +@import './vislib/index' diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js b/src/legacy/core_plugins/vis_type_vislib/public/area.ts similarity index 89% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js rename to src/legacy/core_plugins/vis_type_vislib/public/area.ts index fd13067c84cc0..9484ddc16fe62 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/area.ts @@ -18,8 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; import { Positions, ChartTypes, @@ -33,17 +37,17 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const areaDefinition = { +export const createAreaVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'area', title: i18n.translate('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', description: i18n.translate('kbnVislibVisTypes.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'area', @@ -132,7 +136,9 @@ export const areaDefinition = { { group: AggGroupNames.Metrics, name: 'metric', - title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { defaultMessage: 'Y-axis' }), + title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { + defaultMessage: 'Y-axis', + }), aggFilter: ['!geo_centroid', '!geo_bounds'], min: 1, defaults: [{ schema: 'metric', type: 'count' }], @@ -140,7 +146,9 @@ export const areaDefinition = { { group: AggGroupNames.Metrics, name: 'radius', - title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { defaultMessage: 'Dot size' }), + title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { + defaultMessage: 'Dot size', + }), min: 0, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'], @@ -148,7 +156,9 @@ export const areaDefinition = { { group: AggGroupNames.Buckets, name: 'segment', - title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { defaultMessage: 'X-axis' }), + title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { + defaultMessage: 'X-axis', + }), min: 0, max: 1, aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], @@ -175,4 +185,4 @@ export const areaDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/basic_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/basic_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx index 2bffcb383dde3..81174d63060e5 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/basic_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../legacy_imports'; import { SwitchOption } from './switch'; import { SelectOption } from './select'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx index 947c7ae7e6e36..acbb93c8da189 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx @@ -19,14 +19,17 @@ import React, { useCallback } from 'react'; import { last } from 'lodash'; + import { i18n } from '@kbn/i18n'; -import { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ranges'; +import { RangeValues, RangesParamEditor } from '../../legacy_imports'; + +export type SetColorRangeValue = (paramName: string, value: RangeValues[]) => void; interface ColorRangesProps { 'data-test-subj'?: string; colorsRange: RangeValues[]; - setValue(paramName: string, value: RangeValues[]): void; + setValue: SetColorRangeValue; setValidity?(isValid: boolean): void; setTouched?(isTouched: boolean): void; } diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx index 44837c514d4cf..7b4679ba25e8f 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_schema.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx @@ -22,8 +22,7 @@ import { i18n } from '@kbn/i18n'; import { EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { ColorSchema } from 'ui/vislib/components/color/colormaps'; +import { VisOptionsProps, ColorSchema } from '../../legacy_imports'; import { SelectOption } from './select'; import { SwitchOption } from './switch'; import { ColorSchemaVislibParams } from '../../types'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/number_input.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/number_input.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/range.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/range.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/range.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/range.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/required_number_input.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/select.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/select.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/select.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/switch.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/switch.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/switch.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/switch.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/text_input.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/text_input.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/text_input.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/text_input.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/truncate_labels.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/truncate_labels.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/truncate_labels.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/common/utils.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/utils.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx index 1dd1ab49d9a47..b38c65d086823 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/validation_wrapper.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx @@ -18,7 +18,8 @@ */ import React, { useEffect, useState, useCallback } from 'react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; + +import { VisOptionsProps } from '../../legacy_imports'; export interface ValidationVisOptionsProps extends VisOptionsProps { setMultipleValidity(paramName: string, isValid: boolean): void; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/index.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx index e3805027d658c..2ba4319a82a95 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx @@ -20,7 +20,7 @@ import React, { useCallback } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { GaugeVisParams } from '../../../gauge'; import { RangesPanel } from './ranges_panel'; import { StylePanel } from './style_panel'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/labels_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/labels_panel.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx index 1045543512c6b..b54d9a5703fcd 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/ranges_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/ranges_panel.tsx @@ -22,11 +22,12 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; +import { ColorSchemas } from '../../../legacy_imports'; import { ColorRanges, ColorSchemaOptions, SwitchOption } from '../../common'; import { GaugeOptionsInternalProps } from '.'; import { ColorSchemaVislibParams } from '../../../types'; import { Gauge } from '../../../gauge'; +import { SetColorRangeValue } from '../../common/color_ranges'; function RangesPanel({ setGaugeValue, @@ -68,7 +69,7 @@ function RangesPanel({ diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/style_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/style_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx index a76171673d9a8..0b4986ae8ad80 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/gauge/style_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/style_panel.tsx @@ -22,7 +22,7 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { AggGroupNames } from 'ui/vis/editors/default'; +import { AggGroupNames } from '../../../legacy_imports'; import { SelectOption } from '../../common'; import { GaugeOptionsInternalProps } from '.'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx index 659bcf47f7a0b..d8ca7acda7cbc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback, useEffect, useState } from 'react'; + import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicOptions, ColorRanges, @@ -35,6 +36,7 @@ import { SetColorSchemaOptionsValue } from '../../common/color_schema'; import { HeatmapVisParams } from '../../../heatmap'; import { ValueAxis } from '../../../types'; import { LabelsPanel } from './labels_panel'; +import { SetColorRangeValue } from '../../common/color_ranges'; function HeatmapOptions(props: VisOptionsProps) { const { stateParams, vis, uiState, setValue, setValidity, setTouched } = props; @@ -170,7 +172,7 @@ function HeatmapOptions(props: VisOptionsProps) { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/labels_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx index c0e9a70e8b11e..6ca0bb26ef535 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/heatmap/labels_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback } from 'react'; + import { EuiColorPicker, EuiFormRow, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { ValueAxis } from '../../../types'; import { HeatmapVisParams } from '../../../heatmap'; import { SwitchOption } from '../../common'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/category_axis_panel.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/custom_extents_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap index 256df603a7f33..d34cf930f7e61 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap @@ -17,11 +17,10 @@ exports[`MetricsAxisOptions component should init with the default set of props "bySchemaName": [Function], } } - aggsLabels="" changeValueAxis={[Function]} + isTabSelected={true} setParamByIndex={[Function]} setValue={[MockFunction]} - setVisType={[MockFunction]} stateParams={ Object { "categoryAxes": Array [ @@ -95,6 +94,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "setVisType": [MockFunction], "type": Object { "schemas": Object { "metrics": Array [ @@ -127,13 +127,12 @@ exports[`MetricsAxisOptions component should init with the default set of props "bySchemaName": [Function], } } - aggsLabels="" isCategoryAxisHorizontal={true} + isTabSelected={true} onValueAxisPositionChanged={[Function]} removeValueAxis={[Function]} setParamByIndex={[Function]} setValue={[MockFunction]} - setVisType={[MockFunction]} stateParams={ Object { "categoryAxes": Array [ @@ -207,6 +206,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "setVisType": [MockFunction], "type": Object { "schemas": Object { "metrics": Array [ @@ -238,7 +238,6 @@ exports[`MetricsAxisOptions component should init with the default set of props "bySchemaName": [Function], } } - aggsLabels="" axis={ Object { "id": "CategoryAxis-1", @@ -260,10 +259,10 @@ exports[`MetricsAxisOptions component should init with the default set of props "type": "category", } } + isTabSelected={true} onPositionChanged={[Function]} setCategoryAxis={[Function]} setValue={[MockFunction]} - setVisType={[MockFunction]} stateParams={ Object { "categoryAxes": Array [ @@ -337,6 +336,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "setVisType": [MockFunction], "type": Object { "schemas": Object { "metrics": Array [ diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/label_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/line_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axes_panel.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/value_axis_options.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/y_extents.test.tsx.snap diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx index a32e48baf4588..69622bb3666a6 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.test.tsx @@ -25,6 +25,8 @@ import { Positions, getPositions } from '../../../utils/collections'; import { LabelOptions } from './label_options'; import { categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); + const positions = getPositions(); describe('CategoryAxisPanel component', () => { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx index 11946a5a6bccd..b83508f3f0896 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/category_axis_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback } from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams, Axis } from '../../../types'; import { SelectOption, SwitchOption } from '../../common'; import { LabelOptions } from './label_options'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.test.tsx index ba1a46ba7d89e..9679728a2a3d1 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.test.tsx @@ -31,6 +31,8 @@ import { } from '../../../utils/collections'; import { valueAxis, seriesParam } from './mocks'; +jest.mock('ui/new_platform'); + const interpolationModes = getInterpolationModes(); const chartTypes = getChartTypes(); const chartModes = getChartModes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx index 1c9357c67c2f0..8830c9164c751 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/chart_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/chart_options.tsx @@ -18,10 +18,11 @@ */ import React, { useMemo, useCallback } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types'; import { ChartTypes } from '../../../utils/collections'; import { SelectOption } from '../../common'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx index 5f1779ad35304..a112b9a3db708 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.test.tsx @@ -28,6 +28,8 @@ const DEFAULT_Y_EXTENTS = 'defaultYExtents'; const SCALE = 'scale'; const SET_Y_EXTENTS = 'setYExtents'; +jest.mock('ui/new_platform'); + describe('CustomExtentsOptions component', () => { let setValueAxis: jest.Mock; let setValueAxisScale: jest.Mock; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/custom_extents_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/custom_extents_options.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx similarity index 80% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx index dc5cf42277603..514b957765a99 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx @@ -19,6 +19,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; + import { MetricsAxisOptions } from './index'; import { BasicVislibParams, SeriesParam, ValueAxis } from '../../../types'; import { ValidationVisOptionsProps } from '../../common'; @@ -26,10 +27,10 @@ import { Positions } from '../../../utils/collections'; import { ValueAxesPanel } from './value_axes_panel'; import { CategoryAxisPanel } from './category_axis_panel'; import { ChartTypes } from '../../../utils/collections'; -import { AggConfig } from 'ui/vis'; -import { AggType } from 'ui/agg_types'; +import { AggConfig, AggType } from '../../../legacy_imports'; import { defaultValueAxisId, valueAxis, seriesParam, categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); jest.mock('./series_panel', () => ({ SeriesPanel: () => 'SeriesPanel', })); @@ -62,7 +63,6 @@ const createAggs = (aggs: any[]) => ({ describe('MetricsAxisOptions component', () => { let setValue: jest.Mock; - let setVisType: jest.Mock; let defaultProps: ValidationVisOptionsProps; let axis: ValueAxis; let axisRight: ValueAxis; @@ -70,7 +70,6 @@ describe('MetricsAxisOptions component', () => { beforeEach(() => { setValue = jest.fn(); - setVisType = jest.fn(); axis = { ...valueAxis, @@ -90,12 +89,13 @@ describe('MetricsAxisOptions component', () => { defaultProps = { aggs: createAggs([aggCount]), - aggsLabels: '', + isTabSelected: true, vis: { type: { type: ChartTypes.AREA, schemas: { metrics: [{ name: 'metric' }] }, }, + setVisType: jest.fn(), }, stateParams: { valueAxes: [axis], @@ -104,7 +104,6 @@ describe('MetricsAxisOptions component', () => { grid: { valueAxis: defaultValueAxisId }, }, setValue, - setVisType, } as any; }); @@ -119,7 +118,6 @@ describe('MetricsAxisOptions component', () => { const comp = mount(); comp.setProps({ aggs: createAggs([aggCount, aggAverage]), - aggsLabels: `${aggCount.makeLabel()}, ${aggAverage.makeLabel()}`, }); const updatedSeries = [chart, { ...chart, data: { id: '2', label: aggAverage.makeLabel() } }]; @@ -134,7 +132,7 @@ describe('MetricsAxisOptions component', () => { }); const updatedSeries = [{ ...chart, data: { id: agg.id, label: agg.makeLabel() } }]; - expect(setValue).toHaveBeenLastCalledWith(SERIES_PARAMS, updatedSeries); + expect(setValue).toHaveBeenCalledWith(SERIES_PARAMS, updatedSeries); }); it('should update visType when one seriesParam', () => { @@ -148,7 +146,7 @@ describe('MetricsAxisOptions component', () => { }, }); - expect(setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE); + expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.LINE); }); it('should set histogram visType when multiple seriesParam', () => { @@ -162,7 +160,7 @@ describe('MetricsAxisOptions component', () => { }, }); - expect(setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM); + expect(defaultProps.vis.setVisType).toHaveBeenLastCalledWith(ChartTypes.HISTOGRAM); }); }); @@ -176,7 +174,6 @@ describe('MetricsAxisOptions component', () => { }; comp.setProps({ aggs: createAggs([newAgg]), - aggsLabels: `${newAgg.makeLabel()}`, }); const updatedValues = [{ ...axis, title: { text: newAgg.makeLabel() } }]; expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES, updatedValues); @@ -191,11 +188,14 @@ describe('MetricsAxisOptions component', () => { }; comp.setProps({ aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), }); + const updatedSeriesParams = [{ ...chart, data: { ...chart.data, label: agg.makeLabel() } }]; const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); + + expect(setValue).toHaveBeenCalledTimes(3); + expect(setValue).toHaveBeenNthCalledWith(2, SERIES_PARAMS, updatedSeriesParams); + expect(setValue).toHaveBeenNthCalledWith(3, VALUE_AXES, updatedValues); }); it('should not set the custom title to match the value axis label when more than one agg exists for that axis', () => { @@ -203,7 +203,6 @@ describe('MetricsAxisOptions component', () => { const agg = { id: aggCount.id, makeLabel: () => 'Custom label' }; comp.setProps({ aggs: createAggs([agg, aggAverage]), - aggsLabels: `${agg.makeLabel()}, ${aggAverage.makeLabel()}`, stateParams: { ...defaultProps.stateParams, seriesParams: [chart, chart], @@ -223,48 +222,10 @@ describe('MetricsAxisOptions component', () => { }; comp.setProps({ aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), }); expect(setValue).not.toHaveBeenCalledWith(VALUE_AXES); }); - - it('should overwrite the custom title when the agg type changes', () => { - defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; - const comp = mount(); - const agg = { - id: aggCount.id, - type: { name: 'max' }, - makeLabel: () => 'Max', - }; - comp.setProps({ - aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), - }); - - const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); - }); - - it('should overwrite the custom title when the agg field changes', () => { - defaultProps.stateParams.valueAxes[0].title.text = 'Custom title'; - const agg = { - id: aggCount.id, - type: { name: 'max' }, - makeLabel: () => 'Max', - } as AggConfig; - defaultProps.aggs = createAggs([agg]) as any; - const comp = mount(); - agg.params = { field: { name: 'Field' } }; - agg.makeLabel = () => 'Max, Field'; - comp.setProps({ - aggs: createAggs([agg]), - aggsLabels: agg.makeLabel(), - }); - - const updatedValues = [{ ...axis, title: { text: agg.makeLabel() } }]; - expect(setValue).toHaveBeenCalledWith(VALUE_AXES, updatedValues); - }); }); it('should add value axis', () => { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx index 2ca4ed1e2343d..c4dcbfaa47265 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx @@ -21,7 +21,7 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { cloneDeep, uniq, get } from 'lodash'; import { EuiSpacer } from '@elastic/eui'; -import { AggConfig } from 'ui/vis'; +import { AggConfig } from '../../../legacy_imports'; import { BasicVislibParams, ValueAxis, SeriesParam, Axis } from '../../../types'; import { ValidationVisOptionsProps } from '../../common'; import { SeriesPanel } from './series_panel'; @@ -52,7 +52,7 @@ export type ChangeValueAxis = ( const VALUE_AXIS_PREFIX = 'ValueAxis-'; function MetricsAxisOptions(props: ValidationVisOptionsProps) { - const { stateParams, setValue, aggs, aggsLabels, setVisType, vis } = props; + const { stateParams, setValue, aggs, vis, isTabSelected } = props; const [isCategoryAxisHorizontal, setIsCategoryAxisHorizontal] = useState(true); @@ -89,9 +89,11 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) } ); - const updateAxisTitle = () => { + const updateAxisTitle = (seriesParams?: SeriesParam[]) => { + const series = seriesParams || stateParams.seriesParams; const axes = cloneDeep(stateParams.valueAxes); let isAxesChanged = false; + let lastValuesChanged = false; const lastLabels = { ...lastCustomLabels }; const lastMatchingSeriesAgg = { ...lastSeriesAgg }; @@ -99,8 +101,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) let newCustomLabel = ''; const matchingSeries: AggConfig[] = []; - stateParams.seriesParams.forEach((series, seriesIndex) => { - if ((axisNumber === 0 && !series.valueAxis) || series.valueAxis === axis.id) { + series.forEach((serie, seriesIndex) => { + if ((axisNumber === 0 && !serie.valueAxis) || serie.valueAxis === axis.id) { const aggByIndex = aggs.bySchemaName('metric')[seriesIndex]; matchingSeries.push(aggByIndex); } @@ -125,6 +127,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) field: matchingSeriesAggField, }; lastLabels[axis.id] = newCustomLabel; + lastValuesChanged = true; if ( Object.keys(lastCustomLabels).length !== 0 && @@ -147,8 +150,10 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) setValue('valueAxes', axes); } - setLastSeriesAgg(lastMatchingSeriesAgg); - setLastCustomLabels(lastLabels); + if (lastValuesChanged) { + setLastSeriesAgg(lastMatchingSeriesAgg); + setLastCustomLabels(lastLabels); + } }; const onValueAxisPositionChanged = useCallback( @@ -242,7 +247,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) const metrics = useMemo(() => { const schemaName = vis.type.schemas.metrics[0].name; return aggs.bySchemaName(schemaName); - }, [vis.type.schemas.metrics[0].name, aggs, aggsLabels]); + }, [vis.type.schemas.metrics[0].name, aggs]); const firstValueAxesId = stateParams.valueAxes[0].id; @@ -272,7 +277,8 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) }); setValue('seriesParams', updatedSeries); - }, [aggsLabels, metrics, firstValueAxesId]); + updateAxisTitle(updatedSeries); + }, [metrics, firstValueAxesId]); const visType = useMemo(() => { const types = uniq(stateParams.seriesParams.map(({ type }) => type)); @@ -280,14 +286,10 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) }, [stateParams.seriesParams]); useEffect(() => { - setVisType(visType); - }, [visType]); - - useEffect(() => { - updateAxisTitle(); - }, [aggsLabels]); + vis.setVisType(visType); + }, [vis, visType]); - return ( + return isTabSelected ? ( <> @@ -307,7 +309,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) setCategoryAxis={setCategoryAxis} /> - ); + ) : null; } export { MetricsAxisOptions }; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.test.tsx index abb3a2455f9f9..91d9987c77f3b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.test.tsx @@ -23,6 +23,8 @@ import { LabelOptions, LabelOptionsProps } from './label_options'; import { TruncateLabelsOption } from '../../common'; import { valueAxis, categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); + const FILTER = 'filter'; const ROTATE = 'rotate'; const DISABLED = 'disabled'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx index 427d43e18cca6..6a94eabe25243 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/label_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/label_options.tsx @@ -18,11 +18,12 @@ */ import React, { useCallback, useMemo } from 'react'; + import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams, Axis } from '../../../types'; import { SelectOption, SwitchOption, TruncateLabelsOption } from '../../common'; import { getRotateOptions } from '../../../utils/collections'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx index 0e603814493fa..98ef8a094a260 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.test.tsx @@ -24,6 +24,8 @@ import { NumberInputOption } from '../../common'; import { getInterpolationModes } from '../../../utils/collections'; import { seriesParam } from './mocks'; +jest.mock('ui/new_platform'); + const LINE_WIDTH = 'lineWidth'; const DRAW_LINES = 'drawLinesBetweenPoints'; const interpolationModes = getInterpolationModes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx index 9514b69a20b04..0848b708b9094 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/line_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/line_options.tsx @@ -18,10 +18,11 @@ */ import React, { useCallback } from 'react'; + import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { Vis } from 'ui/vis'; +import { Vis } from '../../../legacy_imports'; import { SeriesParam } from '../../../types'; import { NumberInputOption, SelectOption, SwitchOption } from '../../common'; import { SetChart } from './chart_options'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/mocks.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/mocks.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/series_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx similarity index 97% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/series_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx index 5a455f4adde31..4d87cc61797fc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/series_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/series_panel.tsx @@ -18,11 +18,12 @@ */ import React from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer, EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { BasicVislibParams } from '../../../types'; import { ChartOptions } from './chart_options'; import { SetParamByIndex, ChangeValueAxis } from './'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/utils.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/utils.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts index 7144b0ad4902e..7c4f3b3ec8843 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/utils.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/utils.ts @@ -44,7 +44,7 @@ const makeSerie = ( }; const isAxisHorizontal = (position: Positions) => - [Positions.TOP, Positions.BOTTOM].includes(position); + [Positions.TOP, Positions.BOTTOM].includes(position as any); const RADIX = 10; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx index 080c64db7ff85..7524c7a13435b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.test.tsx @@ -25,6 +25,8 @@ import { Positions, getScaleTypes, getAxisModes, getPositions } from '../../../u import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { valueAxis, seriesParam } from './mocks'; +jest.mock('ui/new_platform'); + const positions = getPositions(); const axisModes = getAxisModes(); const scaleTypes = getScaleTypes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axes_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axes_panel.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx index 8cb476508c78b..bd512e9365783 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.test.tsx @@ -32,6 +32,8 @@ import { } from '../../../utils/collections'; import { valueAxis, categoryAxis } from './mocks'; +jest.mock('ui/new_platform'); + const POSITION = 'position'; const positions = getPositions(); const axisModes = getAxisModes(); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx index 243950b762390..b4ea4cb42ee60 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/value_axis_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/value_axis_options.tsx @@ -100,7 +100,7 @@ function ValueAxisOptions(props: ValueAxisOptionsParams) { if (isCategoryAxisHorizontal) { return isAxisHorizontal(position); } - return [Positions.LEFT, Positions.RIGHT].includes(position); + return [Positions.LEFT, Positions.RIGHT].includes(position as any); }, [isCategoryAxisHorizontal] ); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx similarity index 99% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.test.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx index 2df17b6e34985..17c47b35b20dc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.test.tsx @@ -23,6 +23,8 @@ import { YExtents, YExtentsProps } from './y_extents'; import { ScaleTypes } from '../../../utils/collections'; import { NumberInputOption } from '../../common'; +jest.mock('ui/new_platform'); + describe('YExtents component', () => { let setMultipleValidity: jest.Mock; let setScale: jest.Mock; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/metrics_axes/y_extents.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/y_extents.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/pie.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/pie.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx index 53dde185ec09f..056eb70cb256b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/pie.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx @@ -17,11 +17,12 @@ * under the License. */ import React from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../legacy_imports'; import { BasicOptions, TruncateLabelsOption, SwitchOption } from '../common'; import { PieVisParams } from '../../pie'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/grid_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx similarity index 98% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/grid_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx index 63b2449b823d5..bdb4d3f39c12b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/grid_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx @@ -17,11 +17,12 @@ * under the License. */ import React, { useMemo, useEffect, useCallback } from 'react'; + import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisOptionsProps } from '../../../legacy_imports'; import { SelectOption, SwitchOption } from '../../common'; import { BasicVislibParams, ValueAxis } from '../../../types'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/index.ts b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/index.ts similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/index.ts rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/index.ts diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/point_series.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx similarity index 100% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/threshold_panel.tsx diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts similarity index 77% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js rename to src/legacy/core_plugins/vis_type_vislib/public/gauge.ts index a472d20e07224..5dcc8ad16918d 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts @@ -18,14 +18,43 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; + +import { Schemas, AggGroupNames, ColorSchemas, RangeValues } from './legacy_imports'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, Alignments, ColorModes, GaugeTypes } from './utils/collections'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { ColorSchemaVislibParams, Labels, Style } from './types'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +export interface Gauge extends ColorSchemaVislibParams { + backStyle: 'Full'; + gaugeStyle: 'Full'; + orientation: 'vertical'; + type: 'meter'; + alignment: Alignments; + colorsRange: RangeValues[]; + extendRange: boolean; + gaugeType: GaugeTypes; + labels: Labels; + percentageMode: boolean; + outline?: boolean; + scale: { + show: boolean; + labels: false; + color: 'rgba(105,112,125,0.2)'; + }; + style: Style; +} + +export interface GaugeVisParams { + type: 'gauge'; + addTooltip: boolean; + addLegend: boolean; + isDisplayWarning: boolean; + gauge: Gauge; +} -export const gaugeDefinition = { +export const createGaugeVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'gauge', title: i18n.translate('kbnVislibVisTypes.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), icon: 'visGauge', @@ -79,7 +108,7 @@ export const gaugeDefinition = { }, }, }, - visualization: vislibVisController, + visualization: createVislibVisController(deps), editorConfig: { collections: getGaugeCollections(), optionsTemplate: GaugeOptions, @@ -115,4 +144,4 @@ export const gaugeDefinition = { ]), }, useCustomNoDataScreen: true, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts similarity index 90% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js rename to src/legacy/core_plugins/vis_type_vislib/public/goal.ts index 71fcd6593e72c..302d5f6393ef9 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts @@ -18,21 +18,21 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; + +import { Schemas, AggGroupNames, ColorSchemas } from './legacy_imports'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, GaugeTypes, ColorModes } from './utils/collections'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const goalDefinition = { +export const createGoalVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'goal', title: i18n.translate('kbnVislibVisTypes.goal.goalTitle', { defaultMessage: 'Goal' }), icon: 'visGoal', description: i18n.translate('kbnVislibVisTypes.goal.goalDescription', { defaultMessage: 'A goal chart indicates how close you are to your final goal.', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { addTooltip: true, @@ -108,4 +108,4 @@ export const goalDefinition = { ]), }, useCustomNoDataScreen: true, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts similarity index 81% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js rename to src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts index 292e108053598..eb5f84b409838 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts @@ -18,21 +18,35 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; + +import { Schemas, AggGroupNames, ColorSchemas, RangeValues } from './legacy_imports'; import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections'; import { HeatmapOptions } from './components/options'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { TimeMarker } from './vislib/visualizations/time_marker'; +import { CommonVislibParams, ColorSchemaVislibParams, ValueAxis } from './types'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +export interface HeatmapVisParams extends CommonVislibParams, ColorSchemaVislibParams { + type: 'heatmap'; + addLegend: boolean; + enableHover: boolean; + colorsNumber: number | ''; + colorsRange: RangeValues[]; + valueAxes: ValueAxis[]; + setColorRange: boolean; + percentageMode: boolean; + times: TimeMarker[]; +} -export const heatmapDefinition = { +export const createHeatmapVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'heatmap', title: i18n.translate('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), icon: 'visHeatmap', description: i18n.translate('kbnVislibVisTypes.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'heatmap', @@ -122,4 +136,4 @@ export const heatmapDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js rename to src/legacy/core_plugins/vis_type_vislib/public/histogram.ts index bc017b5a1a871..f92875a62cfd7 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts @@ -18,8 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; + import { Positions, ChartTypes, @@ -32,10 +37,10 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const histogramDefinition = { +export const createHistogramVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'histogram', title: i18n.translate('kbnVislibVisTypes.histogram.histogramTitle', { defaultMessage: 'Vertical Bar', @@ -44,7 +49,7 @@ export const histogramDefinition = { description: i18n.translate('kbnVislibVisTypes.histogram.histogramDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'histogram', @@ -183,4 +188,4 @@ export const histogramDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js rename to src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts index ee3570314618a..ada0c6b44ff70 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts @@ -18,8 +18,13 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; + import { Positions, ChartTypes, @@ -32,10 +37,10 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const horizontalBarDefinition = { +export const createHorizontalBarVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'horizontal_bar', title: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar', @@ -44,7 +49,7 @@ export const horizontalBarDefinition = { description: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', { defaultMessage: 'Assign a continuous variable to each axis', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'histogram', @@ -182,4 +187,4 @@ export const horizontalBarDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/index.js b/src/legacy/core_plugins/vis_type_vislib/public/index.ts similarity index 73% rename from src/legacy/core_plugins/kbn_vislib_vis_types/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/index.ts index 6e1f17c941ece..3b4bcb6bc3a7e 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/index.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/index.ts @@ -17,14 +17,11 @@ * under the License. */ -export default function(kibana) { - return new kibana.Plugin({ - uiExports: { - visTypes: ['plugins/kbn_vislib_vis_types/kbn_vislib_vis_types'], - interpreter: [ - 'plugins/kbn_vislib_vis_types/pie_fn', - 'plugins/kbn_vislib_vis_types/vislib_fn', - ], - }, - }); +import { PluginInitializerContext } from '../../../../core/public'; +import { KbnVislibVisTypesPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); } + +export { ColorModes } from './utils/collections'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts new file mode 100644 index 0000000000000..3d4cf55adc5e0 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy.ts @@ -0,0 +1,74 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +import { PluginInitializerContext } from 'kibana/public'; + +/* eslint-disable prettier/prettier */ +import { + initializeHierarchicalTooltipFormatter, + getHierarchicalTooltipFormatter, + // @ts-ignore +} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; +import { + initializePointSeriesTooltipFormatter, + getPointSeriesTooltipFormatter, + // @ts-ignore +} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; +import { + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, + // @ts-ignore +} from 'ui/vis/response_handlers/vislib'; +// @ts-ignore +import { vislibColor } from 'ui/vis/components/color/color'; + +import { plugin } from '.'; +import { + KbnVislibVisTypesPluginSetupDependencies, + KbnVislibVisTypesPluginStartDependencies, +} from './plugin'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; + +const setupPlugins: Readonly = { + expressions: npSetup.plugins.expressions, + visualizations: visualizationsSetup, + __LEGACY: { + initializeHierarchicalTooltipFormatter, + getHierarchicalTooltipFormatter, + initializePointSeriesTooltipFormatter, + getPointSeriesTooltipFormatter, + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, + vislibColor, + }, +}; + +const startPlugins: Readonly = { + expressions: npStart.plugins.expressions, + visualizations: visualizationsStart, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts new file mode 100644 index 0000000000000..2970942f221e8 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { AggGroupNames, VisOptionsProps } from 'ui/vis/editors/default'; +export { Schemas } from 'ui/vis/editors/default/schemas'; +export { RangeValues, RangesParamEditor } from 'ui/vis/editors/default/controls/ranges'; +export { ColorSchema, ColorSchemas, colorSchemas, getHeatmapColors } from 'ui/color_maps'; +export { AggConfig, Vis, VisParams } from 'ui/vis'; +export { AggType } from 'ui/agg_types'; +export { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from 'ui/vis/vis_types/vislib_vis_legend'; +// @ts-ignore +export { Tooltip } from 'ui/vis/components/tooltip'; +// @ts-ignore +export { SimpleEmitter } from 'ui/utils/simple_emitter'; +// @ts-ignore +export { Binder } from 'ui/binder'; +export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; +// @ts-ignore +export { tabifyAggResponse } from 'ui/agg_response/tabify'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js b/src/legacy/core_plugins/vis_type_vislib/public/line.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js rename to src/legacy/core_plugins/vis_type_vislib/public/line.ts index d6d075f452fed..35a059fadddcb 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/line.ts @@ -18,8 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; +// @ts-ignore +import { palettes } from '@elastic/eui/lib/services'; +// @ts-ignore +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; + +import { Schemas, AggGroupNames } from './legacy_imports'; import { Positions, ChartTypes, @@ -32,18 +36,18 @@ import { InterpolationModes, getConfigCollections, } from './utils/collections'; -import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { KbnVislibVisTypesDependencies } from './plugin'; -export const lineDefinition = { +export const createLineVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'line', title: i18n.translate('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', description: i18n.translate('kbnVislibVisTypes.line.lineDescription', { defaultMessage: 'Emphasize trends', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'line', @@ -175,4 +179,4 @@ export const lineDefinition = { }, ]), }, -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts similarity index 81% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js rename to src/legacy/core_plugins/vis_type_vislib/public/pie.ts index 1cef3114f047d..32307b7a117a1 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts @@ -18,20 +18,34 @@ */ import { i18n } from '@kbn/i18n'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggGroupNames } from 'ui/vis/editors/default'; + +import { Schemas, AggGroupNames } from './legacy_imports'; import { PieOptions } from './components/options'; import { getPositions, Positions } from './utils/collections'; -import { vislibVisController } from './controller'; +import { createVislibVisController } from './vis_controller'; +import { CommonVislibParams } from './types'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +export interface PieVisParams extends CommonVislibParams { + type: 'pie'; + addLegend: boolean; + isDonut: boolean; + labels: { + show: boolean; + values: boolean; + last_level: boolean; + truncate: number | null; + }; +} -export const pieDefinition = { +export const createPieVisTypeDefinition = (deps: KbnVislibVisTypesDependencies) => ({ name: 'pie', title: i18n.translate('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', description: i18n.translate('kbnVislibVisTypes.pie.pieDescription', { defaultMessage: 'Compare parts of a whole', }), - visualization: vislibVisController, + visualization: createVislibVisController(deps), visConfig: { defaults: { type: 'pie', @@ -89,4 +103,4 @@ export const pieDefinition = { }, hierarchicalData: true, responseHandler: 'vislib_slices', -}; +}); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts similarity index 92% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts index 4d932e6e9518e..786de0cc79b86 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.test.ts @@ -19,10 +19,14 @@ // eslint-disable-next-line import { functionWrapper } from '../../../../plugins/expressions/public/functions/tests/utils'; -import { kibanaPie } from './pie_fn'; +import { createPieVisFn } from './pie_fn'; +import { KbnVislibVisTypesDependencies } from './plugin'; jest.mock('ui/new_platform'); +const deps: KbnVislibVisTypesDependencies = { + vislibSlicesResponseHandlerProvider: () => ({ handler: mockResponseHandler }), +} as any; const mockResponseHandler = jest.fn().mockReturnValue( Promise.resolve({ hits: 1, @@ -39,20 +43,15 @@ const mockResponseHandler = jest.fn().mockReturnValue( }, }) ); -jest.mock('ui/vis/response_handlers/vislib', () => ({ - vislibSlicesResponseHandlerProvider: () => ({ handler: mockResponseHandler }), -})); describe('interpreter/functions#pie', () => { - const fn = functionWrapper(kibanaPie); + const fn = functionWrapper(createPieVisFn(deps)); const context = { type: 'kibana_datatable', rows: [{ 'col-0-1': 0 }], columns: [{ id: 'col-0-1', name: 'Count' }], }; const visConfig = { - addTooltip: true, - addLegend: false, type: 'pie', addTooltip: true, addLegend: true, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.js b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts similarity index 68% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.js rename to src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts index f66fd53e59efe..4b536caedb121 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie_fn.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie_fn.ts @@ -17,11 +17,37 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; -import { vislibSlicesResponseHandlerProvider as vislibSlicesResponseHandler } from 'ui/vis/response_handlers/vislib'; import { i18n } from '@kbn/i18n'; -export const kibanaPie = () => ({ +import { + ExpressionFunction, + KibanaDatatable, + Render, +} from '../../../../plugins/expressions/public'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +const name = 'kibana_pie'; + +type Context = KibanaDatatable; + +interface Arguments { + visConfig: string; +} + +type VisParams = Required; + +interface RenderValue { + visConfig: VisParams; +} + +type Return = Promise>; + +export const createPieVisFn = (deps: KbnVislibVisTypesDependencies) => (): ExpressionFunction< + typeof name, + Context, + Arguments, + Return +> => ({ name: 'kibana_pie', type: 'render', context: { @@ -32,14 +58,15 @@ export const kibanaPie = () => ({ }), args: { visConfig: { - types: ['string', 'null'], + types: ['string'], default: '"{}"', + help: '', }, }, async fn(context, args) { const visConfig = JSON.parse(args.visConfig); - const responseHandler = vislibSlicesResponseHandler().handler; + const responseHandler = deps.vislibSlicesResponseHandlerProvider().handler; const convertedData = await responseHandler(context, visConfig.dimensions); return { @@ -56,5 +83,3 @@ export const kibanaPie = () => ({ }; }, }); - -functionsRegistry.register(kibanaPie); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts new file mode 100644 index 0000000000000..a2e8512b2201b --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -0,0 +1,106 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + IUiSettingsClient, + PluginInitializerContext, +} from 'kibana/public'; + +import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { createKbnVislibVisTypesFn } from './vis_type_vislib_vis_fn'; +import { createPieVisFn } from './pie_fn'; +import { + createHistogramVisTypeDefinition, + createLineVisTypeDefinition, + createPieVisTypeDefinition, + createAreaVisTypeDefinition, + createHeatmapVisTypeDefinition, + createHorizontalBarVisTypeDefinition, + createGaugeVisTypeDefinition, + createGoalVisTypeDefinition, +} from './vis_type_vislib_vis_types'; + +type ResponseHandlerProvider = () => { + name: string; + handler: (response: any, dimensions: any) => Promise; +}; +type KbnVislibVisTypesCoreSetup = CoreSetup; + +export interface LegacyDependencies { + initializeHierarchicalTooltipFormatter: () => Promise; + getHierarchicalTooltipFormatter: () => Promise; + initializePointSeriesTooltipFormatter: () => void; + getPointSeriesTooltipFormatter: () => void; + vislibSeriesResponseHandlerProvider: ResponseHandlerProvider; + vislibSlicesResponseHandlerProvider: ResponseHandlerProvider; + vislibColor: (colors: Array, mappings: any) => (value: any) => any; +} + +export type KbnVislibVisTypesDependencies = LegacyDependencies & { + uiSettings: IUiSettingsClient; +}; + +/** @internal */ +export interface KbnVislibVisTypesPluginSetupDependencies { + expressions: ReturnType; + visualizations: VisualizationsSetup; + __LEGACY: LegacyDependencies; +} + +/** @internal */ +export interface KbnVislibVisTypesPluginStartDependencies { + expressions: ReturnType; + visualizations: VisualizationsStart; +} + +/** @internal */ +export class KbnVislibVisTypesPlugin implements Plugin, void> { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: KbnVislibVisTypesCoreSetup, + { expressions, visualizations, __LEGACY }: KbnVislibVisTypesPluginSetupDependencies + ) { + const visualizationDependencies: Readonly = { + ...__LEGACY, + uiSettings: core.uiSettings, + }; + + expressions.registerFunction(createKbnVislibVisTypesFn(visualizationDependencies)); + expressions.registerFunction(createPieVisFn(visualizationDependencies)); + + [ + createHistogramVisTypeDefinition, + createLineVisTypeDefinition, + createPieVisTypeDefinition, + createAreaVisTypeDefinition, + createHeatmapVisTypeDefinition, + createHorizontalBarVisTypeDefinition, + createGaugeVisTypeDefinition, + createGoalVisTypeDefinition, + ].forEach(vis => visualizations.types.createBaseVisualization(vis(visualizationDependencies))); + } + + public start(core: CoreStart, deps: KbnVislibVisTypesPluginStartDependencies) { + // nothing to do here + } +} diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts b/src/legacy/core_plugins/vis_type_vislib/public/types.ts similarity index 95% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts rename to src/legacy/core_plugins/vis_type_vislib/public/types.ts index b023eb54f7295..b6928be89f648 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/types.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/types.ts @@ -17,8 +17,8 @@ * under the License. */ -import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; -import { TimeMarker } from 'ui/vislib/visualizations/time_marker'; +import { ColorSchemas } from './legacy_imports'; +import { TimeMarker } from './vislib/visualizations/time_marker'; import { Positions, ChartModes, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts similarity index 73% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts rename to src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts index 6fe30483a32e8..810ddeea73834 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/collections.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/collections.ts @@ -18,14 +18,16 @@ */ import { i18n } from '@kbn/i18n'; -import { colorSchemas } from 'ui/vislib/components/color/colormaps'; +import { $Values } from '@kbn/utility-types'; +import { colorSchemas } from '../legacy_imports'; -export enum Positions { - RIGHT = 'right', - LEFT = 'left', - TOP = 'top', - BOTTOM = 'bottom', -} +export const Positions = Object.freeze({ + RIGHT: 'right' as 'right', + LEFT: 'left' as 'left', + TOP: 'top' as 'top', + BOTTOM: 'bottom' as 'bottom', +}); +export type Positions = $Values; const getPositions = () => [ { @@ -54,11 +56,12 @@ const getPositions = () => [ }, ]; -export enum ChartTypes { - LINE = 'line', - AREA = 'area', - HISTOGRAM = 'histogram', -} +export const ChartTypes = Object.freeze({ + LINE: 'line' as 'line', + AREA: 'area' as 'area', + HISTOGRAM: 'histogram' as 'histogram', +}); +export type ChartTypes = $Values; const getChartTypes = () => [ { @@ -81,10 +84,11 @@ const getChartTypes = () => [ }, ]; -export enum ChartModes { - NORMAL = 'normal', - STACKED = 'stacked', -} +export const ChartModes = Object.freeze({ + NORMAL: 'normal' as 'normal', + STACKED: 'stacked' as 'stacked', +}); +export type ChartModes = $Values; const getChartModes = () => [ { @@ -101,11 +105,12 @@ const getChartModes = () => [ }, ]; -export enum InterpolationModes { - LINEAR = 'linear', - CARDINAL = 'cardinal', - STEP_AFTER = 'step-after', -} +export const InterpolationModes = Object.freeze({ + LINEAR: 'linear' as 'linear', + CARDINAL: 'cardinal' as 'cardinal', + STEP_AFTER: 'step-after' as 'step-after', +}); +export type InterpolationModes = $Values; const getInterpolationModes = () => [ { @@ -128,16 +133,18 @@ const getInterpolationModes = () => [ }, ]; -export enum AxisTypes { - CATEGORY = 'category', - VALUE = 'value', -} +export const AxisTypes = Object.freeze({ + CATEGORY: 'category' as 'category', + VALUE: 'value' as 'value', +}); +export type AxisTypes = $Values; -export enum ScaleTypes { - LINEAR = 'linear', - LOG = 'log', - SQUARE_ROOT = 'square root', -} +export const ScaleTypes = Object.freeze({ + LINEAR: 'linear' as 'linear', + LOG: 'log' as 'log', + SQUARE_ROOT: 'square root' as 'square root', +}); +export type ScaleTypes = $Values; const getScaleTypes = () => [ { @@ -160,12 +167,13 @@ const getScaleTypes = () => [ }, ]; -export enum AxisModes { - NORMAL = 'normal', - PERCENTAGE = 'percentage', - WIGGLE = 'wiggle', - SILHOUETTE = 'silhouette', -} +export const AxisModes = Object.freeze({ + NORMAL: 'normal' as 'normal', + PERCENTAGE: 'percentage' as 'percentage', + WIGGLE: 'wiggle' as 'wiggle', + SILHOUETTE: 'silhouette' as 'silhouette', +}); +export type AxisModes = $Values; const getAxisModes = () => [ { @@ -194,17 +202,19 @@ const getAxisModes = () => [ }, ]; -export enum Rotates { - HORIZONTAL = 0, - VERTICAL = 90, - ANGLED = 75, -} +export const Rotates = Object.freeze({ + HORIZONTAL: 0, + VERTICAL: 90, + ANGLED: 75, +}); +export type Rotates = $Values; -export enum ThresholdLineStyles { - FULL = 'full', - DASHED = 'dashed', - DOT_DASHED = 'dot-dashed', -} +export const ThresholdLineStyles = Object.freeze({ + FULL: 'full' as 'full', + DASHED: 'dashed' as 'dashed', + DOT_DASHED: 'dot-dashed' as 'dot-dashed', +}); +export type ThresholdLineStyles = $Values; const getThresholdLineStyles = () => [ { @@ -248,16 +258,18 @@ const getRotateOptions = () => [ }, ]; -export enum GaugeTypes { - ARC = 'Arc', - CIRCLE = 'Circle', -} +export const GaugeTypes = Object.freeze({ + ARC: 'Arc' as 'Arc', + CIRCLE: 'Circle' as 'Circle', +}); +export type GaugeTypes = $Values; -export enum ColorModes { - BACKGROUND = 'Background', - LABELS = 'Labels', - NONE = 'None', -} +export const ColorModes = Object.freeze({ + BACKGROUND: 'Background' as 'Background', + LABELS: 'Labels' as 'Labels', + NONE: 'None' as 'None', +}); +export type ColorModes = $Values; const getGaugeTypes = () => [ { @@ -274,11 +286,12 @@ const getGaugeTypes = () => [ }, ]; -export enum Alignments { - AUTOMATIC = 'automatic', - HORIZONTAL = 'horizontal', - VERTICAL = 'vertical', -} +export const Alignments = Object.freeze({ + AUTOMATIC: 'automatic' as 'automatic', + HORIZONTAL: 'horizontal' as 'horizontal', + VERTICAL: 'vertical' as 'vertical', +}); +export type Alignments = $Values; const getAlignments = () => [ { diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/common_config.tsx b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx similarity index 96% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/common_config.tsx rename to src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx index 7e4140606762f..adb93ca8011b2 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/utils/common_config.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx @@ -18,8 +18,9 @@ */ import React from 'react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; import { i18n } from '@kbn/i18n'; + +import { VisOptionsProps } from '../legacy_imports'; import { PointSeriesOptions, MetricsAxisOptions } from '../components/options'; import { ValidationWrapper } from '../components/common'; import { BasicVislibParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx new file mode 100644 index 0000000000000..cff9a0a2e8551 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_controller.tsx @@ -0,0 +1,147 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import $ from 'jquery'; +import React, { RefObject } from 'react'; + +import { CUSTOM_LEGEND_VIS_TYPES, VisLegend, Vis, VisParams } from './legacy_imports'; +// @ts-ignore +import { Vis as Vislib } from './vislib/vis'; +import { Positions } from './utils/collections'; +import { KbnVislibVisTypesDependencies } from './plugin'; +import { mountReactNode } from '../../../../core/public/utils'; + +const legendClassName = { + top: 'visLib--legend-top', + bottom: 'visLib--legend-bottom', + left: 'visLib--legend-left', + right: 'visLib--legend-right', +}; + +export const createVislibVisController = (deps: KbnVislibVisTypesDependencies) => { + return class VislibVisController { + unmount: (() => void) | null = null; + visParams?: VisParams; + legendRef: RefObject; + container: HTMLDivElement; + chartEl: HTMLDivElement; + legendEl: HTMLDivElement; + vislibVis: any; + + constructor(public el: Element, public vis: Vis) { + this.el = el; + this.vis = vis; + this.unmount = null; + this.legendRef = React.createRef(); + + // vis mount point + this.container = document.createElement('div'); + this.container.className = 'visLib'; + this.el.appendChild(this.container); + + // chart mount point + this.chartEl = document.createElement('div'); + this.chartEl.className = 'visLib__chart'; + this.container.appendChild(this.chartEl); + + // legend mount point + this.legendEl = document.createElement('div'); + this.legendEl.className = 'visLib__legend'; + this.container.appendChild(this.legendEl); + } + + render(esResponse: any, visParams: VisParams) { + if (this.vislibVis) { + this.destroy(); + } + + return new Promise(async resolve => { + if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { + return resolve(); + } + + await deps.initializeHierarchicalTooltipFormatter(); + await deps.initializePointSeriesTooltipFormatter(); + + this.vislibVis = new Vislib(this.chartEl, visParams, deps); + this.vislibVis.on('brush', this.vis.API.events.brush); + this.vislibVis.on('click', this.vis.API.events.filter); + this.vislibVis.on('renderComplete', resolve); + + this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); + + if (visParams.addLegend) { + $(this.container) + .attr('class', (i, cls) => { + return cls.replace(/visLib--legend-\S+/g, ''); + }) + .addClass((legendClassName as any)[visParams.legendPosition]); + + this.mountLegend(esResponse, visParams.legendPosition); + } + + this.vislibVis.render(esResponse, this.vis.getUiState()); + + // refreshing the legend after the chart is rendered. + // this is necessary because some visualizations + // provide data necessary for the legend only after a render cycle. + if ( + visParams.addLegend && + CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) + ) { + this.unmountLegend(); + this.mountLegend(esResponse, visParams.legendPosition); + this.vislibVis.render(esResponse, this.vis.getUiState()); + } + }); + } + + mountLegend(visData: any, position: Positions) { + this.unmount = mountReactNode( + + )(this.legendEl); + } + + unmountLegend() { + if (this.unmount) { + this.unmount(); + } + } + + destroy() { + if (this.unmount) { + this.unmount(); + } + + if (this.vislibVis) { + this.vislibVis.off('brush', this.vis.API.events.brush); + this.vislibVis.off('click', this.vis.API.events.filter); + this.vislibVis.destroy(); + delete this.vislibVis; + } + } + }; +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/vislib_fn.js b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts similarity index 68% rename from src/legacy/core_plugins/kbn_vislib_vis_types/public/vislib_fn.js rename to src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts index 85ea5994548d6..0a685cd70e089 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/vislib_fn.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_fn.ts @@ -17,11 +17,36 @@ * under the License. */ -import { functionsRegistry } from 'plugins/interpreter/registries'; import { i18n } from '@kbn/i18n'; -import { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; -export const vislib = () => ({ +import { + ExpressionFunction, + KibanaDatatable, + Render, +} from '../../../../plugins/expressions/public'; +import { KbnVislibVisTypesDependencies } from './plugin'; + +const name = 'vislib'; + +type Context = KibanaDatatable; + +interface Arguments { + type: string; + visConfig: string; +} + +type VisParams = Required; + +interface RenderValue { + visType: string; + visConfig: VisParams; +} + +type Return = Promise>; + +export const createKbnVislibVisTypesFn = ( + deps: KbnVislibVisTypesDependencies +) => (): ExpressionFunction => ({ name: 'vislib', type: 'render', context: { @@ -34,14 +59,16 @@ export const vislib = () => ({ type: { types: ['string'], default: '""', + help: 'vislib vis type', }, visConfig: { - types: ['string', 'null'], + types: ['string'], default: '"{}"', + help: '', }, }, async fn(context, args) { - const responseHandler = vislibSeriesResponseHandlerProvider().handler; + const responseHandler = deps.vislibSeriesResponseHandlerProvider().handler; const visConfigParams = JSON.parse(args.visConfig); const convertedData = await responseHandler(context, visConfigParams.dimensions); @@ -60,5 +87,3 @@ export const vislib = () => ({ }; }, }); - -functionsRegistry.register(vislib); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts new file mode 100644 index 0000000000000..f44d503895483 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vis_type_vislib_vis_types.ts @@ -0,0 +1,27 @@ +/* + * 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 { createHistogramVisTypeDefinition } from './histogram'; +export { createLineVisTypeDefinition } from './line'; +export { createPieVisTypeDefinition } from './pie'; +export { createAreaVisTypeDefinition } from './area'; +export { createHeatmapVisTypeDefinition } from './heatmap'; +export { createHorizontalBarVisTypeDefinition } from './horizontal_bar'; +export { createGaugeVisTypeDefinition } from './gauge'; +export { createGoalVisTypeDefinition } from './goal'; diff --git a/src/legacy/ui/public/vislib/VISLIB.md b/src/legacy/core_plugins/vis_type_vislib/public/vislib/VISLIB.md similarity index 100% rename from src/legacy/ui/public/vislib/VISLIB.md rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/VISLIB.md diff --git a/src/legacy/ui/public/vislib/__tests__/components/heatmap_color.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js similarity index 97% rename from src/legacy/ui/public/vislib/__tests__/components/heatmap_color.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js index 1cafbadcff655..36c5b60abf5c6 100644 --- a/src/legacy/ui/public/vislib/__tests__/components/heatmap_color.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/heatmap_color.js @@ -19,7 +19,8 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { getHeatmapColors } from '../../components/color/heatmap_color'; + +import { getHeatmapColors } from '../../../legacy_imports'; describe('Vislib Heatmap Color Module Test Suite', function() { const emptyObject = {}; diff --git a/src/legacy/ui/public/vislib/__tests__/components/labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/components/labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/components/labels.js diff --git a/src/legacy/ui/public/vislib/__tests__/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/index.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/__snapshots__/dispatch_heatmap.test.js.snap diff --git a/src/legacy/ui/public/vislib/__tests__/lib/axis/axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/axis/axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js index 8b32943b64ea8..3081c12415076 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/axis/axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis/axis.js @@ -20,9 +20,11 @@ import d3 from 'd3'; import _ from 'lodash'; import ngMock from 'ng_mock'; +import 'ui/persisted_state'; + import expect from '@kbn/expect'; import $ from 'jquery'; -import '../../../../persisted_state'; + import { Axis } from '../../../lib/axis'; import { VisConfig } from '../../../lib/vis_config'; @@ -119,7 +121,8 @@ describe('Vislib Axis Class Test Suite', function() { }, data, persistedState, - $('.x-axis-div')[0] + $('.x-axis-div')[0], + () => undefined ); yAxis = new Axis(visConfig, { type: 'value', diff --git a/src/legacy/ui/public/vislib/__tests__/lib/axis_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js similarity index 97% rename from src/legacy/ui/public/vislib/__tests__/lib/axis_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js index d71184fdc1223..cbb294c3b44e4 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/axis_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/axis_title.js @@ -21,12 +21,14 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; import ngMock from 'ng_mock'; + import expect from '@kbn/expect'; +import 'ui/persisted_state'; + import { AxisTitle } from '../../lib/axis/axis_title'; import { AxisConfig } from '../../lib/axis/axis_config'; import { VisConfig } from '../../lib/vis_config'; import { Data } from '../../lib/data'; -import '../../../persisted_state'; describe('Vislib AxisTitle Class Test Suite', function() { let PersistedState; @@ -118,14 +120,15 @@ describe('Vislib AxisTitle Class Test Suite', function() { .style('height', '20px') .style('width', '20px'); - dataObj = new Data(data, new PersistedState()); + dataObj = new Data(data, new PersistedState(), () => undefined); visConfig = new VisConfig( { type: 'histogram', }, data, new PersistedState(), - el.node() + el.node(), + () => undefined ); const xAxisConfig = new AxisConfig(visConfig, { position: 'bottom', diff --git a/src/legacy/ui/public/vislib/__tests__/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/chart_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js index e64999e7bd329..b2086d0749a41 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/chart_title.js @@ -21,9 +21,9 @@ import d3 from 'd3'; import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; + import { ChartTitle } from '../../lib/chart_title'; import { VisConfig } from '../../lib/vis_config'; -import '../../../persisted_state'; describe('Vislib ChartTitle Class Test Suite', function() { let persistedState; @@ -112,7 +112,8 @@ describe('Vislib ChartTitle Class Test Suite', function() { }, data, persistedState, - el.node() + el.node(), + () => undefined ); chartTitle = new ChartTitle(visConfig); }) diff --git a/src/legacy/ui/public/vislib/__tests__/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/lib/data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js index c12259ee55a5a..5811b1d238163 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/data.js @@ -20,9 +20,9 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import { Data } from '../../lib/data'; -import '../../../persisted_state'; const seriesData = { label: '', @@ -168,7 +168,7 @@ describe('Vislib Data Class Test Suite', function() { }); it('should return an object', function() { - const rowIn = new Data(rowsData, persistedState); + const rowIn = new Data(rowsData, persistedState, () => undefined); expect(_.isObject(rowIn)).to.be(true); }); }); @@ -182,7 +182,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(pieData, persistedState); + data = new Data(pieData, persistedState, () => undefined); }); it('should remove zero values', function() { @@ -196,7 +196,7 @@ describe('Vislib Data Class Test Suite', function() { let serOut; beforeEach(function() { - serIn = new Data(seriesData, persistedState); + serIn = new Data(seriesData, persistedState, () => undefined); serOut = serIn.flatten(); }); @@ -210,7 +210,7 @@ describe('Vislib Data Class Test Suite', function() { function testLength(inputData) { return function() { - const data = new Data(inputData, persistedState); + const data = new Data(inputData, persistedState, () => undefined); const len = _.reduce( data.chartData(), function(sum, chart) { @@ -266,7 +266,7 @@ describe('Vislib Data Class Test Suite', function() { }; beforeEach(function() { - data = new Data(geohashGridData, persistedState); + data = new Data(geohashGridData, persistedState, () => undefined); }); describe('getVisData', function() { @@ -287,7 +287,7 @@ describe('Vislib Data Class Test Suite', function() { describe('null value check', function() { it('should return false', function() { - const data = new Data(rowsData, persistedState); + const data = new Data(rowsData, persistedState, () => undefined); expect(data.hasNullValues()).to.be(false); }); @@ -307,7 +307,7 @@ describe('Vislib Data Class Test Suite', function() { ], }); - const data = new Data(nullRowData, persistedState); + const data = new Data(nullRowData, persistedState, () => undefined); expect(data.hasNullValues()).to.be(true); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/dispatch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js similarity index 90% rename from src/legacy/ui/public/vislib/__tests__/lib/dispatch.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js index f2d2e7d05a34d..a93db5637c89d 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/dispatch.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch.js @@ -20,22 +20,23 @@ import _ from 'lodash'; import d3 from 'd3'; import ngMock from 'ng_mock'; + import expect from '@kbn/expect'; +import 'ui/persisted_state'; // Data -import data from 'fixtures/vislib/mock_data/date_histogram/_series'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; -import { SimpleEmitter } from '../../../utils/simple_emitter'; +import data from './fixtures/mock_data/date_histogram/_series'; +import getFixturesVislibVisFixtureProvider from './fixtures/_vis_fixture'; +import { SimpleEmitter } from '../../../legacy_imports'; describe('Vislib Dispatch Class Test Suite', function() { function destroyVis(vis) { vis.destroy(); } - function getEls(el, n, type) { + function getEls(element, n, type) { return d3 - .select(el) + .select(element) .data(new Array(n)) .enter() .append(type); @@ -48,7 +49,8 @@ describe('Vislib Dispatch Class Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); }) @@ -74,8 +76,9 @@ describe('Vislib Dispatch Class Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); persistedState = new ($injector.get('PersistedState'))(); + vis = getVis(); vis.on('brush', _.noop); vis.render(data, persistedState); }) @@ -91,7 +94,7 @@ describe('Vislib Dispatch Class Test Suite', function() { const apply = chart.events.addEvent('event', _.noop); expect(apply).to.be.a('function'); - const els = getEls(vis.el, 3, 'div'); + const els = getEls(vis.element, 3, 'div'); apply(els); els.each(function() { expect(d3.select(this).on('event')).to.be(_.noop); @@ -114,7 +117,7 @@ describe('Vislib Dispatch Class Test Suite', function() { const apply = chart.events[name](chart.series[0].chartEl); expect(apply).to.be.a('function'); - const els = getEls(vis.el, 3, 'div'); + const els = getEls(vis.element, 3, 'div'); apply(els); els.each(function() { expect(d3.select(this).on(event)).to.be.a('function'); @@ -188,7 +191,8 @@ describe('Vislib Dispatch Class Test Suite', function() { let persistedState; ngMock.module('kibana'); ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.on('someEvent', _.noop); vis.render(data, persistedState); @@ -207,7 +211,8 @@ describe('Vislib Dispatch Class Test Suite', function() { let persistedState; ngMock.module('kibana'); ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); vis.on('someEvent', _.noop); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_heatmap.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_heatmap.test.js similarity index 88% rename from src/legacy/ui/public/vislib/__tests__/lib/dispatch_heatmap.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_heatmap.test.js index 19e78ef4f30c2..e22f19ea643fd 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_heatmap.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_heatmap.test.js @@ -16,7 +16,13 @@ * specific language governing permissions and limitations * under the License. */ + import mockDispatchDataD3 from './fixtures/dispatch_heatmap_d3.json'; +import { Dispatch } from '../../lib/dispatch'; +import mockdataPoint from './fixtures/dispatch_heatmap_data_point.json'; +import mockConfigPercentage from './fixtures/dispatch_heatmap_config.json'; + +jest.mock('ui/new_platform'); jest.mock('d3', () => ({ event: { target: { @@ -26,16 +32,14 @@ jest.mock('d3', () => ({ }, }, })); - -import { Dispatch } from '../../lib/dispatch'; -import mockdataPoint from './fixtures/dispatch_heatmap_data_point.json'; -import mockConfigPercentage from './fixtures/dispatch_heatmap_config.json'; - -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get: () => '', - }), - addBasePath: () => {}, +jest.mock('../../../legacy_imports.ts', () => ({ + ...jest.requireActual('../../../legacy_imports.ts'), + chrome: { + getUiSettingsClient: () => ({ + get: () => '', + }), + addBasePath: () => {}, + }, })); function getHandlerMock(config = {}, data = {}) { diff --git a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js similarity index 90% rename from src/legacy/ui/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js index e196c1bde0737..8fe9ac24db77b 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/dispatch_vertical_bar_chart.test.js @@ -16,7 +16,14 @@ * specific language governing permissions and limitations * under the License. */ + import mockDispatchDataD3 from './fixtures/dispatch_bar_chart_d3.json'; +import { Dispatch } from '../../lib/dispatch'; +import mockdataPoint from './fixtures/dispatch_bar_chart_data_point.json'; +import mockConfigPercentage from './fixtures/dispatch_bar_chart_config_percentage.json'; +import mockConfigNormal from './fixtures/dispatch_bar_chart_config_normal.json'; + +jest.mock('ui/new_platform'); jest.mock('d3', () => ({ event: { target: { @@ -26,17 +33,14 @@ jest.mock('d3', () => ({ }, }, })); - -import { Dispatch } from '../../lib/dispatch'; -import mockdataPoint from './fixtures/dispatch_bar_chart_data_point.json'; -import mockConfigPercentage from './fixtures/dispatch_bar_chart_config_percentage.json'; -import mockConfigNormal from './fixtures/dispatch_bar_chart_config_normal.json'; - -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get: () => '', - }), - addBasePath: () => {}, +jest.mock('../../../legacy_imports.ts', () => ({ + ...jest.requireActual('../../../legacy_imports.ts'), + chrome: { + getUiSettingsClient: () => ({ + get: () => '', + }), + addBasePath: () => {}, + }, })); function getHandlerMock(config = {}, data = {}) { diff --git a/src/legacy/ui/public/vislib/__tests__/lib/error_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/error_handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/error_handler.js diff --git a/src/fixtures/vislib/_vis_fixture.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js similarity index 61% rename from src/fixtures/vislib/_vis_fixture.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js index d71c76a1fd99e..c49ca732f0915 100644 --- a/src/fixtures/vislib/_vis_fixture.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/_vis_fixture.js @@ -19,7 +19,23 @@ import _ from 'lodash'; import $ from 'jquery'; -import { VislibVisProvider } from 'ui/vislib/vis'; + +import { Vis } from '../../../vis'; + +// TODO: remove legacy imports when/of converting tests to jest +import { + setHierarchicalTooltipFormatter, + getHierarchicalTooltipFormatter, +} from 'ui/vis/components/tooltip/_hierarchical_tooltip_formatter'; +import { + setPointSeriesTooltipFormatter, + getPointSeriesTooltipFormatter, +} from 'ui/vis/components/tooltip/_pointseries_tooltip_formatter'; +import { + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, +} from 'ui/vis/response_handlers/vislib'; +import { vislibColor } from 'ui/vis/components/color/color'; const $visCanvas = $('
') .attr('id', 'vislib-vis-fixtures') @@ -52,11 +68,25 @@ afterEach(function() { count = 0; }); -export default function VislibFixtures(Private) { - return function(visLibParams) { - const Vis = Private(VislibVisProvider); +const getDeps = () => { + const uiSettings = new Map(); + return { + uiSettings, + vislibColor, + getHierarchicalTooltipFormatter, + getPointSeriesTooltipFormatter, + vislibSeriesResponseHandlerProvider, + vislibSlicesResponseHandlerProvider, + }; +}; + +export default function getVislibFixtures(Private) { + setHierarchicalTooltipFormatter(Private); + setPointSeriesTooltipFormatter(Private); + + return function(visLibParams, element) { return new Vis( - $visCanvas.new(), + element || $visCanvas.new(), _.defaults({}, visLibParams || {}, { addTooltip: true, addLegend: true, @@ -64,7 +94,8 @@ export default function VislibFixtures(Private) { setYExtents: false, yAxis: {}, type: 'histogram', - }) + }), + getDeps() ); }; } diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_normal.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_config_percentage.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_d3.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_bar_chart_data_point.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_config.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_d3.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/dispatch_heatmap_data_point.json diff --git a/src/fixtures/vislib/mock_data/date_histogram/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_columns.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_rows_series_with_holes.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows_series_with_holes.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_rows_series_with_holes.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_rows_series_with_holes.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series_monthly_interval.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_monthly_interval.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series_monthly_interval.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_monthly_interval.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series_neg.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_neg.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series_neg.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_neg.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_series_pos_neg.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_pos_neg.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_series_pos_neg.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_series_pos_neg.js diff --git a/src/fixtures/vislib/mock_data/date_histogram/_stacked_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_stacked_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/date_histogram/_stacked_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/date_histogram/_stacked_series.js diff --git a/src/fixtures/vislib/mock_data/filters/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/filters/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_columns.js diff --git a/src/fixtures/vislib/mock_data/filters/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/filters/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_rows.js diff --git a/src/fixtures/vislib/mock_data/filters/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/filters/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/filters/_series.js diff --git a/src/fixtures/vislib/mock_data/geohash/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/geohash/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_columns.js diff --git a/src/fixtures/vislib/mock_data/geohash/_geo_json.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_geo_json.js similarity index 100% rename from src/fixtures/vislib/mock_data/geohash/_geo_json.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_geo_json.js diff --git a/src/fixtures/vislib/mock_data/geohash/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/geohash/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/geohash/_rows.js diff --git a/src/fixtures/vislib/mock_data/histogram/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_columns.js diff --git a/src/fixtures/vislib/mock_data/histogram/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_rows.js diff --git a/src/fixtures/vislib/mock_data/histogram/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_series.js diff --git a/src/fixtures/vislib/mock_data/histogram/_slices.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_slices.js similarity index 100% rename from src/fixtures/vislib/mock_data/histogram/_slices.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/histogram/_slices.js diff --git a/src/fixtures/vislib/mock_data/not_enough_data/_one_point.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/not_enough_data/_one_point.js similarity index 100% rename from src/fixtures/vislib/mock_data/not_enough_data/_one_point.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/not_enough_data/_one_point.js diff --git a/src/fixtures/vislib/mock_data/range/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/range/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_columns.js diff --git a/src/fixtures/vislib/mock_data/range/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/range/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_rows.js diff --git a/src/fixtures/vislib/mock_data/range/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/range/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/range/_series.js diff --git a/src/fixtures/vislib/mock_data/significant_terms/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/significant_terms/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_columns.js diff --git a/src/fixtures/vislib/mock_data/significant_terms/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/significant_terms/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_rows.js diff --git a/src/fixtures/vislib/mock_data/significant_terms/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/significant_terms/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/significant_terms/_series.js diff --git a/src/fixtures/vislib/mock_data/stacked/_stacked.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/stacked/_stacked.js similarity index 100% rename from src/fixtures/vislib/mock_data/stacked/_stacked.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/stacked/_stacked.js diff --git a/src/fixtures/vislib/mock_data/terms/_columns.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_columns.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_columns.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_columns.js diff --git a/src/fixtures/vislib/mock_data/terms/_rows.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_rows.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_rows.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_rows.js diff --git a/src/fixtures/vislib/mock_data/terms/_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_series.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_series.js diff --git a/src/fixtures/vislib/mock_data/terms/_seriesMultiple.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js similarity index 100% rename from src/fixtures/vislib/mock_data/terms/_seriesMultiple.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/mock_data/terms/_seriesMultiple.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/handler/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js similarity index 87% rename from src/legacy/ui/public/vislib/__tests__/lib/handler/handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js index d37d67567e8a5..b309c97d24000 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/handler/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/handler/handler.js @@ -21,13 +21,14 @@ import ngMock from 'ng_mock'; import expect from '@kbn/expect'; // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; -import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import series from '../fixtures/mock_data/date_histogram/_series'; +import columns from '../fixtures/mock_data/date_histogram/_columns'; +import rows from '../fixtures/mock_data/date_histogram/_rows'; +import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../../persisted_state'; +import 'ui/persisted_state'; + +import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; const dateHistogramArray = [series, columns, rows, stackedSeries]; const names = ['series', 'columns', 'rows', 'stackedSeries']; @@ -40,7 +41,8 @@ dateHistogramArray.forEach(function(data, i) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); }) @@ -106,12 +108,12 @@ dateHistogramArray.forEach(function(data, i) { describe('removeAll Method', function() { beforeEach(function() { ngMock.inject(function() { - vis.handler.removeAll(vis.el); + vis.handler.removeAll(vis.element); }); }); it('should remove all DOM elements from the el', function() { - expect($(vis.el).children().length).to.be(0); + expect($(vis.element).children().length).to.be(0); }); }); @@ -121,7 +123,7 @@ dateHistogramArray.forEach(function(data, i) { }); it('should return an error classed DOM element with a text message', function() { - expect($(vis.el).find('.error').length).to.be(1); + expect($(vis.element).find('.error').length).to.be(1); expect($('.error h4').html()).to.be('This is an error!'); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js similarity index 65% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js index 853a00d035a07..c8636f34ce6f8 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/layout/layout.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout.js @@ -20,16 +20,16 @@ import d3 from 'd3'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; -import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import series from '../fixtures/mock_data/date_histogram/_series'; +import columns from '../fixtures/mock_data/date_histogram/_columns'; +import rows from '../fixtures/mock_data/date_histogram/_rows'; +import stackedSeries from '../fixtures/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; import { Layout } from '../../../lib/layout/layout'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../../persisted_state'; +import getFixturesVislibVisFixtureProvider from '../fixtures/_vis_fixture'; import { VisConfig } from '../../../lib/vis_config'; const dateHistogramArray = [series, columns, rows, stackedSeries]; @@ -46,7 +46,8 @@ dateHistogramArray.forEach(function(data, i) { beforeEach(function() { ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); vis.render(data, persistedState); numberOfCharts = vis.handler.charts.length; @@ -59,17 +60,17 @@ dateHistogramArray.forEach(function(data, i) { describe('createLayout Method', function() { it('should append all the divs', function() { - expect($(vis.el).find('.visWrapper').length).to.be(1); - expect($(vis.el).find('.visAxis--y').length).to.be(2); - expect($(vis.el).find('.visWrapper__column').length).to.be(1); - expect($(vis.el).find('.visAxis__column--y').length).to.be(2); - expect($(vis.el).find('.y-axis-title').length).to.be.above(0); - expect($(vis.el).find('.visAxis__splitAxes--y').length).to.be(2); - expect($(vis.el).find('.visAxis__spacer--y').length).to.be(4); - expect($(vis.el).find('.visWrapper__chart').length).to.be(numberOfCharts); - expect($(vis.el).find('.visAxis--x').length).to.be(2); - expect($(vis.el).find('.visAxis__splitAxes--x').length).to.be(2); - expect($(vis.el).find('.x-axis-title').length).to.be.above(0); + expect($(vis.element).find('.visWrapper').length).to.be(1); + expect($(vis.element).find('.visAxis--y').length).to.be(2); + expect($(vis.element).find('.visWrapper__column').length).to.be(1); + expect($(vis.element).find('.visAxis__column--y').length).to.be(2); + expect($(vis.element).find('.y-axis-title').length).to.be.above(0); + expect($(vis.element).find('.visAxis__splitAxes--y').length).to.be(2); + expect($(vis.element).find('.visAxis__spacer--y').length).to.be(4); + expect($(vis.element).find('.visWrapper__chart').length).to.be(numberOfCharts); + expect($(vis.element).find('.visAxis--x').length).to.be(2); + expect($(vis.element).find('.visAxis__splitAxes--x').length).to.be(2); + expect($(vis.element).find('.x-axis-title').length).to.be.above(0); }); }); @@ -81,35 +82,36 @@ dateHistogramArray.forEach(function(data, i) { }, data, persistedState, - vis.el + vis.element, + () => undefined ); testLayout = new Layout(visConfig); }); it('should append a div with the correct class name', function() { - expect($(vis.el).find('.chart').length).to.be(numberOfCharts); + expect($(vis.element).find('.chart').length).to.be(numberOfCharts); }); it('should bind data to the DOM element', function() { expect( - !!$(vis.el) + !!$(vis.element) .find('.chart') .data() ).to.be(true); }); it('should create children', function() { - expect(typeof $(vis.el).find('.x-axis-div')).to.be('object'); + expect(typeof $(vis.element).find('.x-axis-div')).to.be('object'); }); it('should call split function when provided', function() { - expect(typeof $(vis.el).find('.x-axis-div')).to.be('object'); + expect(typeof $(vis.element).find('.x-axis-div')).to.be('object'); }); it('should throw errors when incorrect arguments provided', function() { expect(function() { testLayout.layout({ - parent: vis.el, + parent: vis.element, type: undefined, class: 'chart', }); @@ -131,7 +133,7 @@ dateHistogramArray.forEach(function(data, i) { expect(function() { testLayout.layout({ - parent: vis.el, + parent: vis.element, type: function(d) { return d; }, @@ -143,26 +145,26 @@ dateHistogramArray.forEach(function(data, i) { describe('appendElem Method', function() { beforeEach(function() { - vis.handler.layout.appendElem(vis.el, 'svg', 'column'); + vis.handler.layout.appendElem(vis.element, 'svg', 'column'); vis.handler.layout.appendElem('.visChart', 'div', 'test'); }); it('should append DOM element to el with a class name', function() { - expect(typeof $(vis.el).find('.column')).to.be('object'); - expect(typeof $(vis.el).find('.test')).to.be('object'); + expect(typeof $(vis.element).find('.column')).to.be('object'); + expect(typeof $(vis.element).find('.test')).to.be('object'); }); }); describe('removeAll Method', function() { beforeEach(function() { - d3.select(vis.el) + d3.select(vis.element) .append('div') .attr('class', 'visualize'); - vis.handler.layout.removeAll(vis.el); + vis.handler.layout.removeAll(vis.element); }); it('should remove all DOM elements from the el', function() { - expect($(vis.el).children().length).to.be(0); + expect($(vis.element).children().length).to.be(0); }); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/layout_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/layout_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/layout_types.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/column_chart/splits.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/splits/gauge_chart/splits.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/layout/types/column_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/layout/types/column_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/layout/types/column_layout.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/types/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/point_series.js diff --git a/src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/types/testdata_linechart_percentile_result.json diff --git a/src/legacy/ui/public/vislib/__tests__/lib/vis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/vis_config.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js index c9dcd4737b51a..3f0253b4a4670 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/vis_config.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/vis_config.js @@ -20,8 +20,8 @@ import d3 from 'd3'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import { VisConfig } from '../../lib/vis_config'; -import '../../../persisted_state'; describe('Vislib VisConfig Class Test Suite', function() { let el; @@ -101,7 +101,8 @@ describe('Vislib VisConfig Class Test Suite', function() { }, data, new PersistedState(), - el + el, + () => undefined ); }) ); diff --git a/src/legacy/ui/public/vislib/__tests__/lib/x_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js similarity index 98% rename from src/legacy/ui/public/vislib/__tests__/lib/x_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js index 2fb7763756975..09fe067537c7f 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/x_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/x_axis.js @@ -21,8 +21,8 @@ import d3 from 'd3'; import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import $ from 'jquery'; -import '../../../persisted_state'; import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; @@ -124,7 +124,8 @@ describe('Vislib xAxis Class Test Suite', function() { }, data, persistedState, - $('.x-axis-div')[0] + $('.x-axis-div')[0], + () => undefined ); xAxis = new Axis(visConfig, { type: 'category', diff --git a/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js similarity index 99% rename from src/legacy/ui/public/vislib/__tests__/lib/y_axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js index 0189b8b960d32..e857aca3bf3ed 100644 --- a/src/legacy/ui/public/vislib/__tests__/lib/y_axis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/y_axis.js @@ -21,8 +21,8 @@ import _ from 'lodash'; import d3 from 'd3'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; +import 'ui/persisted_state'; import $ from 'jquery'; -import '../../../persisted_state'; import { Axis } from '../../lib/axis'; import { VisConfig } from '../../lib/vis_config'; @@ -97,7 +97,8 @@ function createData(seriesData) { }, data, persistedState, - node + node, + () => undefined ); return new YAxis( visConfig, diff --git a/src/legacy/ui/public/vislib/__tests__/vis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/vis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js index f10f44b7100d8..a6d1c4daf5d2c 100644 --- a/src/legacy/ui/public/vislib/__tests__/vis.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/vis.js @@ -18,16 +18,17 @@ */ import _ from 'lodash'; -import expect from '@kbn/expect'; +import $ from 'jquery'; import ngMock from 'ng_mock'; -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import columns from 'fixtures/vislib/mock_data/date_histogram/_columns'; -import rows from 'fixtures/vislib/mock_data/date_histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; -import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../persisted_state'; +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + +import series from './lib/fixtures/mock_data/date_histogram/_series'; +import columns from './lib/fixtures/mock_data/date_histogram/_columns'; +import rows from './lib/fixtures/mock_data/date_histogram/_rows'; +import stackedSeries from './lib/fixtures/mock_data/date_histogram/_stacked_series'; +import getFixturesVislibVisFixtureProvider from './lib/fixtures/_vis_fixture'; const dataArray = [series, columns, rows, stackedSeries]; @@ -45,9 +46,10 @@ dataArray.forEach(function(data, i) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(); persistedState = new ($injector.get('PersistedState'))(); - secondVis = Private(FixturesVislibVisFixtureProvider)(); + secondVis = getVis(); }) ); @@ -99,7 +101,7 @@ dataArray.forEach(function(data, i) { }); it('should not remove visualizations that have not been destroyed', function() { - expect($(vis.el).find('.visWrapper').length).to.be(1); + expect($(vis.element).find('.visWrapper').length).to.be(1); }); }); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/area_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js similarity index 93% rename from src/legacy/ui/public/vislib/__tests__/visualizations/area_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js index d81fd66f1e111..7fe350bd85e05 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/area_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/area_chart.js @@ -18,20 +18,22 @@ */ import d3 from 'd3'; -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; - import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; + const dataTypesArray = { - 'series pos': require('fixtures/vislib/mock_data/date_histogram/_series'), - 'series pos neg': require('fixtures/vislib/mock_data/date_histogram/_series_pos_neg'), - 'series neg': require('fixtures/vislib/mock_data/date_histogram/_series_neg'), - 'term columns': require('fixtures/vislib/mock_data/terms/_columns'), - 'range rows': require('fixtures/vislib/mock_data/range/_rows'), - stackedSeries: require('fixtures/vislib/mock_data/date_histogram/_stacked_series'), + 'series pos': require('../lib/fixtures/mock_data/date_histogram/_series'), + 'series pos neg': require('../lib/fixtures/mock_data/date_histogram/_series_pos_neg'), + 'series neg': require('../lib/fixtures/mock_data/date_histogram/_series_neg'), + 'term columns': require('../lib/fixtures/mock_data/terms/_columns'), + 'range rows': require('../lib/fixtures/mock_data/range/_rows'), + stackedSeries: require('../lib/fixtures/mock_data/date_histogram/_stacked_series'), }; const visLibParams = { @@ -49,7 +51,8 @@ _.forOwn(dataTypesArray, function(dataType, dataTypeName) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(dataType, persistedState); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js similarity index 93% rename from src/legacy/ui/public/vislib/__tests__/visualizations/chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js index f876db0aa6b22..088d3377af4dd 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/chart.js @@ -18,15 +18,16 @@ */ import d3 from 'd3'; -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { VislibVisProvider } from '../../vis'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + import { Chart } from '../../visualizations/_chart'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; describe('Vislib _chart Test Suite', function() { let persistedState; - let Vis; let vis; let el; let myChart; @@ -111,7 +112,7 @@ describe('Vislib _chart Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - Vis = Private(VislibVisProvider); + const getVis = getFixturesVislibVisFixtureProvider(Private); persistedState = new ($injector.get('PersistedState'))(); el = d3 @@ -126,7 +127,7 @@ describe('Vislib _chart Test Suite', function() { zeroFill: true, }; - vis = new Vis(el[0][0], config); + vis = getVis(config, el[0][0]); vis.render(data, persistedState); myChart = vis.handler.charts[0]; diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/column_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js similarity index 91% rename from src/legacy/ui/public/vislib/__tests__/visualizations/column_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js index 2253783e7e644..d02060ef29bdd 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/column_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/column_chart.js @@ -17,24 +17,25 @@ * under the License. */ -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; import d3 from 'd3'; +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; -import termsColumns from 'fixtures/vislib/mock_data/terms/_columns'; -import histogramRows from 'fixtures/vislib/mock_data/histogram/_rows'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; -import { seriesMonthlyInterval } from 'fixtures/vislib/mock_data/date_histogram/_series_monthly_interval'; -import { rowsSeriesWithHoles } from 'fixtures/vislib/mock_data/date_histogram/_rows_series_with_holes'; -import rowsWithZeros from 'fixtures/vislib/mock_data/date_histogram/_rows'; +import series from '../lib/fixtures/mock_data/date_histogram/_series'; +import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; +import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; +import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; +import histogramRows from '../lib/fixtures/mock_data/histogram/_rows'; +import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; +import { seriesMonthlyInterval } from '../lib/fixtures/mock_data/date_histogram/_series_monthly_interval'; +import { rowsSeriesWithHoles } from '../lib/fixtures/mock_data/date_histogram/_rows_series_with_holes'; +import rowsWithZeros from '../lib/fixtures/mock_data/date_histogram/_rows'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -69,7 +70,8 @@ dataTypesArray.forEach(function(dataType) { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(data, persistedState); @@ -257,7 +259,8 @@ describe('stackData method - data set with zeros in percentage mode', function() beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); }) @@ -307,7 +310,8 @@ describe('datumWidth - split chart data set with holes', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(rowsSeriesWithHoles, persistedState); @@ -344,7 +348,8 @@ describe('datumWidth - monthly interval', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); vis.on('brush', _.noop); vis.render(seriesMonthlyInterval, persistedState); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js similarity index 94% rename from src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js index 45d9d4a2e374b..074b34e1c03c4 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/gauge_chart.js @@ -17,13 +17,15 @@ * under the License. */ -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import $ from 'jquery'; import _ from 'lodash'; -import data from 'fixtures/vislib/mock_data/terms/_seriesMultiple'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + +import data from '../lib/fixtures/mock_data/terms/_seriesMultiple'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; describe('Vislib Gauge Chart Test Suite', function() { let PersistedState; @@ -89,7 +91,7 @@ describe('Vislib Gauge Chart Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vislibVis = Private(FixturesVislibVisFixtureProvider); + vislibVis = getFixturesVislibVisFixtureProvider(Private); PersistedState = $injector.get('PersistedState'); generateVis(); }) diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/heatmap_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/visualizations/heatmap_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js index f1dc4bf07ee31..bf1dbad0b44cf 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/heatmap_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/heatmap_chart.js @@ -17,20 +17,21 @@ * under the License. */ -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; import d3 from 'd3'; +import expect from '@kbn/expect'; +import 'ui/persisted_state'; + // Data -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; -import termsColumns from 'fixtures/vislib/mock_data/terms/_columns'; -import stackedSeries from 'fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import series from '../lib/fixtures/mock_data/date_histogram/_series'; +import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; +import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; +import termsColumns from '../lib/fixtures/mock_data/terms/_columns'; +import stackedSeries from '../lib/fixtures/mock_data/date_histogram/_stacked_series'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; // tuple, with the format [description, mode, data] const dataTypesArray = [ @@ -74,7 +75,7 @@ describe('Vislib Heatmap Chart Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vislibVis = Private(FixturesVislibVisFixtureProvider); + vislibVis = getFixturesVislibVisFixtureProvider(Private); PersistedState = $injector.get('PersistedState'); generateVis(); }) diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/line_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js similarity index 92% rename from src/legacy/ui/public/vislib/__tests__/visualizations/line_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js index 354be1f0ced0f..d010944a19e47 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/line_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/line_chart.js @@ -20,18 +20,19 @@ import d3 from 'd3'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; +import $ from 'jquery'; import _ from 'lodash'; +import 'ui/persisted_state'; + // Data -import seriesPos from 'fixtures/vislib/mock_data/date_histogram/_series'; -import seriesPosNeg from 'fixtures/vislib/mock_data/date_histogram/_series_pos_neg'; -import seriesNeg from 'fixtures/vislib/mock_data/date_histogram/_series_neg'; -import histogramColumns from 'fixtures/vislib/mock_data/histogram/_columns'; -import rangeRows from 'fixtures/vislib/mock_data/range/_rows'; -import termSeries from 'fixtures/vislib/mock_data/terms/_series'; -import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import '../../../persisted_state'; +import seriesPos from '../lib/fixtures/mock_data/date_histogram/_series'; +import seriesPosNeg from '../lib/fixtures/mock_data/date_histogram/_series_pos_neg'; +import seriesNeg from '../lib/fixtures/mock_data/date_histogram/_series_neg'; +import histogramColumns from '../lib/fixtures/mock_data/histogram/_columns'; +import rangeRows from '../lib/fixtures/mock_data/range/_rows'; +import termSeries from '../lib/fixtures/mock_data/terms/_series'; +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; const dataTypes = [ ['series pos', seriesPos], @@ -54,6 +55,7 @@ describe('Vislib Line Chart', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { + const getVis = getFixturesVislibVisFixtureProvider(Private); const visLibParams = { type: 'line', addLegend: true, @@ -61,10 +63,10 @@ describe('Vislib Line Chart', function() { drawLinesBetweenPoints: true, }; - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); - vis.on('brush', _.noop); vis.render(data, persistedState); + vis.on('brush', _.noop); }) ); diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js similarity index 94% rename from src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js index 9d299c4d3a5df..381dfcd387cc2 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/pie_chart.js @@ -18,17 +18,20 @@ */ import d3 from 'd3'; -import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; -import fixtures from 'fixtures/fake_hierarchical_data'; import $ from 'jquery'; -import FixturesVislibVisFixtureProvider from 'fixtures/vislib/_vis_fixture'; -import { Vis } from '../../../vis'; -import '../../../persisted_state'; + +import expect from '@kbn/expect'; +// TODO: Remove ui imports once converting to jest +import 'ui/persisted_state'; +import { vislibSlicesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; + +import fixtures from 'fixtures/fake_hierarchical_data'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { vislibSlicesResponseHandlerProvider } from '../../../vis/response_handlers/vislib'; -import { tabifyAggResponse } from '../../../agg_response/tabify'; + +import getFixturesVislibVisFixtureProvider from '../lib/fixtures/_vis_fixture'; +import { Vis, tabifyAggResponse } from '../../../legacy_imports'; const rowAgg = [ { type: 'avg', schema: 'metric', params: { field: 'bytes' } }, @@ -129,7 +132,8 @@ describe('No global chart settings', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - chart1 = Private(FixturesVislibVisFixtureProvider)(visLibParams1); + const getVis = getFixturesVislibVisFixtureProvider(Private); + chart1 = getVis(visLibParams1); persistedState = new ($injector.get('PersistedState'))(); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); responseHandler = vislibSlicesResponseHandlerProvider().handler; @@ -163,7 +167,7 @@ describe('No global chart settings', function() { }); it('should render chart titles for all charts', function() { - expect($(chart1.el).find('.visAxis__splitTitles--y').length).to.be(1); + expect($(chart1.element).find('.visAxis__splitTitles--y').length).to.be(1); }); describe('_validatePieData method', function() { @@ -221,7 +225,8 @@ describe('Vislib PieChart Class Test Suite', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(function(Private, $injector) { - vis = Private(FixturesVislibVisFixtureProvider)(visLibParams); + const getVis = getFixturesVislibVisFixtureProvider(Private); + vis = getVis(visLibParams); persistedState = new ($injector.get('PersistedState'))(); indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); responseHandler = vislibSlicesResponseHandlerProvider().handler; diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js similarity index 97% rename from src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js index cfe61d6f35198..ec22d43c08cb2 100644 --- a/src/legacy/ui/public/vislib/__tests__/visualizations/time_marker.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/time_marker.js @@ -20,8 +20,8 @@ import d3 from 'd3'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import series from 'fixtures/vislib/mock_data/date_histogram/_series'; -import terms from 'fixtures/vislib/mock_data/terms/_columns'; +import series from '../lib/fixtures/mock_data/date_histogram/_series'; +import terms from '../lib/fixtures/mock_data/terms/_columns'; import $ from 'jquery'; import { TimeMarker } from '../../visualizations/time_marker'; diff --git a/src/legacy/ui/public/vislib/__tests__/visualizations/vis_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js similarity index 100% rename from src/legacy/ui/public/vislib/__tests__/visualizations/vis_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/visualizations/vis_types.js diff --git a/src/legacy/ui/public/vislib/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/_index.scss diff --git a/src/legacy/ui/public/vislib/_variables.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/_variables.scss similarity index 100% rename from src/legacy/ui/public/vislib/_variables.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/_variables.scss diff --git a/src/legacy/ui/public/vislib/components/labels/data_array.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/data_array.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/data_array.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/data_array.js diff --git a/src/legacy/ui/public/vislib/components/labels/flatten_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/flatten_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/flatten_series.js diff --git a/src/legacy/ui/public/vislib/components/labels/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/index.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/index.js diff --git a/src/legacy/ui/public/vislib/components/labels/labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/labels.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/labels.js diff --git a/src/legacy/ui/public/vislib/components/labels/truncate_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/truncate_labels.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/truncate_labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/truncate_labels.js diff --git a/src/legacy/ui/public/vislib/components/labels/uniq_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js similarity index 100% rename from src/legacy/ui/public/vislib/components/labels/uniq_labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/labels/uniq_labels.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/flatten_data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/flatten_data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/flatten_data.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/inject_zeros.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/inject_zeros.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/inject_zeros.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/inject_zeros.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/ordered_x_keys.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/ordered_x_keys.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/ordered_x_keys.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/ordered_x_keys.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/uniq_keys.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/uniq_keys.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/uniq_keys.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/uniq_keys.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/zero_fill_data_array.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_fill_data_array.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/zero_fill_data_array.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_fill_data_array.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/zero_filled_array.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_filled_array.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/zero_filled_array.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_filled_array.js diff --git a/src/legacy/ui/public/vislib/components/zero_injection/zero_injection.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_injection.test.js similarity index 100% rename from src/legacy/ui/public/vislib/components/zero_injection/zero_injection.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/components/zero_injection/zero_injection.test.js diff --git a/src/legacy/ui/public/vislib/errors.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/errors.ts similarity index 95% rename from src/legacy/ui/public/vislib/errors.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/errors.ts index 0c6e720f0ef32..9014349c38d25 100644 --- a/src/legacy/ui/public/vislib/errors.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/errors.ts @@ -19,7 +19,7 @@ /* eslint-disable max-classes-per-file */ -import { KbnError } from '../../../../plugins/kibana_utils/public'; +import { KbnError } from '../../../../../plugins/kibana_utils/public'; export class VislibError extends KbnError { constructor(message: string) { diff --git a/src/legacy/ui/public/vislib/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/index.js similarity index 100% rename from src/legacy/ui/public/vislib/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/index.js diff --git a/src/legacy/ui/public/vislib/lib/_alerts.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_alerts.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/_alerts.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_alerts.scss diff --git a/src/legacy/ui/public/vislib/lib/_data_label.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_data_label.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/_data_label.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_data_label.js diff --git a/src/legacy/ui/public/vislib/lib/_error_handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_error_handler.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/_error_handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_error_handler.js diff --git a/src/legacy/ui/public/vislib/lib/_handler.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_handler.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/_handler.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_handler.scss diff --git a/src/legacy/ui/public/vislib/lib/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/_index.scss diff --git a/src/legacy/ui/public/vislib/lib/alerts.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/alerts.js similarity index 97% rename from src/legacy/ui/public/vislib/lib/alerts.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/alerts.js index cf79dabf1b07e..086b4e31be1a3 100644 --- a/src/legacy/ui/public/vislib/lib/alerts.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/alerts.js @@ -71,11 +71,11 @@ export class Alerts { const alerts = this.alerts; const vis = this.vis; - $(vis.el) + $(vis.element) .find('.visWrapper__alerts') .append($('
').addClass('visAlerts__tray')); if (!alerts.size()) return; - $(vis.el) + $(vis.element) .find('.visAlerts__tray') .append(alerts.value()); } @@ -89,13 +89,13 @@ export class Alerts { }; if (this.alertDefs.find(alertDef => alertDef.msg === alert.msg)) return; this.alertDefs.push(alert); - $(vis.el) + $(vis.element) .find('.visAlerts__tray') .append(this._addAlert(alert)); } destroy() { - $(this.vis.el) + $(this.vis.element) .find('.visWrapper__alerts') .remove(); } diff --git a/src/legacy/ui/public/vislib/lib/axis/axis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_config.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_config.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_config.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_labels.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_scale.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_scale.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_scale.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_scale.js diff --git a/src/legacy/ui/public/vislib/lib/axis/axis_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_title.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/axis_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_title.js diff --git a/src/legacy/ui/public/vislib/lib/axis/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/index.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/index.js diff --git a/src/legacy/ui/public/vislib/lib/axis/scale_modes.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/scale_modes.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/scale_modes.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/scale_modes.js diff --git a/src/legacy/ui/public/vislib/lib/axis/time_ticks.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/time_ticks.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.js diff --git a/src/legacy/ui/public/vislib/lib/axis/time_ticks.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.test.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/axis/time_ticks.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/time_ticks.test.js diff --git a/src/legacy/ui/public/vislib/lib/chart_grid.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_grid.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/chart_grid.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_grid.js diff --git a/src/legacy/ui/public/vislib/lib/chart_title.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/chart_title.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js index f79d14a3deaa6..1c84f98614b05 100644 --- a/src/legacy/ui/public/vislib/lib/chart_title.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/chart_title.js @@ -19,8 +19,9 @@ import d3 from 'd3'; import _ from 'lodash'; + import { ErrorHandler } from './_error_handler'; -import { Tooltip } from '../../vis/components/tooltip'; +import { Tooltip } from '../../legacy_imports'; export class ChartTitle extends ErrorHandler { constructor(visConfig) { diff --git a/src/legacy/ui/public/vislib/lib/data.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/data.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js index c10449c681a3c..c7824c43eeec5 100644 --- a/src/legacy/ui/public/vislib/lib/data.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js @@ -19,11 +19,11 @@ import d3 from 'd3'; import _ from 'lodash'; + import { injectZeros } from '../components/zero_injection/inject_zeros'; import { orderXValues } from '../components/zero_injection/ordered_x_keys'; import { labels } from '../components/labels/labels'; -import { vislibColor } from '../../vis/components/color/color'; -import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; +import { getFormat } from '../../legacy_imports'; /** * Provides an API for pulling values off the data @@ -35,8 +35,9 @@ import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; * @param attr {Object|*} Visualization options */ export class Data { - constructor(data, uiState) { + constructor(data, uiState, vislibColor) { this.uiState = uiState; + this.vislibColor = vislibColor; this.data = this.copyDataObj(data); this.type = this.getDataType(); this._cleanVisData(); @@ -472,7 +473,7 @@ export class Data { const defaultColors = this.uiState.get('vis.defaultColors'); const overwriteColors = this.uiState.get('vis.colors'); const colors = defaultColors ? _.defaults({}, overwriteColors, defaultColors) : overwriteColors; - return vislibColor(this.getLabels(), colors); + return this.vislibColor(this.getLabels(), colors); } /** @@ -482,7 +483,7 @@ export class Data { * @returns {Function} Performs lookup on string and returns hex color */ getPieColorFunc() { - return vislibColor( + return this.vislibColor( this.pieNames(this.getVisData()).map(function(d) { return d.label; }), diff --git a/src/legacy/ui/public/vislib/lib/dispatch.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/dispatch.js similarity index 92% rename from src/legacy/ui/public/vislib/lib/dispatch.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/dispatch.js index fb5cc127c27fd..404f7ef82d97f 100644 --- a/src/legacy/ui/public/vislib/lib/dispatch.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/dispatch.js @@ -20,10 +20,8 @@ import d3 from 'd3'; import { get } from 'lodash'; import $ from 'jquery'; -import { SimpleEmitter } from '../../utils/simple_emitter'; -import chrome from 'ui/chrome'; -const config = chrome.getUiSettingsClient(); +import { SimpleEmitter } from '../../legacy_imports'; /** * Handles event responses @@ -33,9 +31,10 @@ const config = chrome.getUiSettingsClient(); * @param handler {Object} Reference to Handler Class Object */ export class Dispatch extends SimpleEmitter { - constructor(handler) { + constructor(handler, uiSettings) { super(); this.handler = handler; + this.uiSettings = uiSettings; this._listeners = {}; } @@ -196,7 +195,7 @@ export class Dispatch extends SimpleEmitter { const addEvent = this.addEvent; const $el = this.handler.el; if (!this.handler.highlight) { - this.handler.highlight = self.highlight; + this.handler.highlight = self.getHighlighter(self.uiSettings); } function hover(d, i) { @@ -289,21 +288,23 @@ export class Dispatch extends SimpleEmitter { } /** - * Highlight the element that is under the cursor + * return function to Highlight the element that is under the cursor * by reducing the opacity of all the elements on the graph. - * @param element {d3.Selection} - * @method highlight + * @param uiSettings + * @method getHighlighter */ - highlight(element) { - const label = this.getAttribute('data-label'); - if (!label) return; - const dimming = config.get('visualization:dimmingOpacity'); - $(element) - .parent() - .find('[data-label]') - .css('opacity', 1) //Opacity 1 is needed to avoid the css application - .not((els, el) => String($(el).data('label')) === label) - .css('opacity', justifyOpacity(dimming)); + getHighlighter(uiSettings) { + return function highlight(element) { + const label = this.getAttribute('data-label'); + if (!label) return; + const dimming = uiSettings.get('visualization:dimmingOpacity'); + $(element) + .parent() + .find('[data-label]') + .css('opacity', 1) //Opacity 1 is needed to avoid the css application + .not((els, el) => String($(el).data('label')) === label) + .css('opacity', justifyOpacity(dimming)); + }; } /** diff --git a/src/legacy/ui/public/vislib/lib/handler.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js similarity index 96% rename from src/legacy/ui/public/vislib/lib/handler.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js index 6047faa0c6b15..b887b61578cc4 100644 --- a/src/legacy/ui/public/vislib/lib/handler.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/handler.js @@ -20,15 +20,16 @@ import d3 from 'd3'; import _ from 'lodash'; import MarkdownIt from 'markdown-it'; + import { NoResults } from '../errors'; -import { Binder } from '../../binder'; import { Layout } from './layout/layout'; import { ChartTitle } from './chart_title'; import { Alerts } from './alerts'; import { Axis } from './axis/axis'; import { ChartGrid as Grid } from './chart_grid'; import { visTypes as chartTypes } from '../visualizations/vis_types'; -import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; +import { Binder } from '../../legacy_imports'; +import { dispatchRenderComplete } from '../../../../../../plugins/kibana_utils/public'; const markdownIt = new MarkdownIt({ html: false, @@ -45,9 +46,10 @@ const markdownIt = new MarkdownIt({ * create the visualization */ export class Handler { - constructor(vis, visConfig) { + constructor(vis, visConfig, deps) { this.el = visConfig.get('el'); this.ChartClass = chartTypes[visConfig.get('type')]; + this.deps = deps; this.charts = []; this.vis = vis; @@ -151,7 +153,7 @@ export class Handler { let loadedCount = 0; const chartSelection = selection.selectAll('.chart'); chartSelection.each(function(chartData) { - const chart = new self.ChartClass(self, this, chartData); + const chart = new self.ChartClass(self, this, chartData, self.deps); self.vis.eventNames().forEach(function(event) { self.enable(event, chart); diff --git a/src/legacy/ui/public/vislib/lib/layout/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_index.scss diff --git a/src/legacy/ui/public/vislib/lib/layout/_layout.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/_layout.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/_layout.scss diff --git a/src/legacy/ui/public/vislib/lib/layout/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/index.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/index.js diff --git a/src/legacy/ui/public/vislib/lib/layout/layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout.js diff --git a/src/legacy/ui/public/vislib/lib/layout/layout_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout_types.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/layout_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/layout_types.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_title_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/chart_title_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/chart_title_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/x_axis_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/x_axis_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/x_axis_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/y_axis_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/column_chart/y_axis_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/column_chart/y_axis_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/gauge_chart/chart_title_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/splits/pie_chart/chart_title_split.js diff --git a/src/legacy/ui/public/vislib/lib/layout/types/column_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/column_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/types/column_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/column_layout.js diff --git a/src/legacy/ui/public/vislib/lib/layout/types/gauge_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/gauge_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/types/gauge_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/gauge_layout.js diff --git a/src/legacy/ui/public/vislib/lib/layout/types/pie_layout.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/pie_layout.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/layout/types/pie_layout.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/layout/types/pie_layout.js diff --git a/src/legacy/ui/public/vislib/lib/types/gauge.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/gauge.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/types/gauge.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/gauge.js diff --git a/src/legacy/ui/public/vislib/lib/types/index.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/index.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/types/index.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/index.js diff --git a/src/legacy/ui/public/vislib/lib/types/pie.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/pie.js similarity index 100% rename from src/legacy/ui/public/vislib/lib/types/pie.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/pie.js diff --git a/src/legacy/ui/public/vislib/lib/types/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/types/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js index 332f7408ebc66..eab3bc02f4eec 100644 --- a/src/legacy/ui/public/vislib/lib/types/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.js @@ -202,7 +202,7 @@ export const vislibPointSeriesTypes = { 'Positive and negative values are not accurately represented by stacked ' + 'area charts. Either changing the chart mode to "overlap" or using a ' + 'bar chart is recommended.', - test: function(vis, data) { + test: function(_, data) { if (!data.shouldBeStacked() || data.maxNumberOfSeries() < 2) return; const hasPos = data.getYMax(data._getY) > 0; @@ -216,7 +216,7 @@ export const vislibPointSeriesTypes = { 'Parts of or the entire area chart might not be displayed due to null ' + 'values in the data. A line chart is recommended when displaying data ' + 'with null values.', - test: function(vis, data) { + test: function(_, data) { return data.hasNullValues(); }, }, @@ -229,7 +229,7 @@ export const vislibPointSeriesTypes = { const tooManySeries = defaults.charts.length && defaults.charts[0].series.length > cfg.heatmapMaxBuckets; if (hasCharts && tooManySeries) { - defaults.error = i18n.translate('common.ui.vislib.heatmap.maxBucketsText', { + defaults.error = i18n.translate('kbnVislibVisTypes.vislib.heatmap.maxBucketsText', { defaultMessage: 'There are too many series defined ({nr}). The configured maximum is {max}.', values: { diff --git a/src/legacy/ui/public/vislib/lib/types/point_series.test.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.test.js similarity index 98% rename from src/legacy/ui/public/vislib/lib/types/point_series.test.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.test.js index cada6127282d8..38a6be548594f 100644 --- a/src/legacy/ui/public/vislib/lib/types/point_series.test.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/types/point_series.test.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import stackedSeries from '../../../../../../fixtures/vislib/mock_data/date_histogram/_stacked_series'; +import stackedSeries from '../../__tests__/lib/fixtures/mock_data/date_histogram/_stacked_series'; import { vislibPointSeriesTypes } from './point_series'; describe('vislibPointSeriesTypes', () => { diff --git a/src/legacy/ui/public/vislib/lib/vis_config.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/vis_config.js similarity index 93% rename from src/legacy/ui/public/vislib/lib/vis_config.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/vis_config.js index 5f10e89474809..091e6b1752d8d 100644 --- a/src/legacy/ui/public/vislib/lib/vis_config.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/vis_config.js @@ -35,8 +35,8 @@ const DEFAULT_VIS_CONFIG = { }; export class VisConfig { - constructor(visConfigArgs, data, uiState, el) { - this.data = new Data(data, uiState); + constructor(visConfigArgs, data, uiState, el, vislibColor) { + this.data = new Data(data, uiState, vislibColor); const visType = visTypes[visConfigArgs.type]; const typeDefaults = visType(visConfigArgs, this.data); diff --git a/src/legacy/ui/public/vislib/partials/touchdown.tmpl.html b/src/legacy/core_plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html similarity index 100% rename from src/legacy/ui/public/vislib/partials/touchdown.tmpl.html rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/partials/touchdown.tmpl.html diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/vis.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vis.js new file mode 100644 index 0000000000000..32afb6a008b61 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vis.js @@ -0,0 +1,184 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; +import d3 from 'd3'; +import { EventEmitter } from 'events'; + +import { VislibError } from './errors'; +import { VisConfig } from './lib/vis_config'; +import { Handler } from './lib/handler'; + +/** + * Creates the visualizations. + * + * @class Vis + * @constructor + * @param element {HTMLElement} jQuery selected HTML element + * @param config {Object} Parameters that define the chart type and chart options + */ +export class Vis extends EventEmitter { + constructor(element, visConfigArgs, deps) { + super(); + this.element = element.get ? element.get(0) : element; + this.visConfigArgs = _.cloneDeep(visConfigArgs); + this.visConfigArgs.dimmingOpacity = deps.uiSettings.get('visualization:dimmingOpacity'); + this.visConfigArgs.heatmapMaxBuckets = deps.uiSettings.get('visualization:heatmap:maxBuckets'); + this.deps = deps; + } + + hasLegend() { + return this.visConfigArgs.addLegend; + } + + initVisConfig(data, uiState) { + this.data = data; + this.uiState = uiState; + this.visConfig = new VisConfig( + this.visConfigArgs, + this.data, + this.uiState, + this.element, + this.deps.vislibColor + ); + } + + /** + * Renders the visualization + * + * @method render + * @param data {Object} Elasticsearch query results + */ + render(data, uiState) { + if (!data) { + throw new Error('No valid data!'); + } + + if (this.handler) { + this.data = null; + this._runOnHandler('destroy'); + } + + this.initVisConfig(data, uiState); + + this.handler = new Handler(this, this.visConfig, this.deps); + this._runOnHandler('render'); + } + + getLegendLabels() { + return this.visConfig ? this.visConfig.get('legend.labels', null) : null; + } + + getLegendColors() { + return this.visConfig ? this.visConfig.get('legend.colors', null) : null; + } + + _runOnHandler(method) { + try { + this.handler[method](); + } catch (error) { + if (error instanceof VislibError) { + error.displayToScreen(this.handler); + } else { + throw error; + } + } + } + + /** + * Destroys the visualization + * Removes chart and all elements associated with it. + * Removes chart and all elements associated with it. + * Remove event listeners and pass destroy call down to owned objects. + * + * @method destroy + */ + destroy() { + const selection = d3.select(this.element).select('.visWrapper'); + + if (this.handler) this._runOnHandler('destroy'); + + selection.remove(); + } + + /** + * Sets attributes on the visualization + * + * @method set + * @param name {String} An attribute name + * @param val {*} Value to which the attribute name is set + */ + set(name, val) { + this.visConfigArgs[name] = val; + this.render(this.data, this.uiState); + } + + /** + * Gets attributes from the visualization + * + * @method get + * @param name {String} An attribute name + * @returns {*} The value of the attribute name + */ + get(name) { + return this.visConfig.get(name); + } + + /** + * Turns on event listeners. + * + * @param event {String} + * @param listener{Function} + * @returns {*} + */ + on(event, listener) { + const first = this.listenerCount(event) === 0; + const ret = EventEmitter.prototype.on.call(this, event, listener); + const added = this.listenerCount(event) > 0; + + // if this is the first listener added for the event + // enable the event in the handler + if (first && added && this.handler) this.handler.enable(event); + + return ret; + } + + /** + * Turns off event listeners. + * + * @param event {String} + * @param listener{Function} + * @returns {*} + */ + off(event, listener) { + const last = this.listenerCount(event) === 1; + const ret = EventEmitter.prototype.off.call(this, event, listener); + const removed = this.listenerCount(event) === 0; + + // Once all listeners are removed, disable the events in the handler + if (last && removed && this.handler) this.handler.disable(event); + return ret; + } + + removeAllListeners(event) { + const ret = EventEmitter.prototype.removeAllListeners.call(this, event); + this.handler.disable(event); + return ret; + } +} diff --git a/src/legacy/ui/public/vislib/vislib.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vislib.js similarity index 82% rename from src/legacy/ui/public/vislib/vislib.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/vislib.js index 847deb8273a73..024dee60ef2bf 100644 --- a/src/legacy/ui/public/vislib/vislib.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/vislib.js @@ -23,8 +23,7 @@ import './lib/types'; import './lib/layout/layout_types'; import './lib/data'; import './visualizations/vis_types'; -import { mappedColors } from '../vis/components/color/mapped_colors'; -import { VislibVisProvider } from './vis'; +import { Vis } from './vis'; // prefetched for faster optimization runs // end prefetching @@ -36,11 +35,9 @@ import { VislibVisProvider } from './vis'; * @main vislib * @return {Object} Contains the version number and the Vis Class for creating visualizations */ -export function VislibProvider(Private, $rootScope) { - $rootScope.$on('$routeChangeStart', () => mappedColors.purge()); - +export function VislibProvider() { return { version: '0.0.0', - Vis: Private(VislibVisProvider), + Vis, }; } diff --git a/src/legacy/ui/public/vislib/visualizations/_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js similarity index 81% rename from src/legacy/ui/public/vislib/visualizations/_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js index 2fbd96d6a070a..ac6e8130a846a 100644 --- a/src/legacy/ui/public/vislib/visualizations/_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/_chart.js @@ -19,12 +19,10 @@ import d3 from 'd3'; import _ from 'lodash'; + import { dataLabel } from '../lib/_data_label'; import { Dispatch } from '../lib/dispatch'; -import { Tooltip } from '../../vis/components/tooltip'; -import { getFormat } from '../../visualize/loader/pipeline_helpers/utilities'; -import { getHierarchicalTooltipFormatter } from '../../vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { getPointSeriesTooltipFormatter } from '../../vis/components/tooltip/_pointseries_tooltip_formatter'; +import { Tooltip, getFormat } from '../../legacy_imports'; /** * The Base Class for all visualizations. @@ -36,26 +34,26 @@ import { getPointSeriesTooltipFormatter } from '../../vis/components/tooltip/_po * @param chartData {Object} Elasticsearch query results for this specific chart */ export class Chart { - constructor(handler, el, chartData) { + constructor(handler, element, chartData, deps) { this.handler = handler; - this.chartEl = el; + this.chartEl = element; this.chartData = chartData; this.tooltips = []; - const events = (this.events = new Dispatch(handler)); + const events = (this.events = new Dispatch(handler, deps.uiSettings)); const fieldFormatter = getFormat(this.handler.data.get('tooltipFormatter')); const tooltipFormatterProvider = this.handler.visConfig.get('type') === 'pie' - ? getHierarchicalTooltipFormatter() - : getPointSeriesTooltipFormatter(); + ? deps.getHierarchicalTooltipFormatter() + : deps.getPointSeriesTooltipFormatter(); const tooltipFormatter = tooltipFormatterProvider(fieldFormatter); if (this.handler.visConfig && this.handler.visConfig.get('addTooltip', false)) { - const $el = this.handler.el; + const element = this.handler.el; // Add tooltip - this.tooltip = new Tooltip('chart', $el, tooltipFormatter, events); + this.tooltip = new Tooltip('chart', element, tooltipFormatter, events); this.tooltips.push(this.tooltip); } diff --git a/src/legacy/ui/public/vislib/visualizations/gauge_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js similarity index 97% rename from src/legacy/ui/public/vislib/visualizations/gauge_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js index c67b1b8881d69..729c664032603 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauge_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauge_chart.js @@ -22,8 +22,8 @@ import { Chart } from './_chart'; import { gaugeTypes } from './gauges/gauge_types'; export class GaugeChart extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); + constructor(handler, chartEl, chartData, deps) { + super(handler, chartEl, chartData, deps); this.gaugeConfig = handler.visConfig.get('gauge', {}); this.gauge = new gaugeTypes[this.gaugeConfig.type](this); } diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/gauges/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_index.scss diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_meter.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/gauges/_meter.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/_meter.scss diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/gauge_types.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/gauges/gauge_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/gauge_types.js diff --git a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/meter.js similarity index 99% rename from src/legacy/ui/public/vislib/visualizations/gauges/meter.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/meter.js index 25da3ba957181..f519914662251 100644 --- a/src/legacy/ui/public/vislib/visualizations/gauges/meter.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/gauges/meter.js @@ -19,7 +19,8 @@ import d3 from 'd3'; import _ from 'lodash'; -import { getHeatmapColors } from '../../components/color/heatmap_color'; + +import { getHeatmapColors } from '../../../legacy_imports'; const arcAngles = { angleFactor: 0.75, diff --git a/src/legacy/ui/public/vislib/visualizations/pie_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js similarity index 99% rename from src/legacy/ui/public/vislib/visualizations/pie_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js index 6d51c69892bc0..a2cbf0fe295ce 100644 --- a/src/legacy/ui/public/vislib/visualizations/pie_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/pie_chart.js @@ -42,8 +42,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class PieChart extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); + constructor(handler, chartEl, chartData, deps) { + super(handler, chartEl, chartData, deps); const charts = this.handler.data.getVisData(); this._validatePieData(charts); this._attr = _.defaults(handler.visConfig.get('chart', {}), defaults); diff --git a/src/legacy/ui/public/vislib/visualizations/point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js similarity index 97% rename from src/legacy/ui/public/vislib/visualizations/point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js index 84dbea2ccc823..c838c51d34bf5 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series.js @@ -20,7 +20,8 @@ import d3 from 'd3'; import _ from 'lodash'; import $ from 'jquery'; -import { Tooltip } from '../../vis/components/tooltip'; + +import { Tooltip } from '../../legacy_imports'; import { Chart } from './_chart'; import { TimeMarker } from './time_marker'; import { seriesTypes } from './point_series/series_types'; @@ -39,9 +40,10 @@ const touchdownTmpl = _.template(touchdownTmplHtml); * @param chartData {Object} Elasticsearch query results for this specific chart */ export class PointSeries extends Chart { - constructor(handler, chartEl, chartData) { - super(handler, chartEl, chartData); + constructor(handler, chartEl, chartData, deps) { + super(handler, chartEl, chartData, deps); + this.deps = deps; this.handler = handler; this.chartData = chartData; this.chartEl = chartEl; @@ -255,7 +257,7 @@ export class PointSeries extends Chart { if (!seriArgs.show) return; const SeriClass = seriTypes[seriArgs.type || self.handler.visConfig.get('chart.type')] || seriTypes.line; - const series = new SeriClass(self.handler, svg, data.series[i], seriArgs); + const series = new SeriClass(self.handler, svg, data.series[i], seriArgs, this.deps); series.events = self.events; svg.call(series.draw()); self.series.push(series); diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_index.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_index.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/_index.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_index.scss diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_labels.scss b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_labels.scss similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/_labels.scss rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_labels.scss diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/_point_series.js diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js index 08147bacdcc98..274ae82271e96 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/area_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/area_chart.js @@ -43,8 +43,8 @@ const defaults = { * chart */ export class AreaChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.isOverlapping = this.seriesConfig.mode !== 'stacked'; diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js index 1f18141d86299..4b422d9b1419f 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/column_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/column_chart.js @@ -57,8 +57,8 @@ function datumWidth(defaultWidth, datum, nextDatum, scale, gutterWidth, groupCou * @param chartData {Object} Elasticsearch query results for this specific chart */ export class ColumnChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.labelOptions = _.defaults(handler.visConfig.get('labels', {}), defaults.showLabel); } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js index 1bc33bd5bbade..948cf98a64352 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/heatmap_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/heatmap_chart.js @@ -19,10 +19,12 @@ import _ from 'lodash'; import moment from 'moment'; -import { PointSeries } from './_point_series'; -import { getHeatmapColors } from '../../components/color/heatmap_color'; + import { isColorDark } from '@elastic/eui'; +import { PointSeries } from './_point_series'; +import { getHeatmapColors } from '../../../legacy_imports'; + const defaults = { color: undefined, // todo fillColor: undefined, // todo @@ -38,8 +40,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class HeatmapChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); this.handler.visConfig.set('legend', { diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js similarity index 98% rename from src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js index 57090b5424f93..0038b4401b302 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/line_chart.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/line_chart.js @@ -42,8 +42,8 @@ const defaults = { * @param chartData {Object} Elasticsearch query results for this specific chart */ export class LineChart extends PointSeries { - constructor(handler, chartEl, chartData, seriesConfigArgs) { - super(handler, chartEl, chartData, seriesConfigArgs); + constructor(handler, chartEl, chartData, seriesConfigArgs, deps) { + super(handler, chartEl, chartData, seriesConfigArgs, deps); this.seriesConfig = _.defaults(seriesConfigArgs || {}, defaults); } diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/series_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/series_types.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/point_series/series_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/point_series/series_types.js diff --git a/src/legacy/ui/public/vislib/visualizations/time_marker.d.ts b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.d.ts similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/time_marker.d.ts rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.d.ts diff --git a/src/legacy/ui/public/vislib/visualizations/time_marker.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/time_marker.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/time_marker.js diff --git a/src/legacy/ui/public/vislib/visualizations/vis_types.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/vis_types.js similarity index 100% rename from src/legacy/ui/public/vislib/visualizations/vis_types.js rename to src/legacy/core_plugins/vis_type_vislib/public/vislib/visualizations/vis_types.js diff --git a/src/legacy/core_plugins/visualizations/index.ts b/src/legacy/core_plugins/visualizations/index.ts index cf2d123d8f184..a2779cfe4346d 100644 --- a/src/legacy/core_plugins/visualizations/index.ts +++ b/src/legacy/core_plugins/visualizations/index.ts @@ -26,7 +26,7 @@ export const visualizations: LegacyPluginInitializer = kibana => publicDir: resolve(__dirname, 'public'), require: [], uiExports: { - interpreter: ['plugins/visualizations/expressions/boot'], + styleSheetPaths: resolve(__dirname, 'public/index.scss'), }, }); diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_embeddables.scss b/src/legacy/core_plugins/visualizations/public/embeddable/_embeddables.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/_embeddables.scss rename to src/legacy/core_plugins/visualizations/public/embeddable/_embeddables.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss b/src/legacy/core_plugins/visualizations/public/embeddable/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/_index.scss rename to src/legacy/core_plugins/visualizations/public/embeddable/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/_visualize_lab_disabled.scss b/src/legacy/core_plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/_visualize_lab_disabled.scss rename to src/legacy/core_plugins/visualizations/public/embeddable/_visualize_lab_disabled.scss diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/constants.ts b/src/legacy/core_plugins/visualizations/public/embeddable/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/constants.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_embeddable.tsx rename to src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_embeddable.tsx diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx similarity index 92% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx rename to src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx index 52fa937bbe047..3d2af2c591a3c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/disabled_lab_visualization.tsx +++ b/src/legacy/core_plugins/visualizations/public/embeddable/disabled_lab_visualization.tsx @@ -29,14 +29,14 @@ export function DisabledLabVisualization({ title }: { title: string }) { />
{title} }} />
diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts b/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts similarity index 89% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts index 36efc4b86d0d3..25aa77ec73579 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/get_index_pattern.ts @@ -17,10 +17,9 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; - import { VisSavedObject } from './visualize_embeddable'; import { indexPatterns, IIndexPattern } from '../../../../../plugins/data/public'; +import { getUISettings, getSavedObjects } from '../np_ready/public/services'; export async function getIndexPattern( savedVis: VisSavedObject @@ -29,8 +28,8 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const savedObjectsClient = npStart.core.savedObjects.client; - const defaultIndex = npStart.core.uiSettings.get('defaultIndex'); + const savedObjectsClient = getSavedObjects().client; + const defaultIndex = getUISettings().get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/index.ts b/src/legacy/core_plugins/visualizations/public/embeddable/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/index.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/index.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts similarity index 99% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts rename to src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts index b7a3a0f000d72..c1b049ab5e969 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -47,7 +47,7 @@ import { APPLY_FILTER_TRIGGER, } from '../../../../../plugins/embeddable/public'; import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; -import { SavedSearch } from '../discover/np_ready/types'; +import { SavedSearch } from '../../../kibana/public/discover/np_ready/types'; const getKeys = (o: T): Array => Object.keys(o) as Array; diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx similarity index 66% rename from src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx rename to src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index dd6723fb578af..3f29f97afee48 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -17,27 +17,9 @@ * under the License. */ -import 'uiExports/contextMenuActions'; -import 'uiExports/devTools'; -import 'uiExports/docViews'; -import 'uiExports/embeddableActions'; -import 'uiExports/fieldFormatEditors'; -import 'uiExports/fieldFormats'; -import 'uiExports/home'; -import 'uiExports/indexManagement'; -import 'uiExports/inspectorViews'; -import 'uiExports/savedObjectTypes'; -import 'uiExports/search'; -import 'uiExports/shareContextMenuExtensions'; -import 'uiExports/visTypes'; -import 'uiExports/visualize'; - import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; -import { npSetup, npStart } from 'ui/new_platform'; - -import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; import { @@ -46,9 +28,8 @@ import { Container, EmbeddableOutput, } from '../../../../../plugins/embeddable/public'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { showNewVisModal } from '../visualize'; -import { SavedVisualizations } from '../visualize/np_ready/types'; +import { showNewVisModal } from '../../../kibana/public/visualize/np_ready/wizard/show_new_vis'; +import { SavedVisualizations } from '../../../kibana/public/visualize/np_ready/types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; import { @@ -58,7 +39,15 @@ import { VisSavedObject, } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { TypesStart } from '../../../visualizations/public/np_ready/public/types'; + +import { + getUISettings, + getCapabilities, + getHttp, + getTypes, + getSavedObjects, + getUsageCollector, +} from '../np_ready/public/services'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -71,60 +60,48 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< VisualizationAttributes > { public readonly type = VISUALIZE_EMBEDDABLE_TYPE; - private readonly visTypes: TypesStart; static async createVisualizeEmbeddableFactory(): Promise { - return new VisualizeEmbeddableFactory(visualizations.types); + return new VisualizeEmbeddableFactory(); } - constructor(visTypes: TypesStart) { + constructor() { super({ savedObjectMetaData: { - name: i18n.translate('kbn.visualize.savedObjectName', { defaultMessage: 'Visualization' }), + name: i18n.translate('visualizations.savedObjectName', { defaultMessage: 'Visualization' }), includeFields: ['visState'], type: 'visualization', getIconForSavedObject: savedObject => { - if (!visTypes) { - return 'visualizeApp'; - } return ( - visTypes.get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp' + getTypes().get(JSON.parse(savedObject.attributes.visState).type).icon || 'visualizeApp' ); }, getTooltipForSavedObject: savedObject => { - if (!visTypes) { - return ''; - } return `${savedObject.attributes.title} (${ - visTypes.get(JSON.parse(savedObject.attributes.visState).type).title + getTypes().get(JSON.parse(savedObject.attributes.visState).type).title })`; }, showSavedObject: savedObject => { - if (!visTypes) { - return false; - } const typeName: string = JSON.parse(savedObject.attributes.visState).type; - const visType = visTypes.get(typeName); + const visType = getTypes().get(typeName); if (!visType) { return false; } - if (npStart.core.uiSettings.get('visualize:enableLabs')) { + if (getUISettings().get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; }, }, }); - - this.visTypes = visTypes; } public isEditable() { - return npStart.core.application.capabilities.visualize.save as boolean; + return getCapabilities().visualize.save as boolean; } public getDisplayName() { - return i18n.translate('kbn.embeddable.visualizations.displayName', { + return i18n.translate('visualizations.displayName', { defaultMessage: 'visualization', }); } @@ -135,16 +112,15 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< parent?: Container ): Promise { const $injector = await chrome.dangerouslyGetActiveInjector(); - const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); try { const visId = savedObject.id as string; const editUrl = visId - ? npStart.core.http.basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) + ? getHttp().basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = getUISettings().get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new DisabledLabEmbeddable(savedObject.title, input); @@ -192,18 +168,16 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< public async create() { // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. - if (this.visTypes) { - showNewVisModal( - this.visTypes, - { - editorParams: ['addToDashboard'], - }, - npStart.core.http.basePath.prepend, - npStart.core.uiSettings, - npStart.core.savedObjects, - npSetup.plugins.usageCollection - ); - } + showNewVisModal( + getTypes(), + { + editorParams: ['addToDashboard'], + }, + getHttp().basePath.prepend, + getUISettings(), + getSavedObjects(), + getUsageCollector() + ); return undefined; } } diff --git a/src/legacy/core_plugins/visualizations/public/index.scss b/src/legacy/core_plugins/visualizations/public/index.scss new file mode 100644 index 0000000000000..957d06be4daf0 --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/index.scss @@ -0,0 +1,3 @@ +@import 'src/legacy/ui/public/styles/styling_constants'; + +@import './embeddable/_index'; diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index 92d8ac2c7db3a..b750557c24b94 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -27,4 +27,7 @@ export { } from '../../../ui/public/agg_types/buckets/date_histogram'; export { createFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; export { I18nContext } from '../../../ui/public/i18n'; +export { DefaultEditorController } from '../../../ui/public/vis/editors/default/default_editor_controller'; +import chrome from '../../../ui/public/chrome'; +export { chrome as legacyChrome }; import '../../../ui/public/directives/bind'; diff --git a/src/legacy/ui/public/visualize/loader/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js similarity index 93% rename from src/legacy/ui/public/visualize/loader/vis.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js index ad946aa1212ff..81224b65f7786 100644 --- a/src/legacy/ui/public/visualize/loader/vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/vis.js @@ -29,11 +29,9 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; -import { PersistedState } from '../../persisted_state'; +import { PersistedState } from '../../../legacy_imports'; -import { start as visualizations } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; - -const visTypes = visualizations.types; +import { getTypes } from '../services'; export class Vis extends EventEmitter { constructor(visState = { type: 'histogram' }) { @@ -64,7 +62,7 @@ export class Vis extends EventEmitter { this.title = state.title || ''; const type = state.type || this.type; if (_.isString(type)) { - this.type = visTypes.get(type); + this.type = getTypes().get(type); if (!this.type) { throw new Error(`Invalid type "${type}"`); } diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts similarity index 85% rename from src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts index ddf1b11945422..24eb974f6edee 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_function.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_function.ts @@ -19,13 +19,10 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { PersistedState } from 'ui/persisted_state'; import { VisResponseValue } from 'src/plugins/visualizations/public'; import { ExpressionFunction, Render } from 'src/plugins/expressions/public'; -import { npStart } from 'ui/new_platform'; -import { start as visualizations } from '../np_ready/public/legacy'; +import { PersistedState } from '../../../legacy_imports'; +import { getTypes, getIndexPatterns, getFilterManager } from '../services'; interface Arguments { index?: string | null; @@ -90,15 +87,10 @@ export const visualization = (): ExpressionFunctionVisualization => ({ }, }, async fn(context, args, handlers) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const Private = $injector.get('Private') as any; - const indexPatterns = npStart.plugins.data.indexPatterns; - const queryFilter = Private(FilterBarQueryFilterProvider); - const visConfigParams = args.visConfig ? JSON.parse(args.visConfig) : {}; const schemas = args.schemas ? JSON.parse(args.schemas) : {}; - const visType = visualizations.types.get(args.type || 'histogram') as any; - const indexPattern = args.index ? await indexPatterns.get(args.index) : null; + const visType = getTypes().get(args.type || 'histogram') as any; + const indexPattern = args.index ? await getIndexPatterns().get(args.index) : null; const uiStateParams = args.uiState ? JSON.parse(args.uiState) : {}; const uiState = new PersistedState(uiStateParams); @@ -114,7 +106,7 @@ export const visualization = (): ExpressionFunctionVisualization => ({ filters: get(context, 'filters', null), uiState, inspectorAdapters: handlers.inspectorAdapters, - queryFilter, + queryFilter: getFilterManager(), forceFetch: true, }); } diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx similarity index 89% rename from src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx index 40648a137c141..2a12884ecf7c8 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/expressions/visualization_renderer.tsx @@ -17,12 +17,12 @@ * under the License. */ -import chrome from 'ui/chrome'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { legacyChrome } from '../../../legacy_imports'; // @ts-ignore -import { Vis } from '../../../../ui/public/visualize/loader/vis'; -import { Visualization } from '../../../visualizations/public/np_ready/public/components'; +import { Vis } from './vis'; +import { Visualization } from '../components'; export const visualization = () => ({ name: 'visualization', @@ -31,7 +31,7 @@ export const visualization = () => ({ render: async (domNode: HTMLElement, config: any, handlers: any) => { const { visData, visConfig, params } = config; const visType = config.visType || visConfig.type; - const $injector = await chrome.dangerouslyGetActiveInjector(); + const $injector = await legacyChrome.dangerouslyGetActiveInjector(); const $rootScope = $injector.get('$rootScope') as any; if (handlers.vis) { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts index 1e86aa64d1fa8..41b23b276e88d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts @@ -27,5 +27,5 @@ import { plugin } from '.'; const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core); -export const start = pluginInstance.start(npStart.core); +export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap index ddd74628e7940..90c1d4472d5ea 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -8,7 +8,7 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunct exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentage=true metric={visdimension 0 format='percent' } "`; +exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function with percentage mode should have percentage format 1`] = `"metricvis percentageMode=true metric={visdimension 0 format='percent' } "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles metric function without buckets 1`] = `"metricvis metric={visdimension 0 } metric={visdimension 1 } "`; 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 ab1664d612b35..a3bf98b5e74a3 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 @@ -305,8 +305,8 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { } let expr = `metricvis `; - expr += prepareValue('percentage', percentageMode); - expr += prepareValue('colorScheme', colorSchema); + expr += prepareValue('percentageMode', percentageMode); + expr += prepareValue('colorSchema', colorSchema); expr += prepareValue('colorMode', metricColorMode); expr += prepareValue('useRanges', useRanges); expr += prepareValue('invertColors', invertColors); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 88c5768a0b4e4..2fa85d86a794e 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -28,6 +28,10 @@ import { PluginInitializerContext } from 'src/core/public'; import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; import { coreMock } from '../../../../../../core/public/mocks'; +import { embeddablePluginMock } from '../../../../../../plugins/embeddable/public/mocks'; +import { expressionsPluginMock } from '../../../../../../plugins/expressions/public/mocks'; +import { dataPluginMock } from '../../../../../../plugins/data/public/mocks'; +import { usageCollectionPluginMock } from '../../../../../../plugins/usage_collection/public/mocks'; const createSetupContract = (): VisualizationsSetup => ({ types: { @@ -49,8 +53,15 @@ const createStartContract = (): VisualizationsStart => ({ const createInstance = async () => { const plugin = new VisualizationsPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup()); - const doStart = () => plugin.start(coreMock.createStart()); + const setup = plugin.setup(coreMock.createSetup(), { + expressions: expressionsPluginMock.createSetupContract(), + embeddable: embeddablePluginMock.createStartContract(), + usageCollection: usageCollectionPluginMock.createSetupContract(), + }); + const doStart = () => + plugin.start(coreMock.createStart(), { + data: dataPluginMock.createStartContract(), + }); return { plugin, diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index ccf6aaf152ea4..cfd22f88167c5 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -16,10 +16,28 @@ * specific language governing permissions and limitations * under the License. */ + import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { TypesService, TypesSetup, TypesStart } from './types'; -import { setUISettings, setTypes, setI18n } from './services'; - +import { + setUISettings, + setTypes, + setI18n, + setCapabilities, + setHttp, + setIndexPatterns, + setSavedObjects, + setUsageCollector, + setFilterManager, +} from './services'; +import { VisualizeEmbeddableFactory } from '../../embeddable/visualize_embeddable_factory'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../embeddable'; +import { ExpressionsSetup } from '../../../../../../plugins/expressions/public'; +import { IEmbeddableSetup } from '../../../../../../plugins/embeddable/public'; +import { visualization as visualizationFunction } from './expressions/visualization_function'; +import { visualization as visualizationRenderer } from './expressions/visualization_renderer'; +import { DataPublicPluginStart } from '../../../../../../plugins/data/public'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; /** * Interface for this plugin's returned setup/start contracts. * @@ -34,6 +52,16 @@ export interface VisualizationsStart { types: TypesStart; } +export interface VisualizationsSetupDeps { + expressions: ExpressionsSetup; + embeddable: IEmbeddableSetup; + usageCollection: UsageCollectionSetup; +} + +export interface VisualizationsStartDeps { + data: DataPublicPluginStart; +} + /** * Visualizations Plugin - public * @@ -42,22 +70,46 @@ export interface VisualizationsStart { * * @internal */ -export class VisualizationsPlugin implements Plugin { +export class VisualizationsPlugin + implements + Plugin< + VisualizationsSetup, + VisualizationsStart, + VisualizationsSetupDeps, + VisualizationsStartDeps + > { private readonly types: TypesService = new TypesService(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup) { + public setup( + core: CoreSetup, + { expressions, embeddable, usageCollection }: VisualizationsSetupDeps + ) { setUISettings(core.uiSettings); + setUsageCollector(usageCollection); + + expressions.registerFunction(visualizationFunction); + expressions.registerRenderer(visualizationRenderer); + + const embeddableFactory = new VisualizeEmbeddableFactory(); + embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + return { types: this.types.setup(), }; } - public start(core: CoreStart) { - setI18n(core.i18n); + public start(core: CoreStart, { data }: VisualizationsStartDeps) { const types = this.types.start(); + setI18n(core.i18n); setTypes(types); + setCapabilities(core.application.capabilities); + setHttp(core.http); + setSavedObjects(core.savedObjects); + setIndexPatterns(data.indexPatterns); + setFilterManager(data.query.filterManager); + return { types, }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts index 434612d11b28a..433c5c7b6df0d 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts @@ -17,12 +17,40 @@ * under the License. */ -import { I18nStart, IUiSettingsClient } from 'src/core/public'; +import { + Capabilities, + HttpStart, + I18nStart, + IUiSettingsClient, + SavedObjectsStart, +} from 'src/core/public'; import { TypesStart } from './types'; import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; +import { FilterManager, IndexPatternsContract } from '../../../../../../plugins/data/public'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/public'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); +export const [getCapabilities, setCapabilities] = createGetterSetter('Capabilities'); + +export const [getHttp, setHttp] = createGetterSetter('Http'); + +export const [getSavedObjects, setSavedObjects] = createGetterSetter( + 'SavedObjects' +); + export const [getTypes, setTypes] = createGetterSetter('Types'); export const [getI18n, setI18n] = createGetterSetter('I18n'); + +export const [getFilterManager, setFilterManager] = createGetterSetter( + 'FilterManager' +); + +export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( + 'IndexPatterns' +); + +export const [getUsageCollector, setUsageCollector] = createGetterSetter( + 'UsageCollection' +); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js index 2f60de3b80614..f849cbfb290ca 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js @@ -18,7 +18,9 @@ */ import _ from 'lodash'; + import { createFiltersFromEvent, onBrushEvent } from '../filters'; +import { DefaultEditorController } from '../../../legacy_imports'; export class BaseVisType { constructor(opts = {}) { @@ -46,7 +48,7 @@ export class BaseVisType { }, requestHandler: 'courier', // select one from registry or pass a function responseHandler: 'none', - editor: 'default', + editor: DefaultEditorController, editorConfig: { collections: {}, // collections used for configuration (list of positions, ...) }, diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js index 115bbd2731840..4f1526c20cb6f 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js @@ -97,6 +97,10 @@ class Vis extends EventEmitter { } } + setVisType(type) { + this.type.type = type; + } + updateState() { this.setState(this.getCurrentState(true)); this.emit('update'); @@ -118,18 +122,6 @@ class Vis extends EventEmitter { }; } - getSerializableState(state) { - return { - title: state.title, - type: state.type, - params: _.cloneDeep(state.params), - aggs: state.aggs.aggs - .map(agg => agg.toJSON()) - .filter(agg => agg.enabled) - .filter(Boolean), - }; - } - copyCurrentState(includeDisabled = false) { const state = this.getCurrentState(includeDisabled); state.aggs = new AggConfigs( diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index a53e8e0498c42..88a794445870c 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/UI_SYSTEMS.md b/src/legacy/ui/public/UI_SYSTEMS.md index 63fd602075653..37bfbcf92f640 100644 --- a/src/legacy/ui/public/UI_SYSTEMS.md +++ b/src/legacy/ui/public/UI_SYSTEMS.md @@ -6,7 +6,3 @@ In this directory you'll find various UI systems you can use to craft effective * [banners](notify/banners/BANNERS.md) * [toastNotifications](notify/toasts/TOAST_NOTIFICATIONS.md) - -## ui/vislib - -* [VisLib](vislib/VISLIB.md) \ No newline at end of file diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index 747ad025ef691..f5a1d0a7922a7 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -26,5 +26,4 @@ // Can't import vis folder here because of cascading issues, it's imported in core_plugins/kibana // @import './vis/index'; -@import './vislib/index'; @import './visualize/index'; diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts index 07e0d46e4eb70..0edf782318862 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/ui/public/agg_types/agg_config.ts @@ -123,6 +123,7 @@ export class AggConfig { public enabled: boolean; public params: any; public parent?: AggConfigs; + public brandNew?: boolean; private __schema: Schema; private __type: AggType; diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts index 6e811afb1849d..bd2f261c0bf1d 100644 --- a/src/legacy/ui/public/agg_types/agg_configs.ts +++ b/src/legacy/ui/public/agg_types/agg_configs.ts @@ -126,10 +126,10 @@ export class AggConfigs { return aggConfigs; } - createAggConfig( + createAggConfig = ( params: AggConfig | AggConfigOptions, { addToAggConfigs = true } = {} - ) { + ) => { let aggConfig; if (params instanceof AggConfig) { aggConfig = params; @@ -141,7 +141,7 @@ export class AggConfigs { this.aggs.push(aggConfig); } return aggConfig as T; - } + }; /** * Data-by-data comparison of this Aggregation diff --git a/src/legacy/ui/public/vislib/components/color/colormaps.ts b/src/legacy/ui/public/color_maps/color_maps.ts similarity index 100% rename from src/legacy/ui/public/vislib/components/color/colormaps.ts rename to src/legacy/ui/public/color_maps/color_maps.ts diff --git a/src/legacy/ui/public/vislib/components/color/heatmap_color.js b/src/legacy/ui/public/color_maps/heatmap_color.js similarity index 98% rename from src/legacy/ui/public/vislib/components/color/heatmap_color.js rename to src/legacy/ui/public/color_maps/heatmap_color.js index 5e788cd1f3345..06d754235f88b 100644 --- a/src/legacy/ui/public/vislib/components/color/heatmap_color.js +++ b/src/legacy/ui/public/color_maps/heatmap_color.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { vislibColorMaps } from './colormaps'; +import { vislibColorMaps } from './color_maps'; function enforceBounds(x) { if (x < 0) { diff --git a/src/legacy/ui/public/color_maps/index.ts b/src/legacy/ui/public/color_maps/index.ts new file mode 100644 index 0000000000000..50dfe682f4418 --- /dev/null +++ b/src/legacy/ui/public/color_maps/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './color_maps'; +// @ts-ignore +export { getHeatmapColors } from './heatmap_color'; +// @ts-ignore +export * from './truncated_color_maps'; diff --git a/src/legacy/ui/public/vislib/components/color/truncated_colormaps.js b/src/legacy/ui/public/color_maps/truncated_color_maps.js similarity index 86% rename from src/legacy/ui/public/vislib/components/color/truncated_colormaps.js rename to src/legacy/ui/public/color_maps/truncated_color_maps.js index ccf005b3726a8..cb7772c875e48 100644 --- a/src/legacy/ui/public/vislib/components/color/truncated_colormaps.js +++ b/src/legacy/ui/public/color_maps/truncated_color_maps.js @@ -17,14 +17,14 @@ * under the License. */ -import { vislibColorMaps } from './colormaps'; +import { vislibColorMaps } from './color_maps'; export const truncatedColorMaps = {}; const colormaps = vislibColorMaps; for (const key in colormaps) { if (colormaps.hasOwnProperty(key)) { - //slice off lightest colors + // slice off lightest colors truncatedColorMaps[key] = { ...colormaps[key], value: colormaps[key].value.slice(Math.floor(colormaps[key].value.length / 4)), @@ -32,7 +32,7 @@ for (const key in colormaps) { } } -export const colorSchemas = Object.values(truncatedColorMaps).map(({ id, label }) => ({ +export const truncatedColorSchemas = Object.values(truncatedColorMaps).map(({ id, label }) => ({ value: id, text: label, })); 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 14376b163d6f0..c8c886015cea3 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/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 06424ea48a40f..d3f74a540b960 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/_mixins.scss b/src/legacy/ui/public/styles/_mixins.scss index ae529a4678d5d..2d78768684841 100644 --- a/src/legacy/ui/public/styles/_mixins.scss +++ b/src/legacy/ui/public/styles/_mixins.scss @@ -25,6 +25,7 @@ // Standardizes the look and layout of resizable area handles @mixin kbnResizer($size: ($euiSizeM + 2px), $direction: horizontal) { + position: relative; display: flex; flex: 0 0 $size; background-color: $euiPageBackgroundColor; @@ -34,10 +35,8 @@ user-select: none; @if ($direction == horizontal) { - cursor: ew-resize; width: $size; } @else if ($direction == vertical) { - cursor: ns-resize; height: $size; width: 100%; } @else { @@ -53,6 +52,21 @@ background-color: $euiColorPrimary; color: $euiColorEmptyShade; } + + &::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + @if ($direction == horizontal) { + cursor: ew-resize; + } @else if ($direction == vertical) { + cursor: ns-resize; + } + } } @mixin kibanaFullScreenGraphics($euiZLevel: $euiZLevel9) { diff --git a/src/legacy/ui/public/styles/bootstrap/_custom_variables.less b/src/legacy/ui/public/styles/bootstrap/_custom_variables.less index aa174684a622b..a348e7bfa86b8 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 3ef17ea35352c..cdbed7fa06367 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 aaf1edbf4860e..6030c8068884d 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 6ea1686dadb8d..f757624ffbca7 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 b14db52644459..52b70bff6b2ad 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 a32b627dba2c2..9961d54028b13 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 9481297b99a28..411d9d59b89c6 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 cbbd35d59ce89..c89bbe73b603a 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 9df72817bb940..c038bb411daec 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 820b66897affa..61925760457c6 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/components/tooltip/_hierarchical_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js index 26a9c5b008f70..aef7bc3913a49 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js +++ b/src/legacy/ui/public/vis/components/tooltip/_hierarchical_tooltip_formatter.js @@ -19,6 +19,9 @@ import _ from 'lodash'; import $ from 'jquery'; + +import chrome from 'ui/chrome'; + import { collectBranch } from './_collect_branch'; import numeral from 'numeral'; import template from './_hierarchical_tooltip.html'; @@ -68,6 +71,12 @@ export const getHierarchicalTooltipFormatter = () => { return _tooltipFormatter; }; +export const initializeHierarchicalTooltipFormatter = async () => { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); +}; + export const setHierarchicalTooltipFormatter = Private => { _tooltipFormatter = Private(HierarchicalTooltipFormatterProvider); }; diff --git a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js index fa0b030c736c1..88c9e3d67b4a9 100644 --- a/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js +++ b/src/legacy/ui/public/vis/components/tooltip/_pointseries_tooltip_formatter.js @@ -18,6 +18,9 @@ */ import $ from 'jquery'; + +import chrome from 'ui/chrome'; + import template from './_pointseries_tooltip.html'; export function PointSeriesTooltipFormatterProvider($compile, $rootScope) { @@ -75,6 +78,12 @@ export const getPointSeriesTooltipFormatter = () => { return _tooltipFormatter; }; +export const initializePointSeriesTooltipFormatter = async () => { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const Private = $injector.get('Private'); + _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); +}; + export const setPointSeriesTooltipFormatter = Private => { _tooltipFormatter = Private(PointSeriesTooltipFormatterProvider); }; diff --git a/src/legacy/ui/public/vis/editor_size.ts b/src/legacy/ui/public/vis/editor_size.ts new file mode 100644 index 0000000000000..5fdda4c2dad41 --- /dev/null +++ b/src/legacy/ui/public/vis/editor_size.ts @@ -0,0 +1,36 @@ +/* + * 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 enum DefaultEditorSize { + SMALL = 'small', + MEDIUM = 'medium', + LARGE = 'large', +} + +export const getInitialWidth = (size: DefaultEditorSize) => { + switch (size) { + case DefaultEditorSize.SMALL: + return 15; + case DefaultEditorSize.LARGE: + return 50; + case DefaultEditorSize.MEDIUM: + default: + return 30; + } +}; diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts index fa15f8642c4dc..0a5d0ea748b5e 100644 --- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts +++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.test.ts @@ -19,7 +19,6 @@ import { EditorConfigProviderRegistry } from './editor_config_providers'; import { EditorParamConfig, FixedParam, NumericIntervalParam, TimeIntervalParam } from './types'; -import { AggType } from '../../../agg_types'; import { AggConfig } from '../..'; jest.mock('ui/new_platform'); @@ -49,10 +48,9 @@ describe('EditorConfigProvider', () => { const provider = jest.fn(() => ({})); registry.register(provider); expect(provider).not.toHaveBeenCalled(); - const aggType = {} as AggType; const aggConfig = {} as AggConfig; - registry.getConfigForAgg(aggType, indexPattern, aggConfig); - expect(provider).toHaveBeenCalledWith(aggType, indexPattern, aggConfig); + registry.getConfigForAgg(indexPattern, aggConfig); + expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig); }); it('should call all registered providers with given parameters', () => { @@ -62,11 +60,10 @@ describe('EditorConfigProvider', () => { registry.register(provider2); expect(provider).not.toHaveBeenCalled(); expect(provider2).not.toHaveBeenCalled(); - const aggType = {} as AggType; const aggConfig = {} as AggConfig; - registry.getConfigForAgg(aggType, indexPattern, aggConfig); - expect(provider).toHaveBeenCalledWith(aggType, indexPattern, aggConfig); - expect(provider2).toHaveBeenCalledWith(aggType, indexPattern, aggConfig); + registry.getConfigForAgg(indexPattern, aggConfig); + expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig); + expect(provider2).toHaveBeenCalledWith(indexPattern, aggConfig); }); describe('merging configs', () => { @@ -75,7 +72,7 @@ describe('EditorConfigProvider', () => { } function getOutputConfig(reg: EditorConfigProviderRegistry) { - return reg.getConfigForAgg({} as AggType, indexPattern, {} as AggConfig).singleParam; + return reg.getConfigForAgg(indexPattern, {} as AggConfig).singleParam; } it('should have hidden true if at least one config was hidden true', () => { diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts index c7fb937b97424..80dc2bcd68f08 100644 --- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts +++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts @@ -19,18 +19,13 @@ import { TimeIntervalParam } from 'ui/vis/editors/config/types'; import { AggConfig } from '../..'; -import { AggType } from '../../../agg_types'; import { IndexPattern } from '../../../../../../plugins/data/public'; import { leastCommonMultiple } from '../../lib/least_common_multiple'; import { parseEsInterval } from '../../../../../core_plugins/data/public'; import { leastCommonInterval } from '../../lib/least_common_interval'; import { EditorConfig, EditorParamConfig, FixedParam, NumericIntervalParam } from './types'; -type EditorConfigProvider = ( - aggType: AggType, - indexPattern: IndexPattern, - aggConfig: AggConfig -) => EditorConfig; +type EditorConfigProvider = (indexPattern: IndexPattern, aggConfig: AggConfig) => EditorConfig; class EditorConfigProviderRegistry { private providers: Set = new Set(); @@ -39,14 +34,8 @@ class EditorConfigProviderRegistry { this.providers.add(configProvider); } - public getConfigForAgg( - aggType: AggType, - indexPattern: IndexPattern, - aggConfig: AggConfig - ): EditorConfig { - const configs = Array.from(this.providers).map(provider => - provider(aggType, indexPattern, aggConfig) - ); + public getConfigForAgg(indexPattern: IndexPattern, aggConfig: AggConfig): EditorConfig { + const configs = Array.from(this.providers).map(provider => provider(indexPattern, aggConfig)); return this.mergeConfigs(configs); } diff --git a/src/legacy/ui/public/vis/editors/default/_default.scss b/src/legacy/ui/public/vis/editors/default/_default.scss index 2a32eadd55b1e..7562583b037df 100644 --- a/src/legacy/ui/public/vis/editors/default/_default.scss +++ b/src/legacy/ui/public/vis/editors/default/_default.scss @@ -1,8 +1,4 @@ .visEditor--default { - // Prevent the default editor from overflowing. Without that you can cause - // a weird issue where the complete page can be scrolled out of view if - // the editor within the sidebar is too height. - overflow-y: hidden; flex: 1 1 auto; display: flex; @@ -16,12 +12,11 @@ */ .visEditor__collapsibleSidebar { - @include flex-parent(0, 0, auto); - margin-right: $euiSizeL; - flex-direction: row; + background: $euiColorLightestShade; min-width: $vis-editor-sidebar-min-width; - width: $vis-editor-sidebar-min-width; max-width: 100%; + position: relative; + flex-shrink: 0; @include euiBreakpoint('xs', 's', 'm') { // If we are on a small screen we force the editor to take 100% width. @@ -33,35 +28,25 @@ } } -.visEditor__collapsibleSidebar.closed { +// !importants on width are required to override resizable panel inline widths +.visEditor__collapsibleSidebar-isClosed { min-width: 0; -} - -.visEditor__collapsibleSidebar--small { - width: 15%; -} + width: $euiSizeXL !important; // Just enough room for the collapse button -.visEditor__collapsibleSidebar--medium { - width: 30%; -} + .visEditorSidebar { + display: none; + } -.visEditor__collapsibleSidebar--large { - width: 50%; + @include euiBreakpoint('xs', 's', 'm') { + height: $euiSizeXXL; // Just enough room for the collapse button + width: 100% !important; + } } - -/** - * Actual sidebar - */ - -.visEditor__sidebar { - @include flex-parent(1, 0, auto); - - // overridden for tablet and desktop - @include euiBreakpoint('l', 'xl') { - flex-basis: $vis-editor-sidebar-basis; - max-width: calc(100% - #{$vis-editor-resizer-width}); - } +.visEditor__collapsibleSidebarButton { + position: absolute; + right: $euiSizeXS; + top: $euiSizeS; } /** @@ -69,17 +54,30 @@ */ .visEditor__resizer { - @include kbnResizer($vis-editor-resizer-width); - + @include kbnResizer($euiSizeM); @include euiBreakpoint('xs', 's', 'm') { display: none; } } +.visEditor__resizer-isHidden { + display: none; +} + /** * Canvas area */ +.visEditor__visualization { + display: flex; + flex-basis: 100%; + + @include euiBreakpoint('xs', 's', 'm') { + // If we are on a small screen we force the visualization to take 100% width. + width: 100% !important; + } +} + .visEditor__canvas { background-color: $euiColorEmptyShade; display: flex; diff --git a/src/legacy/ui/public/vis/editors/default/_index.scss b/src/legacy/ui/public/vis/editors/default/_index.scss index d5938a0298d36..6abb45dc546a3 100644 --- a/src/legacy/ui/public/vis/editors/default/_index.scss +++ b/src/legacy/ui/public/vis/editors/default/_index.scss @@ -1,6 +1,4 @@ -$vis-editor-sidebar-basis: (100/12) * 2%; // two of twelve columns $vis-editor-sidebar-min-width: 350px; -$vis-editor-resizer-width: $euiSizeM; // Main layout @import './default'; diff --git a/src/legacy/ui/public/vis/editors/default/_sidebar.scss b/src/legacy/ui/public/vis/editors/default/_sidebar.scss index e6b75b1a1f783..cbe7172d62341 100644 --- a/src/legacy/ui/public/vis/editors/default/_sidebar.scss +++ b/src/legacy/ui/public/vis/editors/default/_sidebar.scss @@ -2,18 +2,22 @@ // LAYOUT // -.visEditorSidebar__container { - @include flex-parent(1, 1, auto); - background-color: $euiColorLightestShade; +.visEditorSidebar { + min-width: $vis-editor-sidebar-min-width; } .visEditorSidebar__form { @include flex-parent(1, 1, auto); + max-width: 100%; } .visEditorSidebar__config { padding: $euiSizeS; + > * { + flex-grow: 0; + } + @include euiBreakpoint('l', 'xl') { @include flex-parent(1, 1, 1px); @include euiScrollBar; @@ -21,64 +25,26 @@ } } +.visEditorSidebar__config-isHidden { + display: none; +} + // // NAVIGATION // .visEditorSidebar__indexPattern { - font-weight: $euiFontWeightBold; - padding: $euiSizeXS $euiSizeS; - background-color: shadeOrTint($euiColorPrimary, 60%, 60%); - color: $euiColorEmptyShade; - line-height: $euiSizeL; + @include euiTextTruncate; + padding: $euiSizeS $euiSizeXL $euiSizeS $euiSizeS; // Extra padding on the right for the collapse button } -.visEditorSidebar__nav { - min-height: 0; - - .navbar-right { - // Match correct bootstrap container spacing to pull buttons fully right - margin-right: -15px; - } +.visEditorSidebar__indexPatternPlaceholder { + min-height: $euiSizeXXL; + border-bottom: $euiBorderThin; } -/** - * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap. - */ -.visEditorSidebar__navLink { - padding: 2px $euiSizeS !important; /* 1 */ - color: $euiColorDarkShade !important; /* 1 */ - - &.visEditorSidebar__navLink-isSelected { - border-bottom: 2px solid $euiColorPrimary; - border-color: $euiColorPrimary !important; - color: $euiColorPrimary !important; - - &:before { - display: none; - } - - &:hover { - background-color: transparent; - } - } -} - -/** - * 1. TODO: Override bootstrap styles. Remove !important once we're rid of bootstrap. - */ -.visEditorSidebar__navLink--danger { - color: $euiColorEmptyShade !important; /* 1 */ - background-color: $euiColorDanger; - - &:hover { - background-color: shadeOrTint($euiColorDanger, 12%, 0%) !important; /* 1 */ - } -} - -.visEditorSidebar__navButtonLink { - // Make the line-height the same size as the icon for better alignment - line-height: $euiSize; +.visEditorSidebar__nav { + flex-grow: 0; } // @@ -104,15 +70,6 @@ } } -.visEditorSidebar__sectionTitle { - @include euiFontSizeL; - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: $euiSizeM; - text-transform: capitalize; -} - // Collapsible section .visEditorSidebar__collapsible { @@ -127,15 +84,6 @@ // FORMS // -.visEditorSidebar__input, -.visEditorSidebar__select { - @include __legacyInputStyles__bad; -} - -.visEditorSidebar__select { - @include __legacySelectStyles__bad; -} - .visEditorSidebar__formRow { display: flex; align-items: center; @@ -155,14 +103,6 @@ flex: 1 1 60%; } -.visEditorSidebar__formRow--expand { - .visEditorSidebar__formLabel, - .visEditorSidebar__formControl { - flex-basis: auto; - flex-grow: 0; - } -} - .visEditorSidebar__aggGroupAccordionButtonContent { font-size: $euiFontSizeS; @@ -170,3 +110,15 @@ color: $euiColorDarkShade; } } + +.visEditorSidebar__controls { + border-top: $euiBorderThin; + padding: $euiSizeS; + display: flex; + justify-content: flex-end; + align-items: center; + + .visEditorSidebar__autoApplyButton { + margin-left: $euiSizeM; + } +} diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.js b/src/legacy/ui/public/vis/editors/default/agg_group.js deleted file mode 100644 index 8fc4934022cf6..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/agg_group.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'ngreact'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from '../../../modules'; -import { DefaultEditorAggGroup } from './components/agg_group'; - -uiModules - .get('app/visualize') - .directive('visEditorAggGroupWrapper', reactDirective => - reactDirective(wrapInI18nContext(DefaultEditorAggGroup), [ - ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects - ['schemas', { watchDepth: 'collection' }], - ['state', { watchDepth: 'reference' }], - ['addSchema', { watchDepth: 'reference' }], - ['onAggParamsChange', { watchDepth: 'reference' }], - ['onAggTypeChange', { watchDepth: 'reference' }], - ['onToggleEnableAgg', { watchDepth: 'reference' }], - ['removeAgg', { watchDepth: 'reference' }], - ['reorderAggs', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - 'groupName', - 'formIsTouched', - 'lastParentPipelineAggTitle', - 'currentTab', - ]) - ) - .directive('visEditorAggGroup', function() { - return { - restrict: 'E', - scope: true, - require: '?^ngModel', - template: function() { - return ``; - }, - link: function($scope, $el, attr, ngModelCtrl) { - $scope.groupName = attr.groupName; - $scope.$bind('schemas', attr.schemas); - // The model can become touched either onBlur event or when the form is submitted. - // We also watch $touched to identify when the form is submitted. - $scope.$watch( - () => { - return ngModelCtrl.$touched; - }, - value => { - $scope.formIsTouched = value; - } - ); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); - }; - - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - }, - }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/agg_groups.ts b/src/legacy/ui/public/vis/editors/default/agg_groups.ts index f55e6ecd79155..e84306144fa63 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_groups.ts +++ b/src/legacy/ui/public/vis/editors/default/agg_groups.ts @@ -18,11 +18,14 @@ */ import { i18n } from '@kbn/i18n'; +import { $Values } from '@kbn/utility-types'; -export enum AggGroupNames { - Buckets = 'buckets', - Metrics = 'metrics', -} +export const AggGroupNames = Object.freeze({ + Buckets: 'buckets' as 'buckets', + Metrics: 'metrics' as 'metrics', + None: 'none' as 'none', +}); +export type AggGroupNames = $Values; export const aggGroupNamesMap = () => ({ [AggGroupNames.Metrics]: i18n.translate('common.ui.vis.editors.aggGroups.metricsText', { diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap index b78503b298d04..aed0285fd3405 100644 --- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg.test.tsx.snap @@ -57,9 +57,9 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` groupName="metrics" indexPattern={Object {}} metricAggs={Array []} - onAggParamsChange={[MockFunction]} onAggTypeChange={[Function]} - setTouched={[MockFunction]} + setAggParamValue={[MockFunction]} + setTouched={[Function]} setValidity={[Function]} state={Object {}} /> diff --git a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap index 29af0887db2b8..373ff6b4c3ee4 100644 --- a/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/components/__snapshots__/agg_group.test.tsx.snap @@ -5,6 +5,7 @@ exports[`DefaultEditorAgg component should init with the default set of props 1` onDragEnd={[Function]} > diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx index c19f101fa2c32..6849d00158b06 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx @@ -26,6 +26,7 @@ import { act } from 'react-dom/test-utils'; import { DefaultEditorAggParams } from './agg_params'; import { AggType } from 'ui/agg_types'; import { IndexPattern } from '../../../../../../../plugins/data/public'; +import { AGGS_ACTION_KEYS } from './agg_group_state'; jest.mock('./agg_params', () => ({ DefaultEditorAggParams: () => null, @@ -33,18 +34,18 @@ jest.mock('./agg_params', () => ({ describe('DefaultEditorAgg component', () => { let defaultProps: DefaultEditorAggProps; - let onAggParamsChange: jest.Mock; - let setTouched: jest.Mock; + let setAggParamValue: jest.Mock; + let setStateParamValue: jest.Mock; let onToggleEnableAgg: jest.Mock; let removeAgg: jest.Mock; - let setValidity: jest.Mock; + let setAggsState: jest.Mock; beforeEach(() => { - onAggParamsChange = jest.fn(); - setTouched = jest.fn(); + setAggParamValue = jest.fn(); + setStateParamValue = jest.fn(); onToggleEnableAgg = jest.fn(); removeAgg = jest.fn(); - setValidity = jest.fn(); + setAggsState = jest.fn(); defaultProps = { agg: { @@ -66,10 +67,10 @@ describe('DefaultEditorAgg component', () => { isRemovable: false, metricAggs: [], state: {} as VisState, - onAggParamsChange, + setAggParamValue, + setStateParamValue, onAggTypeChange: () => {}, - setValidity, - setTouched, + setAggsState, onToggleEnableAgg, removeAgg, }; @@ -98,7 +99,11 @@ describe('DefaultEditorAgg component', () => { .setValidity(false); }); comp.update(); - expect(setValidity).toBeCalledWith(false); + expect(setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.VALID, + payload: false, + aggId: defaultProps.agg.id, + }); expect( comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists() @@ -119,7 +124,11 @@ describe('DefaultEditorAgg component', () => { .setValidity(true); }); comp.update(); - expect(setValidity).toBeCalledWith(true); + expect(setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.VALID, + payload: true, + aggId: defaultProps.agg.id, + }); expect(comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').text()).toBe( 'Agg description' @@ -128,16 +137,20 @@ describe('DefaultEditorAgg component', () => { it('should call setTouched when accordion is collapsed', () => { const comp = mount(); - expect(defaultProps.setTouched).toBeCalledTimes(0); + expect(defaultProps.setAggsState).toBeCalledTimes(0); comp.find('.euiAccordion__button').simulate('click'); // make sure that the accordion is collapsed expect(comp.find('.euiAccordion-isOpen').exists()).toBeFalsy(); - expect(defaultProps.setTouched).toBeCalledWith(true); + expect(defaultProps.setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.TOUCHED, + payload: true, + aggId: defaultProps.agg.id, + }); }); - it('should call setValidity inside onSetValidity', () => { + it('should call setAggsState inside setValidity', () => { const comp = mount(); act(() => { @@ -147,7 +160,11 @@ describe('DefaultEditorAgg component', () => { .setValidity(false); }); - expect(setValidity).toBeCalledWith(false); + expect(setAggsState).toBeCalledWith({ + type: AGGS_ACTION_KEYS.VALID, + payload: false, + aggId: defaultProps.agg.id, + }); expect( comp.find('.visEditorSidebar__aggGroupAccordionButtonContent span').exists() @@ -197,7 +214,7 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.find('[data-test-subj="toggleDisableAggregationBtn disable"] button').simulate('click'); - expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, false); + expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg.id, false); }); it('should disable the disableAggregation button', () => { @@ -217,7 +234,7 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.find('[data-test-subj="toggleDisableAggregationBtn enable"] button').simulate('click'); - expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg, true); + expect(defaultProps.onToggleEnableAgg).toBeCalledWith(defaultProps.agg.id, true); }); it('should call removeAgg', () => { @@ -225,7 +242,7 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.find('[data-test-subj="removeDimensionBtn"] button').simulate('click'); - expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg); + expect(defaultProps.removeAgg).toBeCalledWith(defaultProps.agg.id); }); }); @@ -269,8 +286,8 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'histogram' } } }); - expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith( - defaultProps.agg.params, + expect(defaultProps.setAggParamValue).toHaveBeenCalledWith( + defaultProps.agg.id, 'min_doc_count', true ); @@ -283,8 +300,8 @@ describe('DefaultEditorAgg component', () => { const comp = mount(); comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'date_histogram' } } }); - expect(defaultProps.onAggParamsChange).toHaveBeenCalledWith( - defaultProps.agg.params, + expect(defaultProps.setAggParamValue).toHaveBeenCalledWith( + defaultProps.agg.id, 'min_doc_count', 0 ); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.tsx index 345c9254ff6c1..5c5905abdb9f0 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { EuiAccordion, EuiToolTip, @@ -31,6 +31,7 @@ import { i18n } from '@kbn/i18n'; import { AggConfig } from '../../..'; import { DefaultEditorAggParams } from './agg_params'; import { DefaultEditorAggCommonProps } from './agg_common_props'; +import { AGGS_ACTION_KEYS, AggsAction } from './agg_group_state'; export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { agg: AggConfig; @@ -41,6 +42,7 @@ export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { isDraggable: boolean; isLastBucket: boolean; isRemovable: boolean; + setAggsState: React.Dispatch; } function DefaultEditorAgg({ @@ -57,12 +59,12 @@ function DefaultEditorAgg({ metricAggs, lastParentPipelineAggTitle, state, - onAggParamsChange, + setAggParamValue, + setStateParamValue, onAggTypeChange, onToggleEnableAgg, removeAgg, - setTouched, - setValidity, + setAggsState, }: DefaultEditorAggProps) { const [isEditorOpen, setIsEditorOpen] = useState((agg as any).brandNew); const [validState, setValidState] = useState(true); @@ -103,8 +105,8 @@ function DefaultEditorAgg({ useEffect(() => { if (isLastBucketAgg && ['date_histogram', 'histogram'].includes(agg.type.name)) { - onAggParamsChange( - agg.params, + setAggParamValue( + agg.id, 'min_doc_count', // "histogram" agg has an editor for "min_doc_count" param, which accepts boolean // "date_histogram" agg doesn't have an editor for "min_doc_count" param, it should be set as a numeric value @@ -113,17 +115,38 @@ function DefaultEditorAgg({ } }, [lastParentPipelineAggTitle, isLastBucket, agg.type]); - const onToggle = (isOpen: boolean) => { - setIsEditorOpen(isOpen); - if (!isOpen) { - setTouched(true); - } - }; + const setTouched = useCallback( + (touched: boolean) => { + setAggsState({ + type: AGGS_ACTION_KEYS.TOUCHED, + payload: touched, + aggId: agg.id, + }); + }, + [setAggsState] + ); - const onSetValidity = (isValid: boolean) => { - setValidity(isValid); - setValidState(isValid); - }; + const setValidity = useCallback( + (isValid: boolean) => { + setAggsState({ + type: AGGS_ACTION_KEYS.VALID, + payload: isValid, + aggId: agg.id, + }); + setValidState(isValid); + }, + [setAggsState] + ); + + const onToggle = useCallback( + (isOpen: boolean) => { + setIsEditorOpen(isOpen); + if (!isOpen) { + setTouched(true); + } + }, + [setTouched] + ); const renderAggButtons = () => { const actionIcons = []; @@ -146,7 +169,7 @@ function DefaultEditorAgg({ color: 'text', disabled: isDisabled, type: 'eye', - onClick: () => onToggleEnableAgg(agg, false), + onClick: () => onToggleEnableAgg(agg.id, false), tooltip: i18n.translate('common.ui.vis.editors.agg.disableAggButtonTooltip', { defaultMessage: 'Disable aggregation', }), @@ -158,7 +181,7 @@ function DefaultEditorAgg({ id: 'enableAggregation', color: 'text', type: 'eyeClosed', - onClick: () => onToggleEnableAgg(agg, true), + onClick: () => onToggleEnableAgg(agg.id, true), tooltip: i18n.translate('common.ui.vis.editors.agg.enableAggButtonTooltip', { defaultMessage: 'Enable aggregation', }), @@ -180,7 +203,7 @@ function DefaultEditorAgg({ id: 'removeDimension', color: 'danger', type: 'cross', - onClick: () => removeAgg(agg), + onClick: () => removeAgg(agg.id), tooltip: i18n.translate('common.ui.vis.editors.agg.removeDimensionButtonTooltip', { defaultMessage: 'Remove dimension', }), @@ -248,9 +271,10 @@ function DefaultEditorAgg({ {SchemaComponent && ( )} diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts index 232eaba76f3a1..a0ddc9a757cc7 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts @@ -18,29 +18,32 @@ */ import { AggType } from 'ui/agg_types'; -import { AggConfig, VisState, VisParams } from '../../..'; -import { AggParams } from '../agg_params'; +import { AggConfig, VisState, VisParams } from 'ui/vis'; import { AggGroupNames } from '../agg_groups'; +import { Schema } from '../schemas'; -export type OnAggParamsChange = < - Params extends AggParams | VisParams, - ParamName extends keyof Params ->( - params: Params, - paramName: ParamName, - value: Params[ParamName] -) => void; +type AggId = AggConfig['id']; +type AggParams = AggConfig['params']; -export interface DefaultEditorAggCommonProps { +export type AddSchema = (schemas: Schema) => void; +export type ReorderAggs = (sourceAgg: AggConfig, destinationAgg: AggConfig) => void; + +export interface DefaultEditorCommonProps { formIsTouched: boolean; groupName: AggGroupNames; - lastParentPipelineAggTitle?: string; metricAggs: AggConfig[]; state: VisState; - onAggParamsChange: OnAggParamsChange; - onAggTypeChange: (agg: AggConfig, aggType: AggType) => void; - onToggleEnableAgg: (agg: AggConfig, isEnable: boolean) => void; - removeAgg: (agg: AggConfig) => void; - setTouched: (isTouched: boolean) => void; - setValidity: (isValid: boolean) => void; + setAggParamValue: ( + aggId: AggId, + paramName: T, + value: AggParams[T] + ) => void; + onAggTypeChange: (aggId: AggId, aggType: AggType) => void; +} + +export interface DefaultEditorAggCommonProps extends DefaultEditorCommonProps { + lastParentPipelineAggTitle?: string; + setStateParamValue: (paramName: T, value: VisParams[T]) => void; + onToggleEnableAgg: (aggId: AggId, isEnable: boolean) => void; + removeAgg: (aggId: AggId) => void; } diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx index 05e8c44b9c720..ae36503c16133 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.test.tsx @@ -109,8 +109,9 @@ describe('DefaultEditorAgg component', () => { reorderAggs, addSchema: () => {}, removeAgg: () => {}, - onAggParamsChange: () => {}, - onAggTypeChange: () => {}, + setAggParamValue: jest.fn(), + setStateParamValue: jest.fn(), + onAggTypeChange: jest.fn(), onToggleEnableAgg: () => {}, }; }); @@ -127,46 +128,6 @@ describe('DefaultEditorAgg component', () => { expect(setTouched).toBeCalledWith(false); }); - it('should mark group as touched when all invalid aggs are touched', () => { - defaultProps.groupName = AggGroupNames.Buckets; - const comp = mount(); - act(() => { - const aggProps = comp.find(DefaultEditorAgg).props(); - aggProps.setValidity(false); - aggProps.setTouched(true); - }); - - expect(setTouched).toBeCalledWith(true); - }); - - it('should mark group as touched when the form applied', () => { - const comp = mount(); - act(() => { - comp - .find(DefaultEditorAgg) - .first() - .props() - .setValidity(false); - }); - expect(setTouched).toBeCalledWith(false); - comp.setProps({ formIsTouched: true }); - - expect(setTouched).toBeCalledWith(true); - }); - - it('should mark group as invalid when at least one agg is invalid', () => { - const comp = mount(); - act(() => { - comp - .find(DefaultEditorAgg) - .first() - .props() - .setValidity(false); - }); - - expect(setValidity).toBeCalledWith(false); - }); - it('should last bucket has truthy isLastBucket prop', () => { defaultProps.groupName = AggGroupNames.Buckets; const comp = mount(); @@ -182,10 +143,10 @@ describe('DefaultEditorAgg component', () => { comp.props().onDragEnd({ source: { index: 0 }, destination: { index: 1 } }); }); - expect(reorderAggs).toHaveBeenCalledWith([ - defaultProps.state.aggs.aggs[1], + expect(reorderAggs).toHaveBeenCalledWith( defaultProps.state.aggs.aggs[0], - ]); + defaultProps.state.aggs.aggs[1] + ); }); it('should show add button when schemas count is less than max', () => { diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx index 528914f4fd006..7416c36bd5cf1 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect, useReducer, useMemo } from 'react'; +import React, { useEffect, useReducer, useMemo, useCallback } from 'react'; import { EuiTitle, EuiDragDropContext, @@ -34,7 +34,7 @@ import { AggConfig } from '../../../../agg_types/agg_config'; import { aggGroupNamesMap, AggGroupNames } from '../agg_groups'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; -import { DefaultEditorAggCommonProps } from './agg_common_props'; +import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props'; import { isInvalidAggsTouched, isAggRemovable, @@ -46,8 +46,10 @@ import { Schema } from '../schemas'; export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { schemas: Schema[]; - addSchema: (schemas: Schema) => void; - reorderAggs: (group: AggConfig[]) => void; + addSchema: AddSchema; + reorderAggs: ReorderAggs; + setValidity(modelName: string, value: boolean): void; + setTouched(isTouched: boolean): void; } function DefaultEditorAggGroup({ @@ -58,7 +60,8 @@ function DefaultEditorAggGroup({ state, schemas = [], addSchema, - onAggParamsChange, + setAggParamValue, + setStateParamValue, onAggTypeChange, onToggleEnableAgg, removeAgg, @@ -66,10 +69,12 @@ function DefaultEditorAggGroup({ setTouched, setValidity, }: DefaultEditorAggGroupProps) { - const groupNameLabel = aggGroupNamesMap()[groupName]; + const groupNameLabel = (aggGroupNamesMap() as any)[groupName]; // e.g. buckets can have no aggs - const group: AggConfig[] = - state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || []; + const group: AggConfig[] = useMemo( + () => state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || [], + [state.aggs.aggs] + ); const stats = { max: 0, @@ -101,7 +106,7 @@ function DefaultEditorAggGroup({ // when isAllAggsTouched is true, it means that all invalid aggs are touched and we will set ngModel's touched to true // which indicates that Apply button can be changed to Error button (when all invalid ngModels are touched) setTouched(isAllAggsTouched); - }, [isAllAggsTouched]); + }, [isAllAggsTouched, setTouched]); useEffect(() => { // when not all invalid aggs are touched and formIsTouched becomes true, it means that Apply button was clicked. @@ -118,38 +123,21 @@ function DefaultEditorAggGroup({ }, [formIsTouched]); useEffect(() => { - setValidity(isGroupValid); - }, [isGroupValid]); - - const onDragEnd: DragDropContextProps['onDragEnd'] = ({ source, destination }) => { - if (source && destination) { - const orderedGroup = Array.from(group); - const [removed] = orderedGroup.splice(source.index, 1); - orderedGroup.splice(destination.index, 0, removed); - - reorderAggs(orderedGroup); - } - }; - - const setTouchedHandler = (aggId: string, touched: boolean) => { - setAggsState({ - type: AGGS_ACTION_KEYS.TOUCHED, - payload: touched, - aggId, - }); - }; - - const setValidityHandler = (aggId: string, valid: boolean) => { - setAggsState({ - type: AGGS_ACTION_KEYS.VALID, - payload: valid, - aggId, - }); - }; + setValidity(`aggGroup__${groupName}`, isGroupValid); + }, [groupName, isGroupValid, setValidity]); + + const onDragEnd: DragDropContextProps['onDragEnd'] = useCallback( + ({ source, destination }) => { + if (source && destination) { + reorderAggs(group[source.index], group[destination.index]); + } + }, + [reorderAggs, group] + ); return ( - +

{groupNameLabel}

@@ -184,12 +172,12 @@ function DefaultEditorAggGroup({ lastParentPipelineAggTitle={lastParentPipelineAggTitle} metricAggs={metricAggs} state={state} - onAggParamsChange={onAggParamsChange} + setAggParamValue={setAggParamValue} + setStateParamValue={setStateParamValue} onAggTypeChange={onAggTypeChange} onToggleEnableAgg={onToggleEnableAgg} removeAgg={removeAgg} - setTouched={isTouched => setTouchedHandler(agg.id, isTouched)} - setValidity={isValid => setValidityHandler(agg.id, isValid)} + setAggsState={setAggsState} /> )} diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx index 980889743c20d..0b787e45a5008 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_group_state.tsx @@ -33,7 +33,7 @@ export interface AggsState { [aggId: string]: AggsItem; } -interface AggsAction { +export interface AggsAction { type: AGGS_ACTION_KEYS; payload: boolean; aggId: string; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx index 72c802d7d237e..d3bbf3cc9903a 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_param.tsx @@ -17,18 +17,59 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { AggParamEditorProps, AggParamCommonProps } from './agg_param_props'; -import { OnAggParamsChange } from './agg_common_props'; +import { DefaultEditorAggCommonProps } from './agg_common_props'; +import { AGG_PARAMS_ACTION_KEYS, AggParamsAction } from './agg_params_state'; interface DefaultEditorAggParamProps extends AggParamCommonProps { paramEditor: React.ComponentType>; - onChange: OnAggParamsChange; + setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue']; + onChangeParamsState: React.Dispatch; } function DefaultEditorAggParam(props: DefaultEditorAggParamProps) { - const { agg, aggParam, paramEditor: ParamEditor, onChange, setValidity, ...rest } = props; + const { + agg, + aggParam, + paramEditor: ParamEditor, + setAggParamValue, + onChangeParamsState, + ...rest + } = props; + + const setValidity = useCallback( + (valid: boolean) => { + onChangeParamsState({ + type: AGG_PARAMS_ACTION_KEYS.VALID, + paramName: aggParam.name, + payload: valid, + }); + }, + [onChangeParamsState, aggParam.name] + ); + + // setTouched can be called from sub-agg which passes a parameter + const setTouched = useCallback( + (isTouched: boolean = true) => { + onChangeParamsState({ + type: AGG_PARAMS_ACTION_KEYS.TOUCHED, + paramName: aggParam.name, + payload: isTouched, + }); + }, + [onChangeParamsState, aggParam.name] + ); + + const setValue = useCallback( + (value: T) => { + if (props.value !== value) { + setAggParamValue(agg.id, aggParam.name, value); + } + }, + [setAggParamValue, agg.id, aggParam.name, props.value] + ); useEffect(() => { if (aggParam.shouldShow && !aggParam.shouldShow(agg)) { @@ -45,7 +86,8 @@ function DefaultEditorAggParam(props: DefaultEditorAggParamProps) { agg={agg} aggParam={aggParam} setValidity={setValidity} - setValue={(value: T) => onChange(agg.params, aggParam.name, value)} + setTouched={setTouched} + setValue={setValue} {...rest} /> ); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts index df77ea9e43eab..953f49d84f5c5 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts @@ -22,28 +22,27 @@ import { AggConfig } from '../../../../agg_types/agg_config'; import { ComboBoxGroupedOptions } from '../utils'; import { EditorConfig } from '../../config/types'; import { VisState } from '../../..'; -import { SubAggParamsProp } from './agg_params'; import { Field } from '../../../../../../../plugins/data/public'; // NOTE: we cannot export the interface with export { InterfaceName } // as there is currently a bug on babel typescript transform plugin for it // https://github.com/babel/babel/issues/7641 // -export interface AggParamCommonProps { +export interface AggParamCommonProps { agg: AggConfig; - aggParam: AggParam; + aggParam: P; disabled?: boolean; editorConfig: EditorConfig; + formIsTouched: boolean; indexedFields?: ComboBoxGroupedOptions; showValidation: boolean; state: VisState; value?: T; metricAggs: AggConfig[]; - subAggParams: SubAggParamsProp; - setValidity(isValid: boolean): void; - setTouched(): void; } -export interface AggParamEditorProps extends AggParamCommonProps { +export interface AggParamEditorProps extends AggParamCommonProps { setValue(value?: T): void; + setValidity(isValid: boolean): void; + setTouched(): void; } diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx index 9f3f5bff3f56e..8c59d69da9478 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { AggConfig, VisState } from '../../..'; import { DefaultEditorAggParams, DefaultEditorAggParamsProps } from './agg_params'; +import { AggGroupNames } from '../agg_groups'; import { IndexPattern } from '../../../../../../../plugins/data/public'; const mockEditorConfig = { @@ -79,7 +80,7 @@ jest.mock('./agg_param', () => ({ })); describe('DefaultEditorAggParams component', () => { - let onAggParamsChange: jest.Mock; + let setAggParamValue: jest.Mock; let onAggTypeChange: jest.Mock; let setTouched: jest.Mock; let setValidity: jest.Mock; @@ -87,7 +88,7 @@ describe('DefaultEditorAggParams component', () => { let defaultProps: DefaultEditorAggParamsProps; beforeEach(() => { - onAggParamsChange = jest.fn(); + setAggParamValue = jest.fn(); onAggTypeChange = jest.fn(); setTouched = jest.fn(); setValidity = jest.fn(); @@ -99,13 +100,16 @@ describe('DefaultEditorAggParams component', () => { params: [{ name: 'interval', deserialize: intervalDeserialize }], }, params: {}, + schema: { + title: '', + }, } as any) as AggConfig, - groupName: 'metrics', + groupName: AggGroupNames.Metrics, formIsTouched: false, indexPattern: {} as IndexPattern, metricAggs: [], state: {} as VisState, - onAggParamsChange, + setAggParamValue, onAggTypeChange, setTouched, setValidity, @@ -131,16 +135,16 @@ describe('DefaultEditorAggParams component', () => { it('should set fixed and default values when editorConfig is defined (works in rollup index)', () => { mount(); - expect(onAggParamsChange).toHaveBeenNthCalledWith( + expect(setAggParamValue).toHaveBeenNthCalledWith( 1, - defaultProps.agg.params, + defaultProps.agg.id, 'useNormalizedEsInterval', false ); expect(intervalDeserialize).toHaveBeenCalledWith('1m'); - expect(onAggParamsChange).toHaveBeenNthCalledWith( + expect(setAggParamValue).toHaveBeenNthCalledWith( 2, - defaultProps.agg.params, + defaultProps.agg.id, 'interval', 'deserialized' ); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx index c62e2837908c7..0f47d9a555d21 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx @@ -17,60 +17,48 @@ * under the License. */ -import React, { useReducer, useEffect, useMemo } from 'react'; +import React, { useCallback, useReducer, useEffect, useMemo } from 'react'; import { EuiForm, EuiAccordion, EuiSpacer, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import useUnmount from 'react-use/lib/useUnmount'; -import { VisState } from 'ui/vis'; -import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; +import { AggConfig } from 'ui/agg_types/'; import { IndexPattern } from '../../../../../../../plugins/data/public'; import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; import { getAggParamsToRender, - getError, getAggTypeOptions, - ParamInstance, isInvalidParamsTouched, } from './agg_params_helper'; import { aggTypeReducer, - AGG_TYPE_ACTION_KEYS, aggParamsReducer, AGG_PARAMS_ACTION_KEYS, initAggParamsState, - AggParamsItem, } from './agg_params_state'; import { editorConfigProviders } from '../../config/editor_config_providers'; import { FixedParam, TimeIntervalParam, EditorParamConfig } from '../../config/types'; import { AggGroupNames } from '../agg_groups'; -import { OnAggParamsChange } from './agg_common_props'; +import { DefaultEditorCommonProps } from './agg_common_props'; const FIXED_VALUE_PROP = 'fixedValue'; const DEFAULT_PROP = 'default'; type EditorParamConfigType = EditorParamConfig & { [key: string]: unknown; }; -export interface SubAggParamsProp { - formIsTouched: boolean; - onAggParamsChange: OnAggParamsChange; - onAggTypeChange: (agg: AggConfig, aggType: AggType) => void; -} -export interface DefaultEditorAggParamsProps extends SubAggParamsProp { + +export interface DefaultEditorAggParamsProps extends DefaultEditorCommonProps { agg: AggConfig; aggError?: string; aggIndex?: number; aggIsTooLow?: boolean; className?: string; disabledParams?: string[]; - groupName: string; indexPattern: IndexPattern; - metricAggs: AggConfig[]; - state: VisState; - setTouched: (isTouched: boolean) => void; setValidity: (isValid: boolean) => void; + setTouched: (isTouched: boolean) => void; } function DefaultEditorAggParams({ @@ -84,20 +72,34 @@ function DefaultEditorAggParams({ formIsTouched, indexPattern, metricAggs, - state = {} as VisState, - onAggParamsChange, + state, + setAggParamValue, onAggTypeChange, setTouched, setValidity, }: DefaultEditorAggParamsProps) { - const groupedAggTypeOptions = getAggTypeOptions(agg, indexPattern, groupName); - const errors = getError(agg, aggIsTooLow); - - const editorConfig = useMemo( - () => editorConfigProviders.getConfigForAgg((aggTypes as any)[groupName], indexPattern, agg), - [groupName, agg.type] - ); - const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state }); + const groupedAggTypeOptions = useMemo(() => getAggTypeOptions(agg, indexPattern, groupName), [ + agg, + indexPattern, + groupName, + ]); + const error = aggIsTooLow + ? i18n.translate('common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage', { + defaultMessage: '"{schema}" aggs must run before all other buckets!', + values: { schema: agg.schema.title }, + }) + : ''; + + const editorConfig = useMemo(() => editorConfigProviders.getConfigForAgg(indexPattern, agg), [ + indexPattern, + agg, + ]); + const params = useMemo(() => getAggParamsToRender({ agg, editorConfig, metricAggs, state }), [ + agg, + editorConfig, + metricAggs, + state, + ]); const allParams = [...params.basic, ...params.advanced]; const [paramsState, onChangeParamsState] = useReducer( aggParamsReducer, @@ -107,21 +109,30 @@ function DefaultEditorAggParams({ const [aggType, onChangeAggType] = useReducer(aggTypeReducer, { touched: false, valid: true }); const isFormValid = - !errors.length && + !error && aggType.valid && Object.entries(paramsState).every(([, paramState]) => paramState.valid); const isAllInvalidParamsTouched = - !!errors.length || isInvalidParamsTouched(agg.type, aggType, paramsState); + !!error || isInvalidParamsTouched(agg.type, aggType, paramsState); + + const onAggSelect = useCallback( + value => { + if (agg.type !== value) { + onAggTypeChange(agg.id, value); + // reset touched and valid of params + onChangeParamsState({ type: AGG_PARAMS_ACTION_KEYS.RESET }); + } + }, + [onAggTypeChange, agg] + ); // reset validity before component destroyed useUnmount(() => setValidity(true)); useEffect(() => { Object.entries(editorConfig).forEach(([param, paramConfig]) => { - const paramOptions = agg.type.params.find( - (paramOption: AggParam) => paramOption.name === param - ); + const paramOptions = agg.type.params.find(paramOption => paramOption.name === param); const hasFixedValue = paramConfig.hasOwnProperty(FIXED_VALUE_PROP); const hasDefault = paramConfig.hasOwnProperty(DEFAULT_PROP); @@ -142,7 +153,11 @@ function DefaultEditorAggParams({ } else { newValue = typedParamConfig[property]; } - onAggParamsChange(agg.params, param, newValue); + + // this check is obligatory to avoid infinite render, because setAggParamValue creates a brand new agg object + if (agg.params[param] !== newValue) { + setAggParamValue(agg.id, param, newValue); + } } }); }, [editorConfig]); @@ -160,43 +175,11 @@ function DefaultEditorAggParams({ setTouched(isAllInvalidParamsTouched); }, [isAllInvalidParamsTouched]); - const renderParam = (paramInstance: ParamInstance, model: AggParamsItem) => { - return ( - { - onChangeParamsState({ - type: AGG_PARAMS_ACTION_KEYS.VALID, - paramName: paramInstance.aggParam.name, - payload: valid, - }); - }} - // setTouched can be called from sub-agg which passes a parameter - setTouched={(isTouched: boolean = true) => { - onChangeParamsState({ - type: AGG_PARAMS_ACTION_KEYS.TOUCHED, - paramName: paramInstance.aggParam.name, - payload: isTouched, - }); - }} - subAggParams={{ - onAggParamsChange, - onAggTypeChange, - formIsTouched, - }} - {...paramInstance} - /> - ); - }; - return ( = 1 && groupName === AggGroupNames.Buckets} showValidation={formIsTouched || aggType.touched} - setValue={value => { - onAggTypeChange(agg, value); - // reset touched and valid of params - onChangeParamsState({ type: AGG_PARAMS_ACTION_KEYS.RESET }); - }} - setTouched={() => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.TOUCHED, payload: true })} - setValidity={valid => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.VALID, payload: valid })} + setValue={onAggSelect} + onChangeAggType={onChangeAggType} /> - {params.basic.map((param: ParamInstance) => { + {params.basic.map(param => { const model = paramsState[param.aggParam.name] || { touched: false, valid: true, }; - return renderParam(param, model); + return ( + + ); })} {params.advanced.length ? ( @@ -238,12 +226,23 @@ function DefaultEditorAggParams({ )} > - {params.advanced.map((param: ParamInstance) => { + {params.advanced.map(param => { const model = paramsState[param.aggParam.name] || { touched: false, valid: true, }; - return renderParam(param, model); + + return ( + + ); })} diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts index fb37dd5d94618..c983bb7813de7 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts @@ -22,7 +22,6 @@ import { AggType } from 'ui/agg_types'; import { IndexedArray } from 'ui/indexed_array'; import { getAggParamsToRender, - getError, getAggTypeOptions, isInvalidParamsTouched, } from './agg_params_helper'; @@ -162,20 +161,6 @@ describe('DefaultEditorAggParams helpers', () => { }); }); - describe('getError', () => { - it('should not have any errors', () => { - const errors = getError({ schema: { title: 'Split series' } } as AggConfig, false); - - expect(errors).toEqual([]); - }); - - it('should push an error if an agg is too low', () => { - const errors = getError({ schema: { title: 'Split series' } } as AggConfig, true); - - expect(errors).toEqual(['"Split series" aggs must run before all other buckets!']); - }); - }); - describe('getAggTypeOptions', () => { it('should return agg type options grouped by subtype', () => { const indexPattern = {} as IndexPattern; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts index e0e014f69ef3f..3970238a68435 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts @@ -18,7 +18,6 @@ */ import { get, isEmpty } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { aggTypeFilters } from 'ui/agg_types/filter'; import { aggTypes, AggParam, FieldParamType, AggType } from 'ui/agg_types'; import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; @@ -91,27 +90,13 @@ function getAggParamsToRender({ agg, editorConfig, metricAggs, state }: ParamIns metricAggs, state, value: agg.params[param.name], - } as ParamInstance); + }); } }); return params; } -function getError(agg: AggConfig, aggIsTooLow: boolean) { - const errors = []; - if (aggIsTooLow) { - errors.push( - i18n.translate('common.ui.vis.editors.aggParams.errors.aggWrongRunOrderErrorMessage', { - defaultMessage: '"{schema}" aggs must run before all other buckets!', - values: { schema: agg.schema.title }, - }) - ); - } - - return errors; -} - function getAggTypeOptions( agg: AggConfig, indexPattern: IndexPattern, @@ -148,4 +133,4 @@ function isInvalidParamsTouched( return invalidParams.every(param => param.touched); } -export { getAggParamsToRender, getError, getAggTypeOptions, isInvalidParamsTouched }; +export { getAggParamsToRender, getAggTypeOptions, isInvalidParamsTouched }; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx index 443c655912a55..6b6bb93b29b3e 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx @@ -17,7 +17,7 @@ * under the License. */ import { get, has } from 'lodash'; -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow, EuiLink, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -26,6 +26,7 @@ import { AggType } from 'ui/agg_types'; import { documentationLinks } from '../../../../documentation_links/documentation_links'; import { ComboBoxGroupedOptions } from '../utils'; import { IndexPattern } from '../../../../../../../plugins/data/public'; +import { AGG_TYPE_ACTION_KEYS, AggTypeAction } from './agg_params_state'; interface DefaultEditorAggSelectProps { aggError?: string; @@ -35,9 +36,8 @@ interface DefaultEditorAggSelectProps { showValidation: boolean; isSubAggregation: boolean; value: AggType; - setValidity: (isValid: boolean) => void; + onChangeAggType: React.Dispatch; setValue: (aggType: AggType) => void; - setTouched: () => void; } function DefaultEditorAggSelect({ @@ -49,8 +49,7 @@ function DefaultEditorAggSelect({ aggTypeOptions, showValidation, isSubAggregation, - setTouched, - setValidity, + onChangeAggType, }: DefaultEditorAggSelectProps) { const selectedOptions: ComboBoxGroupedOptions = value ? [{ label: value.title, target: value }] @@ -101,6 +100,25 @@ function DefaultEditorAggSelect({ const isValid = !!value && !errors.length; + const onChange = useCallback( + (options: EuiComboBoxOptionProps[]) => { + const selectedOption = get(options, '0.target'); + if (selectedOption) { + setValue(selectedOption as AggType); + } + }, + [setValue] + ); + + const setTouched = useCallback( + () => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.TOUCHED, payload: true }), + [onChangeAggType] + ); + const setValidity = useCallback( + valid => onChangeAggType({ type: AGG_TYPE_ACTION_KEYS.VALID, payload: valid }), + [onChangeAggType] + ); + useEffect(() => { setValidity(isValid); }, [isValid]); @@ -111,13 +129,6 @@ function DefaultEditorAggSelect({ } }, [errors.length]); - const onChange = (options: EuiComboBoxOptionProps[]) => { - const selectedOption = get(options, '0.target'); - if (selectedOption) { - setValue(selectedOption as AggType); - } - }; - return ( ; + vis: Vis; +} + +function DefaultEditorControls({ + applyChanges, + isDirty, + isInvalid, + isTouched, + dispatch, + vis, +}: DefaultEditorControlsProps) { + const { enableAutoApply } = vis.type.editorConfig; + const [autoApplyEnabled, setAutoApplyEnabled] = useState(false); + const toggleAutoApply = useCallback(e => setAutoApplyEnabled(e.target.checked), []); + const onClickDiscard = useCallback(() => dispatch(discardChanges(vis)), [dispatch, vis]); + + useDebounce( + () => { + if (autoApplyEnabled && isDirty) { + applyChanges(); + } + }, + 300, + [isDirty, autoApplyEnabled, applyChanges] + ); + + return ( +
+ {!autoApplyEnabled && ( + + + + + + + + + {isInvalid && isTouched ? ( + + + + + + ) : ( + + + + )} + + + )} + {enableAutoApply && ( + + + + )} +
+ ); +} + +export { DefaultEditorControls }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx new file mode 100644 index 0000000000000..4481ec5cb9b83 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/data_tab.tsx @@ -0,0 +1,135 @@ +/* + * 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, { useMemo, useCallback } from 'react'; +import { findLast } from 'lodash'; +import { EuiSpacer } from '@elastic/eui'; + +import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; +import { AggConfig } from 'ui/agg_types'; +import { MetricAggType } from 'ui/agg_types/metrics/metric_agg_type'; +import { VisState } from 'ui/vis'; +import { DefaultEditorAggGroup } from '../agg_group'; +import { AggGroupNames } from '../../agg_groups'; +import { + EditorAction, + addNewAgg, + removeAgg, + reorderAggs, + setAggParamValue, + changeAggType, + toggleEnabledAgg, +} from './state'; +import { ISchemas } from '../../schemas'; +import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props'; + +export interface DefaultEditorDataTabProps { + dispatch: React.Dispatch; + formIsTouched: boolean; + isTabSelected: boolean; + metricAggs: AggConfig[]; + schemas: ISchemas; + state: VisState; + setTouched(isTouched: boolean): void; + setValidity(modelName: string, value: boolean): void; + setStateValue: DefaultEditorAggCommonProps['setStateParamValue']; +} + +function DefaultEditorDataTab({ + dispatch, + formIsTouched, + metricAggs, + schemas, + state, + setTouched, + setValidity, + setStateValue, +}: DefaultEditorDataTabProps) { + const lastParentPipelineAgg = useMemo( + () => + findLast( + metricAggs, + ({ type }: { type: MetricAggType }) => type.subtype === parentPipelineAggHelper.subtype + ), + [metricAggs] + ); + const lastParentPipelineAggTitle = lastParentPipelineAgg && lastParentPipelineAgg.type.title; + + const addSchema: AddSchema = useCallback(schema => dispatch(addNewAgg(schema)), [dispatch]); + + const onAggRemove: DefaultEditorAggCommonProps['removeAgg'] = useCallback( + aggId => dispatch(removeAgg(aggId)), + [dispatch] + ); + + const onReorderAggs: ReorderAggs = useCallback((...props) => dispatch(reorderAggs(...props)), [ + dispatch, + ]); + + const onAggParamValueChange: DefaultEditorAggCommonProps['setAggParamValue'] = useCallback( + (...props) => dispatch(setAggParamValue(...props)), + [dispatch] + ); + + const onAggTypeChange: DefaultEditorAggCommonProps['onAggTypeChange'] = useCallback( + (...props) => dispatch(changeAggType(...props)), + [dispatch] + ); + + const onToggleEnableAgg: DefaultEditorAggCommonProps['onToggleEnableAgg'] = useCallback( + (...props) => dispatch(toggleEnabledAgg(...props)), + [dispatch] + ); + + const commonProps = { + addSchema, + formIsTouched, + lastParentPipelineAggTitle, + metricAggs, + state, + reorderAggs: onReorderAggs, + setAggParamValue: onAggParamValueChange, + setStateParamValue: setStateValue, + onAggTypeChange, + onToggleEnableAgg, + setValidity, + setTouched, + removeAgg: onAggRemove, + }; + + return ( + <> + + + + + + + ); +} + +export { DefaultEditorDataTab }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/index.ts new file mode 100644 index 0000000000000..31228aad85d1e --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/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 { DefaultEditorSideBar } from './sidebar'; +export { DefaultEditorDataTab } from './data_tab'; +export { OptionTab } from './navbar'; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx new file mode 100644 index 0000000000000..a1b5003a092f7 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/navbar.tsx @@ -0,0 +1,59 @@ +/* + * 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 { EuiTabs, EuiTab } from '@elastic/eui'; + +import { VisOptionsProps } from '../../vis_options_props'; +import { DefaultEditorDataTabProps } from './data_tab'; + +export interface OptionTab { + editor: React.ComponentType; + name: string; + title: string; +} + +interface DefaultEditorNavBarProps { + optionTabs: OptionTab[]; + selectedTab: string; + setSelectedTab(name: string): void; +} + +function DefaultEditorNavBar({ + selectedTab, + setSelectedTab, + optionTabs, +}: DefaultEditorNavBarProps) { + return ( + + {optionTabs.map(({ name, title }) => ( + setSelectedTab(name)} + > + {title} + + ))} + + ); +} + +export { DefaultEditorNavBar }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx b/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx new file mode 100644 index 0000000000000..bf35c46dbb7b5 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/sidebar.tsx @@ -0,0 +1,226 @@ +/* + * 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, { useMemo, useState, useCallback, KeyboardEventHandler, useEffect } from 'react'; +import { get, isEqual } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; + +import { Vis } from 'ui/vis'; +import { PersistedState } from 'ui/persisted_state'; +import { DefaultEditorNavBar, OptionTab } from './navbar'; +import { DefaultEditorControls } from './controls'; +import { setStateParamValue, useEditorReducer, useEditorFormState } from './state'; +import { AggGroupNames } from '../../agg_groups'; +import { DefaultEditorAggCommonProps } from '../agg_common_props'; + +interface DefaultEditorSideBarProps { + isCollapsed: boolean; + onClickCollapse: () => void; + optionTabs: OptionTab[]; + uiState: PersistedState; + vis: Vis; +} + +function DefaultEditorSideBar({ + isCollapsed, + onClickCollapse, + optionTabs, + uiState, + vis, +}: DefaultEditorSideBarProps) { + const [selectedTab, setSelectedTab] = useState(optionTabs[0].name); + const [isDirty, setDirty] = useState(false); + const [state, dispatch] = useEditorReducer(vis); + const { formState, setTouched, setValidity, resetValidity } = useEditorFormState(); + + const responseAggs = useMemo(() => state.aggs.getResponseAggs(), [state.aggs]); + const metricAggs = useMemo( + () => responseAggs.filter(agg => get(agg, 'schema.group') === AggGroupNames.Metrics), + [responseAggs] + ); + const hasHistogramAgg = useMemo(() => responseAggs.some(agg => agg.type.name === 'histogram'), [ + responseAggs, + ]); + + const setStateValidity = useCallback( + (value: boolean) => { + setValidity('visOptions', value); + }, + [setValidity] + ); + + const setStateValue: DefaultEditorAggCommonProps['setStateParamValue'] = useCallback( + (paramName, value) => { + const shouldUpdate = !isEqual(state.params[paramName], value); + + if (shouldUpdate) { + dispatch(setStateParamValue(paramName, value)); + } + }, + [dispatch, state.params] + ); + + const applyChanges = useCallback(() => { + if (formState.invalid || !isDirty) { + setTouched(true); + + return; + } + + vis.setCurrentState(state); + vis.updateState(); + vis.emit('dirtyStateChange', { + isDirty: false, + }); + setTouched(false); + }, [vis, state, formState.invalid, setDirty, setTouched, isDirty]); + + const onSubmit: KeyboardEventHandler = useCallback( + event => { + if (event.ctrlKey && event.keyCode === keyCodes.ENTER) { + event.preventDefault(); + event.stopPropagation(); + + applyChanges(); + } + }, + [applyChanges] + ); + + useEffect(() => { + vis.on('dirtyStateChange', ({ isDirty: dirty }: { isDirty: boolean }) => { + setDirty(dirty); + + if (!dirty) { + resetValidity(); + } + }); + }, [resetValidity, vis]); + + const dataTabProps = { + dispatch, + formIsTouched: formState.touched, + metricAggs, + state, + schemas: vis.type.schemas, + setValidity, + setTouched, + setStateValue, + }; + + const optionTabProps = { + aggs: state.aggs, + hasHistogramAgg, + stateParams: state.params, + vis, + uiState, + setValue: setStateValue, + setValidity: setStateValidity, + setTouched, + }; + + return ( + <> + + +
+ {vis.type.requiresSearch && vis.type.options.showIndexSelection ? ( + +

+ {vis.indexPattern.title} +

+
+ ) : ( +
+ )} + + {optionTabs.length > 1 && ( + + )} + + {optionTabs.map(({ editor: Editor, name }) => { + const isTabSelected = selectedTab === name; + + return ( +
+ +
+ ); + })} + + + + + + + + + + + ); +} + +export { DefaultEditorSideBar }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts new file mode 100644 index 0000000000000..ab1d65c626ae3 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/actions.ts @@ -0,0 +1,171 @@ +/* + * 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 { AggConfig, Vis, VisParams } from 'ui/vis'; +import { EditorStateActionTypes } from './constants'; +import { Schema } from '../../../schemas'; + +export interface ActionType { + type: T; + payload: P; +} + +type AggId = AggConfig['id']; +type AggParams = AggConfig['params']; + +type AddNewAgg = ActionType; +type DiscardChanges = ActionType; +type ChangeAggType = ActionType< + EditorStateActionTypes.CHANGE_AGG_TYPE, + { aggId: AggId; value: AggConfig['type'] } +>; +type SetAggParamValue = ActionType< + EditorStateActionTypes.SET_AGG_PARAM_VALUE, + { + aggId: AggId; + paramName: T; + value: AggParams[T]; + } +>; +type SetStateParamValue = ActionType< + EditorStateActionTypes.SET_STATE_PARAM_VALUE, + { paramName: T; value: AggParams[T] } +>; +type RemoveAgg = ActionType; +type ReorderAggs = ActionType< + EditorStateActionTypes.REORDER_AGGS, + { sourceAgg: AggConfig; destinationAgg: AggConfig } +>; +type ToggleEnabledAgg = ActionType< + EditorStateActionTypes.TOGGLE_ENABLED_AGG, + { aggId: AggId; enabled: AggConfig['enabled'] } +>; +type UpdateStateParams = ActionType< + EditorStateActionTypes.UPDATE_STATE_PARAMS, + { params: VisParams } +>; + +export type EditorAction = + | AddNewAgg + | DiscardChanges + | ChangeAggType + | SetAggParamValue + | SetStateParamValue + | RemoveAgg + | ReorderAggs + | ToggleEnabledAgg + | UpdateStateParams; + +export interface EditorActions { + addNewAgg(schema: Schema): AddNewAgg; + discardChanges(vis: Vis): DiscardChanges; + changeAggType(aggId: AggId, value: AggConfig['type']): ChangeAggType; + setAggParamValue( + aggId: AggId, + paramName: T, + value: AggParams[T] + ): SetAggParamValue; + setStateParamValue( + paramName: T, + value: AggParams[T] + ): SetStateParamValue; + removeAgg(aggId: AggId): RemoveAgg; + reorderAggs(sourceAgg: AggConfig, destinationAgg: AggConfig): ReorderAggs; + toggleEnabledAgg(aggId: AggId, enabled: AggConfig['enabled']): ToggleEnabledAgg; + updateStateParams(params: VisParams): UpdateStateParams; +} + +const addNewAgg: EditorActions['addNewAgg'] = schema => ({ + type: EditorStateActionTypes.ADD_NEW_AGG, + payload: { + schema, + }, +}); + +const discardChanges: EditorActions['discardChanges'] = vis => ({ + type: EditorStateActionTypes.DISCARD_CHANGES, + payload: vis, +}); + +const changeAggType: EditorActions['changeAggType'] = (aggId, value) => ({ + type: EditorStateActionTypes.CHANGE_AGG_TYPE, + payload: { + aggId, + value, + }, +}); + +const setAggParamValue: EditorActions['setAggParamValue'] = (aggId, paramName, value) => ({ + type: EditorStateActionTypes.SET_AGG_PARAM_VALUE, + payload: { + aggId, + paramName, + value, + }, +}); + +const setStateParamValue: EditorActions['setStateParamValue'] = (paramName, value) => ({ + type: EditorStateActionTypes.SET_STATE_PARAM_VALUE, + payload: { + paramName, + value, + }, +}); + +const removeAgg: EditorActions['removeAgg'] = aggId => ({ + type: EditorStateActionTypes.REMOVE_AGG, + payload: { + aggId, + }, +}); + +const reorderAggs: EditorActions['reorderAggs'] = (sourceAgg, destinationAgg) => ({ + type: EditorStateActionTypes.REORDER_AGGS, + payload: { + sourceAgg, + destinationAgg, + }, +}); + +const toggleEnabledAgg: EditorActions['toggleEnabledAgg'] = (aggId, enabled) => ({ + type: EditorStateActionTypes.TOGGLE_ENABLED_AGG, + payload: { + aggId, + enabled, + }, +}); + +const updateStateParams: EditorActions['updateStateParams'] = params => ({ + type: EditorStateActionTypes.UPDATE_STATE_PARAMS, + payload: { + params, + }, +}); + +export { + addNewAgg, + discardChanges, + changeAggType, + setAggParamValue, + setStateParamValue, + removeAgg, + reorderAggs, + toggleEnabledAgg, + updateStateParams, +}; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts new file mode 100644 index 0000000000000..2c5f5f1384858 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/constants.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export enum EditorStateActionTypes { + ADD_NEW_AGG = 'ADD_NEW_AGG', + DISCARD_CHANGES = 'DISCARD_CHANGES', + CHANGE_AGG_TYPE = 'CHANGE_AGG_TYPE', + SET_AGG_PARAM_VALUE = 'SET_AGG_PARAM_VALUE', + SET_STATE_PARAM_VALUE = 'SET_STATE_PARAM_VALUE', + TOGGLE_ENABLED_AGG = 'TOGGLE_ENABLED_AGG', + REMOVE_AGG = 'REMOVE_AGG', + REORDER_AGGS = 'REORDER_AGGS', + UPDATE_STATE_PARAMS = 'UPDATE_STATE_PARAMS', +} diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts new file mode 100644 index 0000000000000..1f98a5f7fa7df --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/editor_form_state.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useState, useCallback } from 'react'; + +export type SetValidity = (modelName: string, value: boolean) => void; +export type SetTouched = (value: boolean) => void; + +const initialFormState = { + validity: {}, + touched: false, + invalid: false, +}; + +function useEditorFormState() { + const [formState, setFormState] = useState(initialFormState); + + const setValidity: SetValidity = useCallback((modelName, value) => { + setFormState(model => { + const validity = { + ...model.validity, + [modelName]: value, + }; + + return { + ...model, + validity, + invalid: Object.values(validity).some(valid => !valid), + }; + }); + }, []); + + const resetValidity = useCallback(() => { + setFormState(initialFormState); + }, []); + + const setTouched = useCallback((touched: boolean) => { + setFormState(model => ({ + ...model, + touched, + })); + }, []); + + return { + formState, + setValidity, + setTouched, + resetValidity, + }; +} + +export { useEditorFormState }; diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts new file mode 100644 index 0000000000000..6dbd9a69d82c0 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/index.ts @@ -0,0 +1,59 @@ +/* + * 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, useReducer, useCallback } from 'react'; +import { isEqual } from 'lodash'; + +import { Vis, VisState, VisParams } from 'ui/vis'; +import { editorStateReducer, initEditorState } from './reducers'; +import { EditorStateActionTypes } from './constants'; +import { EditorAction, updateStateParams } from './actions'; + +export * from './editor_form_state'; +export * from './actions'; + +export function useEditorReducer(vis: Vis): [VisState, React.Dispatch] { + const [state, dispatch] = useReducer(editorStateReducer, vis, initEditorState); + + useEffect(() => { + const handleVisUpdate = (params: VisParams) => { + if (!isEqual(params, state.params)) { + dispatch(updateStateParams(params)); + } + }; + + // fires when visualization state changes, and we need to copy changes to editorState + vis.on('updateEditorStateParams', handleVisUpdate); + + return () => vis.off('updateEditorStateParams', handleVisUpdate); + }, [vis, state.params]); + + const wrappedDispatch = useCallback( + (action: EditorAction) => { + dispatch(action); + + vis.emit('dirtyStateChange', { + isDirty: action.type !== EditorStateActionTypes.DISCARD_CHANGES, + }); + }, + [vis] + ); + + return [state, wrappedDispatch]; +} diff --git a/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts new file mode 100644 index 0000000000000..db52291c823e7 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/components/sidebar/state/reducers.ts @@ -0,0 +1,182 @@ +/* + * 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 { cloneDeep } from 'lodash'; + +import { AggConfigs, AggConfig } from 'ui/agg_types'; +import { Vis, VisState } from 'ui/vis'; +import { move } from 'ui/utils/collection'; +import { EditorStateActionTypes } from './constants'; +import { AggGroupNames } from '../../../agg_groups'; +import { getEnabledMetricAggsCount } from '../../agg_group_helper'; +import { EditorAction } from './actions'; + +function initEditorState(vis: Vis) { + return vis.copyCurrentState(true); +} + +function editorStateReducer(state: VisState, action: EditorAction): VisState { + switch (action.type) { + case EditorStateActionTypes.ADD_NEW_AGG: { + const aggConfig = state.aggs.createAggConfig(action.payload as AggConfig, { + addToAggConfigs: false, + }); + aggConfig.brandNew = true; + const newAggs = [...state.aggs.aggs, aggConfig]; + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.DISCARD_CHANGES: { + return initEditorState(action.payload); + } + + case EditorStateActionTypes.CHANGE_AGG_TYPE: { + const { aggId, value } = action.payload; + + const newAggs = state.aggs.aggs.map(agg => { + if (agg.id === aggId) { + agg.type = value; + + return agg.toJSON(); + } + + return agg; + }); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.SET_AGG_PARAM_VALUE: { + const { aggId, paramName, value } = action.payload; + + const newAggs = state.aggs.aggs.map(agg => { + if (agg.id === aggId) { + const parsedAgg = agg.toJSON(); + + return { + ...parsedAgg, + params: { + ...parsedAgg.params, + [paramName]: value, + }, + }; + } + + return agg; + }); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.SET_STATE_PARAM_VALUE: { + const { paramName, value } = action.payload; + + return { + ...state, + params: { + ...state.params, + [paramName]: value, + }, + }; + } + + case EditorStateActionTypes.REMOVE_AGG: { + let isMetric = false; + + const newAggs = state.aggs.aggs.filter(({ id, schema }) => { + if (id === action.payload.aggId) { + if (schema.group === AggGroupNames.Metrics) { + isMetric = true; + } + + return false; + } + + return true; + }); + + if (isMetric && getEnabledMetricAggsCount(newAggs) === 0) { + const aggToEnable = newAggs.find(agg => agg.schema.name === 'metric'); + + if (aggToEnable) { + aggToEnable.enabled = true; + } + } + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.REORDER_AGGS: { + const { sourceAgg, destinationAgg } = action.payload; + const destinationIndex = state.aggs.aggs.indexOf(destinationAgg); + const newAggs = move([...state.aggs.aggs], sourceAgg, destinationIndex); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.TOGGLE_ENABLED_AGG: { + const { aggId, enabled } = action.payload; + + const newAggs = state.aggs.aggs.map(agg => { + if (agg.id === aggId) { + const parsedAgg = agg.toJSON(); + + return { + ...parsedAgg, + enabled, + }; + } + + return agg; + }); + + return { + ...state, + aggs: new AggConfigs(state.aggs.indexPattern, newAggs, state.aggs.schemas), + }; + } + + case EditorStateActionTypes.UPDATE_STATE_PARAMS: { + const { params } = action.payload; + + return { + ...state, + params: cloneDeep(params), + }; + } + } +} + +export { editorStateReducer, initEditorState }; diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx index f215cf755886d..55cd237a56689 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx @@ -17,12 +17,12 @@ * under the License. */ -import { VisParams } from '../../..'; -import { AggParams } from '../agg_params'; -import { OnAggParamsChange } from '../components/agg_common_props'; +import { AggConfig, VisParams } from 'ui/vis'; +import { DefaultEditorAggCommonProps } from '../components/agg_common_props'; export interface AggControlProps { - aggParams: AggParams; + agg: AggConfig; editorStateParams: VisParams; - setValue: OnAggParamsChange; + setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue']; + setStateParamValue: DefaultEditorAggCommonProps['setStateParamValue']; } diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts index 6491ef2e46054..98e4931b23ea3 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts @@ -55,7 +55,7 @@ function useFallbackMetric( .filter(isCompatibleAgg) .find(aggregation => aggregation.id === value); - if (!respAgg) { + if (!respAgg && value !== fallbackValue) { setValue(fallbackValue); } } diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx index 4d15ac8e80e63..67ce3ba6d5072 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx @@ -22,7 +22,7 @@ import { act } from 'react-dom/test-utils'; import { mount, shallow, ReactWrapper } from 'enzyme'; import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui'; import { Field } from '../../../../../../../plugins/data/public'; -import { ComboBoxGroupedOptions, SubAggParamsProp } from '..'; +import { ComboBoxGroupedOptions } from '..'; import { FieldParamEditor, FieldParamEditorProps } from './field'; import { AggConfig, VisState } from '../../..'; @@ -69,6 +69,7 @@ describe('FieldParamEditor component', () => { editorComponent: () => null, onChange, } as any, + formIsTouched: false, value: undefined, editorConfig: {}, indexedFields, @@ -78,7 +79,6 @@ describe('FieldParamEditor component', () => { setTouched, state: {} as VisState, metricAggs: [] as AggConfig[], - subAggParams: {} as SubAggParamsProp, }; }); @@ -130,15 +130,6 @@ describe('FieldParamEditor component', () => { expect(setValidity).toHaveBeenCalledWith(false); }); - it('should call setTouched when the control is invalid', () => { - defaultProps.value = field; - const comp = mount(); - expect(setTouched).not.toHaveBeenCalled(); - comp.setProps({ customError: 'customError' }); - - expect(setTouched).toHaveBeenCalled(); - }); - it('should call onChange when a field selected', () => { const comp = mount(); act(() => { diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.tsx index 75a9e24cd0dee..b8cd0d630a019 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/field.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/field.tsx @@ -26,6 +26,7 @@ import { AggConfig } from '../../..'; import { Field } from '../../../../../../../plugins/data/public'; import { formatListAsProse, parseCommaSeparatedList } from '../../../../../../utils'; import { AggParam, FieldParamType } from '../../../../agg_types'; +import { useValidation } from './agg_utils'; import { AggParamEditorProps, ComboBoxGroupedOptions } from '..'; const label = i18n.translate('common.ui.aggTypes.field.fieldLabel', { defaultMessage: 'Field' }); @@ -78,13 +79,7 @@ function FieldParamEditor({ const isValid = !!value && !errors.length; - useEffect(() => { - setValidity(isValid); - - if (!!errors.length) { - setTouched(); - } - }, [isValid]); + useValidation(setValidity, isValid); useEffect(() => { // set field if only one available diff --git a/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx index 9d337035f8734..efa8366ec550b 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/order_agg.tsx @@ -17,41 +17,43 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; import { AggParamType } from '../../../../agg_types/param_types/agg'; import { AggConfig } from '../../..'; +import { useSubAggParamsHandlers } from './utils'; +import { AggGroupNames } from '../agg_groups'; import { AggParamEditorProps, DefaultEditorAggParams } from '..'; function OrderAggParamEditor({ agg, + aggParam, + formIsTouched, value, metricAggs, state, setValue, setValidity, setTouched, - subAggParams, -}: AggParamEditorProps) { +}: AggParamEditorProps) { + const orderBy = agg.params.orderBy; + useEffect(() => { - if (metricAggs) { - const orderBy = agg.params.orderBy; + if (orderBy === 'custom' && !value) { + setValue(aggParam.makeAgg(agg)); + } - // we aren't creating a custom aggConfig - if (!orderBy || orderBy !== 'custom') { - setValue(undefined); - } else if (value) { - setValue(value); - } else { - const paramDef = agg.type.paramByName('orderAgg'); - if (paramDef) { - setValue((paramDef as AggParamType).makeAgg(agg)); - } - } + if (orderBy !== 'custom' && value) { + setValue(undefined); } - }, [agg.params.orderBy, metricAggs]); + }, [orderBy]); - const [innerState, setInnerState] = useState(true); + const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( + agg, + aggParam, + value as AggConfig, + setValue + ); if (!agg.params.orderAgg) { return null; @@ -62,18 +64,14 @@ function OrderAggParamEditor({ { - // to force update when sub-agg params are changed - setInnerState(!innerState); - subAggParams.onAggParamsChange(...rest); - }} - onAggTypeChange={subAggParams.onAggTypeChange} + setAggParamValue={setAggParamValue} + onAggTypeChange={onAggTypeChange} setValidity={setValidity} setTouched={setTouched} /> 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 88f389cb7c009..4fe9eede8465e 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/editors/default/controls/radius_ratio_option.tsx b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx index d9e0abc0cce59..4d481bd74e8a3 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiFormRow, EuiIconTip, EuiRange, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -26,7 +26,7 @@ import { AggControlProps } from './agg_control_props'; const DEFAULT_VALUE = 50; const PARAM_NAME = 'radiusRatio'; -function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlProps) { +function RadiusRatioOptionControl({ editorStateParams, setStateParamValue }: AggControlProps) { const label = ( <> { if (!editorStateParams.radiusRatio) { - setValue(editorStateParams, PARAM_NAME, DEFAULT_VALUE); + setStateParamValue(PARAM_NAME, DEFAULT_VALUE); } }, []); + const onChange = useCallback( + (e: React.ChangeEvent | React.MouseEvent) => + setStateParamValue(PARAM_NAME, parseFloat(e.currentTarget.value)), + [setStateParamValue] + ); + return ( <> @@ -58,9 +64,7 @@ function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlPro min={1} max={100} value={editorStateParams.radiusRatio || DEFAULT_VALUE} - onChange={( - e: React.ChangeEvent | React.MouseEvent - ) => setValue(editorStateParams, PARAM_NAME, parseFloat(e.currentTarget.value))} + onChange={onChange} showRange showValue valueAppend="%" diff --git a/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx b/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx index e6e4d34aea836..3ba4279a370dd 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/rows_or_columns.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiButtonGroup, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AggControlProps } from './agg_control_props'; @@ -28,8 +28,8 @@ const PARAMS = { COLUMNS: 'visEditorSplitBy__false', }; -function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { - const idSelected = `visEditorSplitBy__${aggParams.row}`; +function RowsOrColumnsControl({ agg, setAggParamValue }: AggControlProps) { + const idSelected = `visEditorSplitBy__${agg.params.row}`; const options = [ { id: PARAMS.ROWS, @@ -44,6 +44,10 @@ function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { }), }, ]; + const onChange = useCallback( + optionId => setAggParamValue(agg.id, PARAMS.NAME, optionId === PARAMS.ROWS), + [setAggParamValue] + ); return ( <> @@ -56,7 +60,7 @@ function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { options={options} isFullWidth={true} idSelected={idSelected} - onChange={optionId => setValue(aggParams, PARAMS.NAME, optionId === PARAMS.ROWS)} + onChange={onChange} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/string.tsx b/src/legacy/ui/public/vis/editors/default/controls/string.tsx index 63827205afdb3..dbfd0a7db33fb 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/string.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/string.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import { AggParamEditorProps } from '..'; @@ -37,6 +37,8 @@ function StringParamEditor({ setValidity(isValid); }, [isValid]); + const onChange = useCallback(ev => setValue(ev.target.value), [setValue]); + return ( setValue(ev.target.value)} + onChange={onChange} fullWidth={true} compressed onBlur={setTouched} diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx index 559a3f47db563..b233480cb35ba 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/sub_agg.tsx @@ -17,35 +17,40 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { AggParamType } from '../../../../agg_types/param_types/agg'; + +import { AggParamType } from 'ui/agg_types/param_types/agg'; import { AggConfig } from '../../..'; -import { AggParamEditorProps, DefaultEditorAggParams } from '..'; +import { useSubAggParamsHandlers } from './utils'; +import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..'; function SubAggParamEditor({ agg, + aggParam, + formIsTouched, value, metricAggs, state, setValue, setValidity, setTouched, - subAggParams, -}: AggParamEditorProps) { +}: AggParamEditorProps) { useEffect(() => { // we aren't creating a custom aggConfig if (agg.params.metricAgg !== 'custom') { setValue(undefined); } else if (!agg.params.customMetric) { - const customMetric = agg.type.paramByName('customMetric'); - if (customMetric) { - setValue((customMetric as AggParamType).makeAgg(agg)); - } + setValue(aggParam.makeAgg(agg)); } }, [value, metricAggs]); - const [innerState, setInnerState] = useState(true); + const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( + agg, + aggParam, + agg.params.customMetric, + setValue + ); if (agg.params.metricAgg !== 'custom' || !agg.params.customMetric) { return null; @@ -56,18 +61,14 @@ function SubAggParamEditor({ { - // to force update when sub-agg params are changed - setInnerState(!innerState); - subAggParams.onAggParamsChange(...rest); - }} - onAggTypeChange={subAggParams.onAggTypeChange} + setAggParamValue={setAggParamValue} + onAggTypeChange={onAggTypeChange} setValidity={setValidity} setTouched={setTouched} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx index df1640273135e..d0a44d1d35d1c 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/sub_metric.tsx @@ -17,23 +17,25 @@ * under the License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggParamType } from '../../../../agg_types/param_types/agg'; + +import { AggParamType } from 'ui/agg_types/param_types/agg'; import { AggConfig } from '../../../../agg_types/agg_config'; +import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps, DefaultEditorAggParams, AggGroupNames } from '..'; function SubMetricParamEditor({ agg, aggParam, + formIsTouched, metricAggs, state, setValue, setValidity, setTouched, - subAggParams, -}: AggParamEditorProps) { +}: AggParamEditorProps) { const metricTitle = i18n.translate('common.ui.aggTypes.metrics.metricTitle', { defaultMessage: 'Metric', }); @@ -49,14 +51,16 @@ function SubMetricParamEditor({ if (agg.params[type]) { setValue(agg.params[type]); } else { - const param = agg.type.paramByName(type); - if (param) { - setValue((param as AggParamType).makeAgg(agg)); - } + setValue(aggParam.makeAgg(agg)); } }, []); - const [innerState, setInnerState] = useState(true); + const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( + agg, + aggParam, + agg.params[type], + setValue + ); if (!agg.params[type]) { return null; @@ -71,16 +75,12 @@ function SubMetricParamEditor({ agg={agg.params[type]} groupName={aggGroup} className="visEditorAgg__subAgg" - formIsTouched={subAggParams.formIsTouched} + formIsTouched={formIsTouched} indexPattern={agg.getIndexPattern()} metricAggs={metricAggs} state={state} - onAggParamsChange={(...rest) => { - // to force update when sub-agg params are changed - setInnerState(!innerState); - subAggParams.onAggParamsChange(...rest); - }} - onAggTypeChange={subAggParams.onAggTypeChange} + setAggParamValue={setAggParamValue} + onAggTypeChange={onAggTypeChange} setValidity={setValidity} setTouched={setTouched} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts index 2e00b62d9b7fd..c5abf31a3cd8f 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts +++ b/src/legacy/ui/public/vis/editors/default/controls/test_utils.ts @@ -20,14 +20,13 @@ import { AggConfig, VisState } from '../../..'; import { EditorConfig } from '../../config/types'; import { AggParam } from '../../../../agg_types'; -import { SubAggParamsProp } from '..'; export const aggParamCommonPropsMock = { agg: {} as AggConfig, aggParam: {} as AggParam, editorConfig: {} as EditorConfig, + formIsTouched: false, metricAggs: [] as AggConfig[], - subAggParams: {} as SubAggParamsProp, state: {} as VisState, showValidation: false, }; diff --git a/src/legacy/ui/public/vis/editors/default/controls/utils.ts b/src/legacy/ui/public/vis/editors/default/controls/utils.ts new file mode 100644 index 0000000000000..5fd7c284fa23d --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/utils.ts @@ -0,0 +1,65 @@ +/* + * 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 } from 'react'; +import { AggConfig } from 'ui/vis'; +import { AggParamType } from 'ui/agg_types/param_types/agg'; + +type SetValue = (value?: AggConfig) => void; + +function useSubAggParamsHandlers( + agg: AggConfig, + aggParam: AggParamType, + subAgg: AggConfig, + setValue: SetValue +) { + const setAggParamValue = useCallback( + (aggId, paramName, val) => { + const parsedParams = subAgg.toJSON(); + const params = { + ...parsedParams, + params: { + ...parsedParams.params, + [paramName]: val, + }, + }; + + setValue(aggParam.makeAgg(agg, params)); + }, + [agg, aggParam, setValue, subAgg] + ); + + const onAggTypeChange = useCallback( + (aggId, aggType) => { + const parsedAgg = subAgg.toJSON(); + + const params = { + ...parsedAgg, + type: aggType, + }; + + setValue(aggParam.makeAgg(agg, params)); + }, + [agg, aggParam, setValue, subAgg] + ); + + return { onAggTypeChange, setAggParamValue }; +} + +export { useSubAggParamsHandlers }; diff --git a/src/legacy/ui/public/vis/editors/default/default.html b/src/legacy/ui/public/vis/editors/default/default.html deleted file mode 100644 index 60fcbafdb88f5..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/default.html +++ /dev/null @@ -1,17 +0,0 @@ -
-
- - -
- -
- -
diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js deleted file mode 100644 index 66f52ea84398f..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import 'ui/angular-bootstrap'; -import './fancy_forms'; -import './sidebar'; -import { i18n } from '@kbn/i18n'; -import './vis_options'; -import './vis_editor_resizer'; -import './vis_type_agg_filter'; -import $ from 'jquery'; - -import _ from 'lodash'; -import angular from 'angular'; -import defaultEditorTemplate from './default.html'; -import { keyCodes } from '@elastic/eui'; -import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; -import { DefaultEditorSize } from '../../editor_size'; - -import { AggGroupNames } from './agg_groups'; - -import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; - -const defaultEditor = function($rootScope, $compile) { - return class DefaultEditor { - static key = 'default'; - - constructor(el, savedObj) { - this.el = $(el); - this.savedObj = savedObj; - this.vis = savedObj.vis; - - if (!this.vis.type.editorConfig.optionTabs && this.vis.type.editorConfig.optionsTemplate) { - this.vis.type.editorConfig.optionTabs = [ - { - name: 'options', - title: i18n.translate('common.ui.vis.editors.sidebar.tabs.optionsLabel', { - defaultMessage: 'Options', - }), - editor: this.vis.type.editorConfig.optionsTemplate, - }, - ]; - } - } - - render({ uiState, timeRange, filters, query, appState }) { - let $scope; - - const updateScope = () => { - $scope.vis = this.vis; - $scope.uiState = uiState; - //$scope.$apply(); - }; - - return new Promise(async resolve => { - if (!this.$scope) { - this.$scope = $scope = $rootScope.$new(); - - updateScope(); - - $scope.state = $scope.vis.copyCurrentState(true); - $scope.oldState = $scope.vis.getSerializableState($scope.state); - - $scope.toggleSidebar = () => { - $scope.$broadcast('render'); - }; - - this.el.one('renderComplete', resolve); - // track state of editable vis vs. "actual" vis - $scope.stageEditableVis = () => { - $scope.oldState = $scope.vis.getSerializableState($scope.state); - $scope.vis.setCurrentState($scope.state); - $scope.vis.updateState(); - $scope.vis.dirty = false; - }; - $scope.resetEditableVis = () => { - $scope.state = $scope.vis.copyCurrentState(true); - $scope.vis.dirty = false; - }; - - $scope.autoApplyEnabled = false; - if ($scope.vis.type.editorConfig.enableAutoApply) { - $scope.toggleAutoApply = () => { - $scope.autoApplyEnabled = !$scope.autoApplyEnabled; - }; - - $scope.$watch( - 'vis.dirty', - _.debounce(() => { - if (!$scope.autoApplyEnabled || !$scope.vis.dirty) return; - $scope.stageEditableVis(); - }, 800) - ); - } - - $scope.submitEditorWithKeyboard = event => { - if (event.ctrlKey && event.keyCode === keyCodes.ENTER) { - event.preventDefault(); - event.stopPropagation(); - $scope.stageEditableVis(); - } - }; - - $scope.getSidebarClass = () => { - if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.SMALL) { - return 'visEditor__collapsibleSidebar--small'; - } else if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.MEDIUM) { - return 'visEditor__collapsibleSidebar--medium'; - } else if ($scope.vis.type.editorConfig.defaultSize === DefaultEditorSize.LARGE) { - return 'visEditor__collapsibleSidebar--large'; - } - }; - - $scope.$watch( - () => { - return $scope.vis.getSerializableState($scope.state); - }, - function(newState) { - $scope.vis.dirty = !angular.equals(newState, $scope.oldState); - const responseAggs = $scope.state.aggs.getResponseAggs(); - $scope.hasHistogramAgg = responseAggs.some(agg => agg.type.name === 'histogram'); - $scope.metricAggs = responseAggs.filter( - agg => _.get(agg, 'schema.group') === AggGroupNames.Metrics - ); - const lastParentPipelineAgg = _.findLast( - $scope.metricAggs, - ({ type }) => type.subtype === parentPipelineAggHelper.subtype - ); - $scope.lastParentPipelineAggTitle = - lastParentPipelineAgg && lastParentPipelineAgg.type.title; - }, - true - ); - - // fires when visualization state changes, and we need to copy changes to editorState - $scope.$watch( - () => { - return $scope.vis.getCurrentState(false); - }, - newState => { - if (!_.isEqual(newState, $scope.oldState)) { - $scope.state = $scope.vis.copyCurrentState(true); - $scope.oldState = newState; - } - }, - true - ); - - // Load the default editor template, attach it to the DOM and compile it. - // It should be added to the DOM before compiling, to prevent some resize - // listener issues. - const template = $(defaultEditorTemplate); - this.el.html(template); - $compile(template)($scope); - } else { - $scope = this.$scope; - updateScope(); - } - - if (!this._handler) { - const visualizationEl = this.el.find('.visEditor__canvas')[0]; - - this._handler = await embeddables - .getEmbeddableFactory('visualization') - .createFromObject(this.savedObj, { - uiState: uiState, - appState, - timeRange: timeRange, - filters: filters || [], - query: query, - }); - this._handler.render(visualizationEl); - } else { - this._handler.updateInput({ - timeRange: timeRange, - filters: filters || [], - query: query, - }); - } - }); - } - - resize() {} - - destroy() { - if (this.$scope) { - this.$scope.$destroy(); - this.$scope = null; - } - if (this._handler) { - this._handler.destroy(); - } - } - }; -}; - -export { defaultEditor }; diff --git a/src/legacy/ui/public/vis/editors/default/default_editor.tsx b/src/legacy/ui/public/vis/editors/default/default_editor.tsx new file mode 100644 index 0000000000000..3e99bb83d224f --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/default_editor.tsx @@ -0,0 +1,127 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useRef, useState, useCallback } from 'react'; + +import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; +import { EditorRenderProps } from '../../../../../core_plugins/kibana/public/visualize/np_ready/types'; +import { VisualizeEmbeddable } from '../../../../../core_plugins/visualizations/public/embeddable'; +import { VisualizeEmbeddableFactory } from '../../../../../core_plugins/visualizations/public/embeddable/visualize_embeddable_factory'; +import { + PanelsContainer, + Panel, +} from '../../../../../core_plugins/console/public/np_ready/application/components/split_panel'; + +import './vis_type_agg_filter'; +import { DefaultEditorSideBar } from './components/sidebar'; +import { DefaultEditorControllerState } from './default_editor_controller'; +import { getInitialWidth } from '../../editor_size'; + +function DefaultEditor({ + savedObj, + uiState, + timeRange, + filters, + appState, + optionTabs, + query, +}: DefaultEditorControllerState & EditorRenderProps) { + const visRef = useRef(null); + const visHandler = useRef(null); + const [isCollapsed, setIsCollapsed] = useState(false); + const [factory, setFactory] = useState(null); + const { vis } = savedObj; + + const onClickCollapse = useCallback(() => { + setIsCollapsed(value => !value); + }, []); + + useEffect(() => { + async function visualize() { + if (!visRef.current || (!visHandler.current && factory)) { + return; + } + + if (!visHandler.current) { + const embeddableFactory = embeddables.getEmbeddableFactory( + 'visualization' + ) as VisualizeEmbeddableFactory; + setFactory(embeddableFactory); + + visHandler.current = (await embeddableFactory.createFromObject(savedObj, { + // should be look through createFromObject interface again because of "id" param + id: '', + uiState, + appState, + timeRange, + filters, + query, + })) as VisualizeEmbeddable; + + visHandler.current.render(visRef.current); + } else { + visHandler.current.updateInput({ + timeRange, + filters, + query, + }); + } + } + + visualize(); + }, [uiState, savedObj, timeRange, filters, appState, query, factory]); + + useEffect(() => { + return () => { + if (visHandler.current) { + visHandler.current.destroy(); + } + }; + }, []); + + const editorInitialWidth = getInitialWidth(vis.type.editorConfig.defaultSize); + + return ( + + +
+ + + + + + + ); +} + +export { DefaultEditor }; diff --git a/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx b/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx new file mode 100644 index 0000000000000..bf843a98deaa5 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/default_editor_controller.tsx @@ -0,0 +1,89 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; + +import { EditorRenderProps } from '../../../../../core_plugins/kibana/public/visualize/np_ready/types'; +import { VisSavedObject } from '../../../../../core_plugins/visualizations/public/embeddable/visualize_embeddable'; +import { DefaultEditor } from './default_editor'; +import { DefaultEditorDataTab, OptionTab } from './components/sidebar'; + +export interface DefaultEditorControllerState { + savedObj: VisSavedObject; + optionTabs: OptionTab[]; +} + +class DefaultEditorController { + private el: HTMLElement; + private state: DefaultEditorControllerState; + + constructor(el: HTMLElement, savedObj: VisSavedObject) { + this.el = el; + const { type: visType } = savedObj.vis; + + const optionTabs = [ + ...(visType.schemas.buckets || visType.schemas.metrics + ? [ + { + name: 'data', + title: i18n.translate('common.ui.vis.editors.sidebar.tabs.dataLabel', { + defaultMessage: 'Data', + }), + editor: DefaultEditorDataTab, + }, + ] + : []), + + ...(!visType.editorConfig.optionTabs && visType.editorConfig.optionsTemplate + ? [ + { + name: 'options', + title: i18n.translate('common.ui.vis.editors.sidebar.tabs.optionsLabel', { + defaultMessage: 'Options', + }), + editor: visType.editorConfig.optionsTemplate, + }, + ] + : visType.editorConfig.optionTabs), + ]; + + this.state = { + savedObj, + optionTabs, + }; + } + + render(props: EditorRenderProps) { + render( + + + , + this.el + ); + } + + destroy() { + unmountComponentAtNode(this.el); + } +} + +export { DefaultEditorController }; diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js deleted file mode 100644 index e9fda6bb9efab..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/fancy_forms.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import $ from 'jquery'; - -describe('fancy forms', function() { - let $el; - let $scope; - let $compile; - let $rootScope; - let ngForm; - - function generateEl() { - return $('
').html($('')); - } - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject(function($injector) { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - - $scope = $rootScope.$new(); - $el = generateEl(); - - $compile($el)($scope); - $scope.$apply(); - - ngForm = $el.controller('form'); - }) - ); - - describe('ngFormController', function() { - it('counts errors', function() { - expect(ngForm.errorCount()).to.be(1); - }); - - it('clears errors', function() { - $scope.val = 'something'; - $scope.$apply(); - expect(ngForm.errorCount()).to.be(0); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/nested_fancy_forms.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/nested_fancy_forms.js deleted file mode 100644 index 969fa3bfbd888..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/__tests__/nested_fancy_forms.js +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import testSubjSelector from '@kbn/test-subj-selector'; -import sinon from 'sinon'; -import $ from 'jquery'; - -const template = ` - - -
    -
  • - - - - -
  • -
- -
-`; - -describe('fancy forms', function() { - let setup; - const trash = []; - - beforeEach(ngMock.module('kibana')); - beforeEach( - ngMock.inject($injector => { - const $rootScope = $injector.get('$rootScope'); - const $compile = $injector.get('$compile'); - - setup = function(options = {}) { - const { name = 'person1', tasks = [], onSubmit = () => {} } = options; - - const $el = $(template).appendTo('body'); - trash.push(() => $el.remove()); - const $scope = $rootScope.$new(); - - $scope.name = name; - $scope.tasks = tasks; - $scope.onSubmit = onSubmit; - - $compile($el)($scope); - $scope.$apply(); - - return { - $el, - $scope, - }; - }; - }) - ); - - afterEach(() => trash.splice(0).forEach(fn => fn())); - - describe('nested forms', function() { - it('treats new fields as "soft" errors', function() { - const { $scope } = setup({ name: '' }); - expect($scope.person.errorCount()).to.be(1); - expect($scope.person.softErrorCount()).to.be(0); - }); - - it('upgrades fields to regular errors on attempted submit', function() { - const { $scope, $el } = setup({ name: '' }); - - expect($scope.person.errorCount()).to.be(1); - expect($scope.person.softErrorCount()).to.be(0); - $el.find(testSubjSelector('submit')).click(); - expect($scope.person.errorCount()).to.be(1); - expect($scope.person.softErrorCount()).to.be(1); - }); - - it('prevents submit when there are errors', function() { - const onSubmit = sinon.stub(); - const { $scope, $el } = setup({ name: '', onSubmit }); - - expect($scope.person.errorCount()).to.be(1); - sinon.assert.notCalled(onSubmit); - $el.find(testSubjSelector('submit')).click(); - expect($scope.person.errorCount()).to.be(1); - sinon.assert.notCalled(onSubmit); - - $scope.$apply(() => { - $scope.name = 'foo'; - }); - - expect($scope.person.errorCount()).to.be(0); - sinon.assert.notCalled(onSubmit); - $el.find(testSubjSelector('submit')).click(); - expect($scope.person.errorCount()).to.be(0); - sinon.assert.calledOnce(onSubmit); - }); - - it('new fields are no longer soft after blur', function() { - const { $scope, $el } = setup({ name: '' }); - expect($scope.person.softErrorCount()).to.be(0); - $el.find(testSubjSelector('name')).blur(); - expect($scope.person.softErrorCount()).to.be(1); - }); - - it('counts errors/softErrors in sub forms', function() { - const { $scope, $el } = setup(); - - expect($scope.person.errorCount()).to.be(0); - - $scope.$apply(() => { - $scope.tasks = [ - { - name: 'foo', - description: '', - }, - { - name: 'foo', - description: '', - }, - ]; - }); - - expect($scope.person.errorCount()).to.be(2); - expect($scope.person.softErrorCount()).to.be(0); - - $el - .find(testSubjSelector('taskDesc')) - .first() - .blur(); - - expect($scope.person.errorCount()).to.be(2); - expect($scope.person.softErrorCount()).to.be(1); - }); - - it('only counts down', function() { - const { $scope, $el } = setup({ - tasks: [ - { - name: 'foo', - description: '', - }, - { - name: 'bar', - description: '', - }, - { - name: 'baz', - description: '', - }, - ], - }); - - // top level form sees 3 errors - expect($scope.person.errorCount()).to.be(3); - expect($scope.person.softErrorCount()).to.be(0); - - $el - .find('ng-form') - .toArray() - .forEach((el, i) => { - const $task = $(el); - const $taskScope = $task.scope(); - const form = $task.controller('form'); - - // sub forms only see one error - expect(form.errorCount()).to.be(1); - expect(form.softErrorCount()).to.be(0); - - // blurs only count locally - $task.find(testSubjSelector('taskDesc')).blur(); - expect(form.softErrorCount()).to.be(1); - - // but parent form see them - expect($scope.person.softErrorCount()).to.be(1); - - $taskScope.$apply(() => { - $taskScope.task.description = 'valid'; - }); - - expect(form.errorCount()).to.be(0); - expect(form.softErrorCount()).to.be(0); - expect($scope.person.errorCount()).to.be(2 - i); - expect($scope.person.softErrorCount()).to.be(0); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js deleted file mode 100644 index 90971140482f7..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_form_controller.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function decorateFormController($delegate, $injector) { - const [directive] = $delegate; - const FormController = directive.controller; - - class KbnFormController extends FormController { - // prevent inheriting FormController's static $inject property - // which is angular's cache of the DI arguments for a function - static $inject = ['$scope', '$element']; - - constructor($scope, $element, ...superArgs) { - super(...superArgs); - - const onSubmit = event => { - this._markInvalidTouched(event); - }; - - $element.on('submit', onSubmit); - $scope.$on('$destroy', () => { - $element.off('submit', onSubmit); - }); - } - - errorCount() { - return this._getInvalidModels().length; - } - - // same as error count, but filters out untouched and pristine models - softErrorCount() { - return this._getInvalidModels().filter(model => model.$touched || model.$dirty).length; - } - - $setTouched() { - this._getInvalidModels().forEach(model => model.$setTouched()); - } - - _markInvalidTouched(event) { - if (this.errorCount()) { - event.preventDefault(); - event.stopImmediatePropagation(); - this.$setTouched(); - } - } - - _getInvalidModels() { - return this.$$controls.reduce((acc, control) => { - // recurse into sub-form - if (typeof control._getInvalidModels === 'function') { - return [...acc, ...control._getInvalidModels()]; - } - - if (control.$invalid) { - return [...acc, control]; - } - - return acc; - }, []); - } - } - - // replace controller with our wrapper - directive.controller = [ - ...$injector.annotate(KbnFormController), - ...$injector.annotate(FormController), - (...args) => new KbnFormController(...args), - ]; - - return $delegate; -} diff --git a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js b/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js deleted file mode 100644 index bb4d026aa1810..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/fancy_forms/kbn_model_controller.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function decorateModelController($delegate, $injector) { - const [directive] = $delegate; - const ModelController = directive.controller; - - class KbnModelController extends ModelController { - // prevent inheriting ModelController's static $inject property - // which is angular's cache of the DI arguments for a function - static $inject = ['$scope', '$element']; - - constructor($scope, $element, ...superArgs) { - super(...superArgs); - - const onInvalid = () => { - this.$setTouched(); - }; - - // the browser emits an "invalid" event when browser supplied - // validation fails, which implies that the user has indirectly - // interacted with the control and it should be treated as "touched" - $element.on('invalid', onInvalid); - $scope.$on('$destroy', () => { - $element.off('invalid', onInvalid); - }); - } - } - - // replace controller with our wrapper - directive.controller = [ - ...$injector.annotate(KbnModelController), - ...$injector.annotate(ModelController), - (...args) => new KbnModelController(...args), - ]; - - return $delegate; -} diff --git a/src/legacy/ui/public/vis/editors/default/index.ts b/src/legacy/ui/public/vis/editors/default/index.ts index 7079ba23afb5c..fada4e5d2266f 100644 --- a/src/legacy/ui/public/vis/editors/default/index.ts +++ b/src/legacy/ui/public/vis/editors/default/index.ts @@ -18,7 +18,7 @@ */ export { AggParamEditorProps } from './components/agg_param_props'; -export { DefaultEditorAggParams, SubAggParamsProp } from './components/agg_params'; +export { DefaultEditorAggParams } from './components/agg_params'; export { ComboBoxGroupedOptions } from './utils'; export * from './vis_options_props'; export * from './utils'; diff --git a/src/legacy/ui/public/vis/editors/default/schemas.js b/src/legacy/ui/public/vis/editors/default/schemas.ts similarity index 67% rename from src/legacy/ui/public/vis/editors/default/schemas.js rename to src/legacy/ui/public/vis/editors/default/schemas.ts index 69449dc8504a8..3cacd1cfbe68f 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.js +++ b/src/legacy/ui/public/vis/editors/default/schemas.ts @@ -18,13 +18,49 @@ */ import _ from 'lodash'; + +import { Optional } from '@kbn/utility-types'; + +import { AggParam } from '../../../agg_types'; import { IndexedArray } from '../../../indexed_array'; import { RowsOrColumnsControl } from './controls/rows_or_columns'; import { RadiusRatioOptionControl } from './controls/radius_ratio_option'; import { AggGroupNames } from './agg_groups'; +import { AggControlProps } from './controls/agg_control_props'; + +export interface ISchemas { + [AggGroupNames.Buckets]: Schema[]; + [AggGroupNames.Metrics]: Schema[]; +} + +export interface Schema { + aggFilter: string | string[]; + editor: boolean | string; + group: AggGroupNames; + max: number; + min: number; + name: string; + params: AggParam[]; + title: string; + defaults: unknown; + hideCustomLabel?: boolean; + mustBeFirst?: boolean; + aggSettings?: any; + editorComponent?: React.ComponentType; +} class Schemas { - constructor(schemas) { + // @ts-ignore + all: IndexedArray; + + constructor( + schemas: Array< + Optional< + Schema, + 'min' | 'max' | 'group' | 'title' | 'aggFilter' | 'editor' | 'params' | 'defaults' + > + > + ) { _(schemas || []) .map(schema => { if (!schema.name) throw new Error('all schema must have a unique name'); @@ -35,7 +71,7 @@ class Schemas { name: 'row', default: true, }, - ]; + ] as AggParam[]; schema.editorComponent = RowsOrColumnsControl; } else if (schema.name === 'radius') { schema.editorComponent = RadiusRatioOptionControl; @@ -51,21 +87,23 @@ class Schemas { params: [], }); - return schema; + return schema as Schema; }) - .tap(schemas => { + .tap((fullSchemas: Schema[]) => { this.all = new IndexedArray({ index: ['name'], group: ['group'], immutable: true, - initialSet: schemas, + initialSet: fullSchemas, }); }) .groupBy('group') .forOwn((group, groupName) => { + // @ts-ignore this[groupName] = new IndexedArray({ index: ['name'], immutable: true, + // @ts-ignore initialSet: group, }); }) diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.html b/src/legacy/ui/public/vis/editors/default/sidebar.html deleted file mode 100644 index b0a03e461fc1c..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/sidebar.html +++ /dev/null @@ -1,191 +0,0 @@ -
-
- -

- {{ vis.indexPattern.title }} -

- - - -
- - -
- - -
- -
- -
- -
-
diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.js b/src/legacy/ui/public/vis/editors/default/sidebar.js deleted file mode 100644 index 195ae68c0e959..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/sidebar.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import './agg_group'; -import './vis_options'; -import 'ui/directives/css_truncate'; -import { uiModules } from '../../../modules'; -import sidebarTemplate from './sidebar.html'; -import { move } from '../../../utils/collection'; -import { AggGroupNames } from './agg_groups'; -import { getEnabledMetricAggsCount } from './components/agg_group_helper'; - -uiModules.get('app/visualize').directive('visEditorSidebar', function() { - return { - restrict: 'E', - template: sidebarTemplate, - scope: true, - require: '?^ngModel', - controllerAs: 'sidebar', - controller: function($scope) { - $scope.$watch('vis.type', visType => { - if (visType) { - this.showData = visType.schemas.buckets || visType.schemas.metrics; - if (_.has(visType, 'editorConfig.optionTabs')) { - const activeTabs = visType.editorConfig.optionTabs.filter(tab => { - return _.get(tab, 'active', false); - }); - if (activeTabs.length > 0) { - this.section = activeTabs[0].name; - } - } - this.section = - this.section || - (this.showData ? 'data' : _.get(visType, 'editorConfig.optionTabs[0].name')); - } - }); - - $scope.onAggTypeChange = (agg, value) => { - if (agg.type !== value) { - agg.type = value; - } - }; - - $scope.onAggParamsChange = (params, paramName, value) => { - if (params[paramName] !== value) { - params[paramName] = value; - } - }; - - $scope.addSchema = function(schema) { - const aggConfig = $scope.state.aggs.createAggConfig({ schema }); - aggConfig.brandNew = true; - }; - - $scope.removeAgg = function(agg) { - const aggs = $scope.state.aggs.aggs; - const index = aggs.indexOf(agg); - - if (index === -1) { - return; - } - - aggs.splice(index, 1); - - if (agg.schema.group === AggGroupNames.Metrics) { - const metrics = $scope.state.aggs.bySchemaGroup(AggGroupNames.Metrics); - - if (getEnabledMetricAggsCount(metrics) === 0) { - metrics.find(aggregation => aggregation.schema.name === 'metric').enabled = true; - } - } - }; - - $scope.onToggleEnableAgg = (agg, isEnable) => { - agg.enabled = isEnable; - }; - - $scope.reorderAggs = group => { - //the aggs have been reordered in [group] and we need - //to apply that ordering to [vis.aggs] - const indexOffset = $scope.state.aggs.aggs.indexOf(group[0]); - _.forEach(group, (agg, index) => { - move($scope.state.aggs.aggs, agg, indexOffset + index); - }); - }; - }, - }; -}); diff --git a/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js b/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js deleted file mode 100644 index 3cbc94a029326..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/vis_editor_resizer.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import $ from 'jquery'; -import { uiModules } from '../../../modules'; -import { keyCodes } from '@elastic/eui'; - -uiModules.get('kibana').directive('visEditorResizer', function() { - return { - restrict: 'E', - link: function($scope, $el) { - const $left = $el.parent(); - - $el.on('mousedown', function(event) { - $el.addClass('active'); - const startWidth = $left.width(); - const startX = event.pageX; - - function onMove(event) { - const newWidth = startWidth + event.pageX - startX; - $left.width(newWidth); - } - - $(document.body) - .on('mousemove', onMove) - .one('mouseup', () => { - $el.removeClass('active'); - $(document.body).off('mousemove', onMove); - $scope.$broadcast('render'); - }); - }); - - $el.on('keydown', event => { - const { keyCode } = event; - - if (keyCode === keyCodes.LEFT || keyCode === keyCodes.RIGHT) { - event.preventDefault(); - const startWidth = $left.width(); - const newWidth = startWidth + (keyCode === keyCodes.LEFT ? -15 : 15); - $left.width(newWidth); - $scope.$broadcast('render'); - } - }); - }, - }; -}); diff --git a/src/legacy/ui/public/vis/editors/default/vis_options.js b/src/legacy/ui/public/vis/editors/default/vis_options.js deleted file mode 100644 index 9c9b0353cee27..0000000000000 --- a/src/legacy/ui/public/vis/editors/default/vis_options.js +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from '../../../modules'; -import { VisOptionsReactWrapper } from './vis_options_react_wrapper'; -import { safeMakeLabel } from './controls/agg_utils'; - -/** - * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. - * This lets you specify a full-screen UI for editing a vis type, instead of using the regular - * sidebar. - */ - -uiModules - .get('app/visualize') - .directive('visOptionsReactWrapper', reactDirective => - reactDirective(wrapInI18nContext(VisOptionsReactWrapper), [ - ['component', { wrapApply: false }], - ['aggs', { watchDepth: 'collection' }], - ['stateParams', { watchDepth: 'collection' }], - ['vis', { watchDepth: 'collection' }], - ['uiState', { watchDepth: 'collection' }], - ['setValue', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - ['setVisType', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - 'hasHistogramAgg', - 'currentTab', - 'aggsLabels', - ]) - ) - .directive('visEditorVisOptions', function($compile) { - return { - restrict: 'E', - require: '?^ngModel', - scope: { - vis: '=', - visData: '=', - uiState: '=', - editor: '=', - visualizeEditor: '=', - editorState: '=', - onAggParamsChange: '=', - hasHistogramAgg: '=', - currentTab: '=', - }, - link: function($scope, $el, attrs, ngModelCtrl) { - $scope.setValue = (paramName, value) => - $scope.onAggParamsChange($scope.editorState.params, paramName, value); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`visOptions`, isValid); - }; - - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - - $scope.setVisType = type => { - $scope.vis.type.type = type; - }; - - // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it - $scope.aggsLabels = ''; - - $scope.$watch( - () => { - return $scope.editorState.aggs.aggs - .map(agg => { - return safeMakeLabel(agg); - }) - .join(); - }, - value => { - $scope.aggsLabels = value; - } - ); - - const comp = - typeof $scope.editor === 'string' - ? $scope.editor - : ` - `; - const $editor = $compile(comp)($scope); - $el.append($editor); - }, - }; - }); diff --git a/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx b/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx index 3f9fa9f5f352f..5b4badc103645 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx +++ b/src/legacy/ui/public/vis/editors/default/vis_options_props.tsx @@ -23,13 +23,12 @@ import { Vis } from './../..'; export interface VisOptionsProps { aggs: AggConfigs; - aggsLabels: string; hasHistogramAgg: boolean; + isTabSelected: boolean; stateParams: VisParamType; vis: Vis; uiState: PersistedState; setValue(paramName: T, value: VisParamType[T]): void; setValidity(isValid: boolean): void; - setVisType(type: string): void; setTouched(isTouched: boolean): void; } diff --git a/src/legacy/ui/public/vis/map/service_settings.js b/src/legacy/ui/public/vis/map/service_settings.js index 1096aa8eb4503..233ee526c439b 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/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss index 4d7c0e2bdcadb..62050ce4e99fd 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss @@ -1,4 +1,4 @@ -@import '../../vislib/variables'; +@import '../../../../core_plugins/vis_type_vislib/public/vislib/variables'; // NOTE: Some of the styles attempt to align with the TSVB legend diff --git a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js index 88412c76d0d36..c34294d45548c 100644 --- a/src/legacy/ui/public/vis/vis_types/angular_vis_type.js +++ b/src/legacy/ui/public/vis/vis_types/angular_vis_type.js @@ -18,6 +18,7 @@ */ import $ from 'jquery'; +import { isEqual } from 'lodash'; import chrome from 'ui/chrome'; export class AngularVisController { @@ -37,6 +38,11 @@ export class AngularVisController { this.$scope.vis = this.vis; this.$scope.visState = this.vis.getState(); this.$scope.esResponse = esResponse; + + if (!isEqual(this.$scope.visParams, visParams)) { + this.vis.emit('updateEditorStateParams', visParams); + } + this.$scope.visParams = visParams; this.$scope.renderComplete = resolve; this.$scope.renderFailed = reject; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts new file mode 100644 index 0000000000000..d9eea83d40b48 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/pie_utils.ts @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +/** + * Returns an array of names ordered by appearance in the nested array + * of objects + * + * > Duplicated utilty method from vislib Data class to decouple `vislib_vis_legend` from `vislib` + * + * @see src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/data.js + * + * @returns {Array} Array of unique names (strings) + */ +export function getPieNames(data: any[]): string[] { + const names: string[] = []; + + _.forEach(data, function(obj) { + const columns = obj.raw ? obj.raw.columns : undefined; + _.forEach(getNames(obj, columns), function(name) { + names.push(name); + }); + }); + + return _.uniq(names, 'label'); +} + +/** + * Flattens hierarchical data into an array of objects with a name and index value. + * The indexed value determines the order of nesting in the data. + * Returns an array with names sorted by the index value. + * + * @param data {Object} Chart data object + * @param columns {Object} Contains formatter information + * @returns {Array} Array of names (strings) + */ +function getNames(data: any, columns: any): string[] { + const slices = data.slices; + + if (slices.children) { + const namedObj = returnNames(slices.children, 0, columns); + + return _(namedObj) + .sortBy(function(obj) { + return obj.index; + }) + .unique(function(d) { + return d.label; + }) + .value(); + } + + return []; +} + +/** + * Helper function for getNames + * Returns an array of objects with a name (key) value and an index value. + * The index value allows us to sort the names in the correct nested order. + * + * @param array {Array} Array of data objects + * @param index {Number} Number of times the object is nested + * @param columns {Object} Contains name formatter information + * @returns {Array} Array of labels (strings) + */ +function returnNames(array: any[], index: number, columns: any): any[] { + const names: any[] = []; + + _.forEach(array, function(obj) { + names.push({ + label: obj.name, + values: [obj.rawData], + index, + }); + + if (obj.children) { + const plusIndex = index + 1; + + _.forEach(returnNames(obj.children, plusIndex, columns), function(namedObj) { + names.push(namedObj); + }); + } + }); + + return names; +} diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx index f0100e369f050..d98590f9885b9 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx @@ -23,12 +23,11 @@ import { compact, uniq, map } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; -// @ts-ignore -import { Data } from '../../../vislib/lib/data'; // @ts-ignore import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; import { VisLegendItem } from './vislib_vis_legend_item'; +import { getPieNames } from './pie_utils'; import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; export interface VisLegendProps { @@ -128,7 +127,7 @@ export class VisLegend extends PureComponent { if (!data) return []; data = data.columns || data.rows || [data]; - if (type === 'pie') return Data.prototype.pieNames(data); + if (type === 'pie') return getPieNames(data); return this.getSeriesLabels(data); }; diff --git a/src/legacy/ui/public/vislib/vis.js b/src/legacy/ui/public/vislib/vis.js deleted file mode 100644 index 6ce58c1f5b23b..0000000000000 --- a/src/legacy/ui/public/vislib/vis.js +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import d3 from 'd3'; -import { EventEmitter } from 'events'; -import chrome from '../chrome'; -import { VislibError } from './errors'; -import { VisConfig } from './lib/vis_config'; -import { Handler } from './lib/handler'; -import { setHierarchicalTooltipFormatter } from '../vis/components/tooltip/_hierarchical_tooltip_formatter'; -import { setPointSeriesTooltipFormatter } from '../vis/components/tooltip/_pointseries_tooltip_formatter'; - -const config = chrome.getUiSettingsClient(); - -export function VislibVisProvider(Private) { - setHierarchicalTooltipFormatter(Private); - setPointSeriesTooltipFormatter(Private); - - /** - * Creates the visualizations. - * - * @class Vis - * @constructor - * @param $el {HTMLElement} jQuery selected HTML element - * @param config {Object} Parameters that define the chart type and chart options - */ - class Vis extends EventEmitter { - constructor($el, visConfigArgs) { - super(); - this.el = $el.get ? $el.get(0) : $el; - this.visConfigArgs = _.cloneDeep(visConfigArgs); - this.visConfigArgs.dimmingOpacity = config.get('visualization:dimmingOpacity'); - this.visConfigArgs.heatmapMaxBuckets = config.get('visualization:heatmap:maxBuckets'); - } - - hasLegend() { - return this.visConfigArgs.addLegend; - } - - initVisConfig(data, uiState) { - this.data = data; - - this.uiState = uiState; - - this.visConfig = new VisConfig(this.visConfigArgs, this.data, this.uiState, this.el); - } - - /** - * Renders the visualization - * - * @method render - * @param data {Object} Elasticsearch query results - */ - render(data, uiState) { - if (!data) { - throw new Error('No valid data!'); - } - - if (this.handler) { - this.data = null; - this._runOnHandler('destroy'); - } - - this.initVisConfig(data, uiState); - - this.handler = new Handler(this, this.visConfig); - this._runOnHandler('render'); - } - - getLegendLabels() { - return this.visConfig ? this.visConfig.get('legend.labels', null) : null; - } - - getLegendColors() { - return this.visConfig ? this.visConfig.get('legend.colors', null) : null; - } - - _runOnHandler(method) { - try { - this.handler[method](); - } catch (error) { - if (error instanceof VislibError) { - error.displayToScreen(this.handler); - } else { - throw error; - } - } - } - - /** - * Destroys the visualization - * Removes chart and all elements associated with it. - * Removes chart and all elements associated with it. - * Remove event listeners and pass destroy call down to owned objects. - * - * @method destroy - */ - destroy() { - const selection = d3.select(this.el).select('.visWrapper'); - - if (this.handler) this._runOnHandler('destroy'); - - selection.remove(); - } - - /** - * Sets attributes on the visualization - * - * @method set - * @param name {String} An attribute name - * @param val {*} Value to which the attribute name is set - */ - set(name, val) { - this.visConfigArgs[name] = val; - this.render(this.data, this.uiState); - } - - /** - * Gets attributes from the visualization - * - * @method get - * @param name {String} An attribute name - * @returns {*} The value of the attribute name - */ - get(name) { - return this.visConfig.get(name); - } - - /** - * Turns on event listeners. - * - * @param event {String} - * @param listener{Function} - * @returns {*} - */ - on(event, listener) { - const first = this.listenerCount(event) === 0; - const ret = EventEmitter.prototype.on.call(this, event, listener); - const added = this.listenerCount(event) > 0; - - // if this is the first listener added for the event - // enable the event in the handler - if (first && added && this.handler) this.handler.enable(event); - - return ret; - } - - /** - * Turns off event listeners. - * - * @param event {String} - * @param listener{Function} - * @returns {*} - */ - off(event, listener) { - const last = this.listenerCount(event) === 1; - const ret = EventEmitter.prototype.off.call(this, event, listener); - const removed = this.listenerCount(event) === 0; - - // Once all listeners are removed, disable the events in the handler - if (last && removed && this.handler) this.handler.disable(event); - return ret; - } - - removeAllListeners(event) { - const ret = EventEmitter.prototype.removeAllListeners.call(this, event); - this.handler.disable(event); - return ret; - } - } - - return Vis; -} diff --git a/src/plugins/bfetch/README.md b/src/plugins/bfetch/README.md index 9c18720e30d96..9ed90a4de306e 100644 --- a/src/plugins/bfetch/README.md +++ b/src/plugins/bfetch/README.md @@ -3,7 +3,54 @@ `bfetch` allows to batch HTTP requests and streams responses back. +# Example + +We will create a batch processing endpoint that receives a number then doubles it +and streams it back. We will also consider the number to be time in milliseconds +and before streaming the number back the server will wait for the specified number of +milliseconds. + +To do that, first create server-side batch processing route using [`addBatchProcessingRoute`](./docs/server/reference.md#addBatchProcessingRoute). + +```ts +plugins.bfetch.addBatchProcessingRoute<{ num: number }, { num: number }>( + '/my-plugin/double', + () => ({ + onBatchItem: async ({ num }) => { + // Validate inputs. + if (num < 0) throw new Error('Invalid number'); + // Wait number of specified milliseconds. + await new Promise(r => setTimeout(r, num)); + // Double the number and send it back. + return { num: 2 * num }; + }, + }) +); +``` + +Now on client-side create `double` function using [`batchedFunction`](./docs/browser/reference.md#batchedFunction). +The newly created `double` function can be called many times and it +will package individual calls into batches and send them to the server. + +```ts +const double = plugins.bfetch.batchedFunction<{ num: number }, { num: number }>({ + url: '/my-plugin/double', +}); +``` + +Note: the created `double` must accept a single object argument (`{ num: number }` in this case) +and it will return a promise that resolves into an object, too (also `{ num: number }` in this case). + +Use the `double` function. + +```ts +double({ num: 1 }).then(console.log, console.error); // { num: 2 } +double({ num: 2 }).then(console.log, console.error); // { num: 4 } +double({ num: 3 }).then(console.log, console.error); // { num: 6 } +``` + + ## Reference - [Browser](./docs/browser/reference.md) -- Server +- [Server](./docs/server/reference.md) diff --git a/src/plugins/bfetch/common/batch.ts b/src/plugins/bfetch/common/batch.ts new file mode 100644 index 0000000000000..6fd2c7e35ed91 --- /dev/null +++ b/src/plugins/bfetch/common/batch.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface ErrorLike { + message: string; +} + +export interface BatchRequestData { + batch: Item[]; +} + +export interface BatchResponseItem { + id: number; + result?: Result; + error?: Error; +} diff --git a/src/plugins/bfetch/common/buffer/create_batched_function.ts b/src/plugins/bfetch/common/buffer/create_batched_function.ts new file mode 100644 index 0000000000000..24f28659863a7 --- /dev/null +++ b/src/plugins/bfetch/common/buffer/create_batched_function.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 { ItemBufferParams } from './item_buffer'; +import { TimedItemBufferParams, TimedItemBuffer } from './timed_item_buffer'; + +type Fn = (...args: any) => any; + +export interface BatchedFunctionParams { + onCall: (...args: Parameters) => [ReturnType, BatchEntry]; + onBatch: (items: BatchEntry[]) => void; + flushOnMaxItems?: ItemBufferParams['flushOnMaxItems']; + maxItemAge?: TimedItemBufferParams['maxItemAge']; +} + +export const createBatchedFunction = ( + params: BatchedFunctionParams +): [Func, TimedItemBuffer] => { + const { onCall, onBatch, maxItemAge = 10, flushOnMaxItems = 25 } = params; + const buffer = new TimedItemBuffer({ + onFlush: onBatch, + maxItemAge, + flushOnMaxItems, + }); + + const fn: Func = ((...args) => { + const [result, batchEntry] = onCall(...args); + buffer.write(batchEntry); + return result; + }) as Func; + + return [fn, buffer]; +}; diff --git a/src/plugins/bfetch/common/buffer/index.ts b/src/plugins/bfetch/common/buffer/index.ts new file mode 100644 index 0000000000000..33bc52733289b --- /dev/null +++ b/src/plugins/bfetch/common/buffer/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 './item_buffer'; +export * from './timed_item_buffer'; +export * from './create_batched_function'; diff --git a/src/plugins/bfetch/common/buffer/item_buffer.ts b/src/plugins/bfetch/common/buffer/item_buffer.ts new file mode 100644 index 0000000000000..663aa5d7b0b7f --- /dev/null +++ b/src/plugins/bfetch/common/buffer/item_buffer.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export interface ItemBufferParams { + /** + * Flushes buffer automatically if number of items in the buffer reaches + * this number. Omit it or set to `Infinity` to never flush on max buffer + * size automatically. + */ + flushOnMaxItems?: number; + + /** + * Callback that is called every time buffer is flushed. It receives a single + * argument which is a list of all buffered items. If `.flush()` is called + * when buffer is empty, `.onflush` is called with empty array. + */ + onFlush: (items: Item[]) => void; +} + +/** + * A simple buffer that collects items. Can be cleared or flushed; and can + * automatically flush when specified number of items is reached. + */ +export class ItemBuffer { + private list: Item[] = []; + + constructor(public readonly params: ItemBufferParams) {} + + /** + * Get current buffer size. + */ + public get length(): number { + return this.list.length; + } + + /** + * Add item to the buffer. + */ + public write(item: Item) { + this.list.push(item); + + const { flushOnMaxItems } = this.params; + if (flushOnMaxItems) { + if (this.list.length >= flushOnMaxItems) { + this.flush(); + } + } + } + + /** + * Remove all items from the buffer. + */ + public clear() { + this.list = []; + } + + /** + * Call `.onflush` method and clear buffer. + */ + public flush() { + let list; + [list, this.list] = [this.list, []]; + this.params.onFlush(list); + } +} diff --git a/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts b/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts new file mode 100644 index 0000000000000..5b145a2523070 --- /dev/null +++ b/src/plugins/bfetch/common/buffer/tests/create_batched_function.test.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createBatchedFunction } from '../create_batched_function'; + +describe('createBatchedFunction', () => { + test('calls onCall every time fn is called, calls onBatch once flushOnMaxItems reached', async () => { + const onBatch = jest.fn(); + const onCall = jest.fn(() => [1, 2] as any); + const [fn] = createBatchedFunction({ + onBatch, + onCall, + flushOnMaxItems: 2, + maxItemAge: 10, + }); + + expect(onCall).toHaveBeenCalledTimes(0); + expect(onBatch).toHaveBeenCalledTimes(0); + + fn(123); + + expect(onCall).toHaveBeenCalledTimes(1); + expect(onCall).toHaveBeenCalledWith(123); + expect(onBatch).toHaveBeenCalledTimes(0); + + fn(456); + + expect(onCall).toHaveBeenCalledTimes(2); + expect(onCall).toHaveBeenCalledWith(456); + expect(onBatch).toHaveBeenCalledTimes(1); + expect(onBatch).toHaveBeenCalledWith([2, 2]); + }); + + test('calls onBatch once timeout is reached', async () => { + const onBatch = jest.fn(); + const onCall = jest.fn(() => [4, 3] as any); + const [fn] = createBatchedFunction({ + onBatch, + onCall, + flushOnMaxItems: 2, + maxItemAge: 10, + }); + + expect(onCall).toHaveBeenCalledTimes(0); + expect(onBatch).toHaveBeenCalledTimes(0); + + fn(123); + + expect(onCall).toHaveBeenCalledTimes(1); + expect(onCall).toHaveBeenCalledWith(123); + expect(onBatch).toHaveBeenCalledTimes(0); + + await new Promise(r => setTimeout(r, 15)); + + expect(onCall).toHaveBeenCalledTimes(1); + expect(onBatch).toHaveBeenCalledTimes(1); + expect(onBatch).toHaveBeenCalledWith([3]); + }); +}); diff --git a/src/legacy/core_plugins/interpreter/server/routes/index.ts b/src/plugins/bfetch/common/buffer/tests/item_buffer.test.ts similarity index 85% rename from src/legacy/core_plugins/interpreter/server/routes/index.ts rename to src/plugins/bfetch/common/buffer/tests/item_buffer.test.ts index 50385147dd38e..a921fa8e589a3 100644 --- a/src/legacy/core_plugins/interpreter/server/routes/index.ts +++ b/src/plugins/bfetch/common/buffer/tests/item_buffer.test.ts @@ -17,8 +17,7 @@ * under the License. */ -import { registerServerFunctions } from './server_functions'; +import { ItemBuffer } from '../item_buffer'; +import { runItemBufferTests } from './run_item_buffer_tests'; -export function routes(server: any) { - registerServerFunctions(server); -} +runItemBufferTests(ItemBuffer); diff --git a/src/plugins/bfetch/common/buffer/tests/run_item_buffer_tests.ts b/src/plugins/bfetch/common/buffer/tests/run_item_buffer_tests.ts new file mode 100644 index 0000000000000..b3ba9375448dc --- /dev/null +++ b/src/plugins/bfetch/common/buffer/tests/run_item_buffer_tests.ts @@ -0,0 +1,239 @@ +/* + * 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 { ItemBuffer, ItemBufferParams } from '../item_buffer'; + +export const runItemBufferTests = ( + Buffer: new >(params: Params) => ItemBuffer +) => { + describe('ItemBuffer', () => { + test('can create with or without optional "flushOnMaxItems" param', () => { + new Buffer({ + onFlush: () => {}, + }); + + new Buffer({ + onFlush: () => {}, + flushOnMaxItems: 123, + }); + }); + + test('can add items to the buffer', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write('a'); + buf.write('b'); + buf.write('c'); + }); + + test('returns number of items in the buffer', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + expect(buf.length).toBe(0); + buf.write('a'); + expect(buf.length).toBe(1); + buf.write('b'); + expect(buf.length).toBe(2); + buf.write('c'); + expect(buf.length).toBe(3); + }); + + test('returns correct number of items after .clear() was called', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + expect(buf.length).toBe(0); + buf.write('a'); + expect(buf.length).toBe(1); + buf.clear(); + buf.write('b'); + expect(buf.length).toBe(1); + buf.write('c'); + expect(buf.length).toBe(2); + }); + + test('returns correct number of items after .flush() was called', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + expect(buf.length).toBe(0); + buf.write('a'); + expect(buf.length).toBe(1); + buf.flush(); + buf.write('b'); + expect(buf.length).toBe(1); + buf.write('c'); + expect(buf.length).toBe(2); + }); + + test('can flush buffer and receive items in chronological order', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write('a'); + buf.write('b'); + buf.write('c'); + + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual(['a', 'b', 'c']); + }); + + test('clears buffer after flush', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write('a'); + buf.write('b'); + buf.write('c'); + + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual(['a', 'b', 'c']); + + buf.write('d'); + + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(2); + expect(onFlush.mock.calls[1][0]).toEqual(['d']); + }); + + test('can call .flush() any time as many times as needed', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.flush(); + buf.write(123); + buf.flush(); + buf.flush(); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(4); + expect(onFlush.mock.calls[0][0]).toEqual([]); + expect(onFlush.mock.calls[1][0]).toEqual([123]); + expect(onFlush.mock.calls[2][0]).toEqual([]); + expect(onFlush.mock.calls[3][0]).toEqual([]); + }); + + test('calling .clear() before .flush() cases to return empty list', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.write(1); + buf.write(2); + buf.clear(); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual([]); + }); + + test('can call .clear() any time as many times as needed', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + }); + + buf.clear(); + buf.flush(); + buf.write(123); + buf.clear(); + buf.flush(); + buf.clear(); + buf.clear(); + buf.flush(); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(4); + expect(onFlush.mock.calls[0][0]).toEqual([]); + expect(onFlush.mock.calls[1][0]).toEqual([]); + expect(onFlush.mock.calls[2][0]).toEqual([]); + expect(onFlush.mock.calls[3][0]).toEqual([]); + }); + + describe('when `flushOnMaxItems` is set', () => { + test('does not flush automatically before `flushOnMaxItems` is reached', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + flushOnMaxItems: 2, + }); + + buf.write(1); + + expect(onFlush).toHaveBeenCalledTimes(0); + }); + + test('automatically flushes buffer when `flushOnMaxItems` is reached', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + flushOnMaxItems: 2, + }); + + buf.write(1); + buf.write(2); + + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush.mock.calls[0][0]).toEqual([1, 2]); + }); + + test('flushes again when `flushOnMaxItems` limit is reached the second time', () => { + const onFlush = jest.fn(); + const buf = new Buffer({ + onFlush, + flushOnMaxItems: 2, + }); + + buf.write(1); + buf.write(2); + buf.write(3); + buf.write(4); + buf.write(5); + buf.flush(); + + expect(onFlush).toHaveBeenCalledTimes(3); + expect(onFlush.mock.calls[0][0]).toEqual([1, 2]); + expect(onFlush.mock.calls[1][0]).toEqual([3, 4]); + expect(onFlush.mock.calls[2][0]).toEqual([5]); + }); + }); + }); +}; diff --git a/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts new file mode 100644 index 0000000000000..c1c6a8f187a44 --- /dev/null +++ b/src/plugins/bfetch/common/buffer/tests/timed_item_buffer.test.ts @@ -0,0 +1,104 @@ +/* + * 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 { TimedItemBuffer } from '../timed_item_buffer'; +import { runItemBufferTests } from './run_item_buffer_tests'; + +describe('TimedItemBuffer', () => { + runItemBufferTests(TimedItemBuffer); + + test('does not do unnecessary flushes', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + maxItemAge: 3, + }); + + expect(onFlush).toHaveBeenCalledTimes(0); + buf.write(0); + expect(onFlush).toHaveBeenCalledTimes(0); + buf.flush(); + expect(onFlush).toHaveBeenCalledTimes(1); + }); + + test('does not do extra flush after timeout if buffer was flushed during timeout wait', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + maxItemAge: 10, + }); + + buf.write(0); + await new Promise(r => setTimeout(r, 3)); + buf.flush(); + await new Promise(r => setTimeout(r, 11)); + + expect(onFlush).toHaveBeenCalledTimes(1); + }); + + test('flushes buffer automatically after timeout reached', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + maxItemAge: 2, + }); + + buf.write(1); + buf.write(2); + expect(onFlush).toHaveBeenCalledTimes(0); + + await new Promise(r => setTimeout(r, 3)); + expect(onFlush).toHaveBeenCalledTimes(1); + expect(onFlush).toHaveBeenCalledWith([1, 2]); + }); + + test('does not call flush after timeout if flush was triggered because buffer size reached', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + flushOnMaxItems: 2, + maxItemAge: 2, + }); + + buf.write(1); + buf.write(2); + + expect(onFlush).toHaveBeenCalledTimes(1); + await new Promise(r => setTimeout(r, 3)); + expect(onFlush).toHaveBeenCalledTimes(1); + }); + + test('does not automatically flush if `.clear()` was called', async () => { + const onFlush = jest.fn(); + const buf = new TimedItemBuffer({ + onFlush, + flushOnMaxItems: 25, + maxItemAge: 5, + }); + + buf.write(1); + buf.write(2); + await new Promise(r => setImmediate(r)); + buf.clear(); + + expect(onFlush).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 6)); + expect(onFlush).toHaveBeenCalledTimes(0); + }); +}); diff --git a/src/plugins/bfetch/common/buffer/timed_item_buffer.ts b/src/plugins/bfetch/common/buffer/timed_item_buffer.ts new file mode 100644 index 0000000000000..8d0f9e4856f8c --- /dev/null +++ b/src/plugins/bfetch/common/buffer/timed_item_buffer.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ItemBuffer, ItemBufferParams } from './item_buffer'; + +export interface TimedItemBufferParams extends ItemBufferParams { + /** + * Flushes buffer when oldest item reaches age specified by this parameter, + * in milliseconds. + */ + maxItemAge?: number; +} + +export class TimedItemBuffer extends ItemBuffer { + private timer: any; + + constructor(public readonly params: TimedItemBufferParams) { + super(params); + } + + public write(item: Item) { + super.write(item); + + if (this.params.maxItemAge && this.length === 1) { + this.timer = setTimeout(this.onTimeout, this.params.maxItemAge); + } + } + + public clear() { + clearTimeout(this.timer); + super.clear(); + } + + public flush() { + clearTimeout(this.timer); + super.flush(); + } + + private onTimeout = () => { + this.flush(); + }; +} diff --git a/src/plugins/bfetch/common/index.ts b/src/plugins/bfetch/common/index.ts index afa73ade80084..085b8e7c58a67 100644 --- a/src/plugins/bfetch/common/index.ts +++ b/src/plugins/bfetch/common/index.ts @@ -19,3 +19,5 @@ export * from './util'; export * from './streaming'; +export * from './buffer'; +export * from './batch'; diff --git a/src/plugins/bfetch/common/streaming/types.ts b/src/plugins/bfetch/common/streaming/types.ts index 1ee92edbc89ff..197ee9a52ff01 100644 --- a/src/plugins/bfetch/common/streaming/types.ts +++ b/src/plugins/bfetch/common/streaming/types.ts @@ -20,5 +20,5 @@ import { Observable } from 'rxjs'; export interface StreamingResponseHandler { - onRequest(payload: Payload): Observable; + getResponseStream(payload: Payload): Observable; } diff --git a/src/plugins/bfetch/common/util/index.ts b/src/plugins/bfetch/common/util/index.ts index 02843af9b4350..b5d1fcabbcd85 100644 --- a/src/plugins/bfetch/common/util/index.ts +++ b/src/plugins/bfetch/common/util/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from './normalize_error'; export * from './remove_leading_slash'; diff --git a/src/plugins/bfetch/common/util/normalize_error.ts b/src/plugins/bfetch/common/util/normalize_error.ts new file mode 100644 index 0000000000000..c2ee3d83f5eb5 --- /dev/null +++ b/src/plugins/bfetch/common/util/normalize_error.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ErrorLike } from '../batch'; + +export const normalizeError = (err: any): E => { + if (!err) { + return { + message: 'Unknown error.', + } as E; + } + if (err instanceof Error) { + return { message: err.message } as E; + } + if (typeof err === 'object') { + return { + ...err, + message: err.message || 'Unknown error.', + } as E; + } + return { + message: String(err), + } as E; +}; diff --git a/src/plugins/bfetch/docs/browser/reference.md b/src/plugins/bfetch/docs/browser/reference.md index 47a67c08a4c1f..444b1aa08a98e 100644 --- a/src/plugins/bfetch/docs/browser/reference.md +++ b/src/plugins/bfetch/docs/browser/reference.md @@ -1,8 +1,37 @@ # `bfetch` browser reference +- [`batchedFunction`](#batchedFunction) - [`fetchStreaming`](#fetchStreaming) +## `batchedFunction` + +Creates a function that will buffer its calls (until timeout—10ms default— or capacity reached—25 default) +and send all calls in one batch to the specified endpoint. The endpoint is expected +to stream results back in ND-JSON format using `Transfer-Encoding: chunked`, which is +implemented by `addBatchProcessingRoute` server-side method of `bfetch` plugin. + +The created function is expected to be called with a single object argument and will +return a promise that will resolve to an object. + +```ts +const fn = bfetch.batchedFunction({ url: '/my-plugin/something' }); + +const result = await fn({ foo: 'bar' }); +``` + +Options: + +- `url` — URL endpoint that will receive a batch of requests. This endpoint is expected + to receive batch as a serialized JSON array. It should stream responses back + in ND-JSON format using `Transfer-Encoding: chunked` HTTP/1 streaming. +- `fetchStreaming` — The instance of `fetchStreaming` function that will perform ND-JSON handling. + There should be a version of this function available in setup contract of `bfetch` plugin. +- `flushOnMaxItems` — The maximum size of function call buffer before sending the batch request. +- `maxItemAge` — The maximum timeout in milliseconds of the oldest item in the batch + before sending the batch request. + + ## `fetchStreaming` Executes an HTTP request and expects that server streams back results using @@ -12,4 +41,4 @@ HTTP/1 `Transfer-Encoding: chunked`. const { stream } = bfetch.fetchStreaming({ url: 'http://elastic.co' }); stream.subscribe(value => {}); -``` \ No newline at end of file +``` diff --git a/src/plugins/bfetch/docs/server/reference.md b/src/plugins/bfetch/docs/server/reference.md new file mode 100644 index 0000000000000..424532a50b817 --- /dev/null +++ b/src/plugins/bfetch/docs/server/reference.md @@ -0,0 +1,54 @@ +# `bfetch` server reference + +- [`addBatchProcessingRoute`](#addBatchProcessingRoute) +- [`addStreamingResponseRoute`](#addStreamingResponseRoute) + + +## `addBatchProcessingRoute` + +Sets up a server endpoint that expects to work with [`batchedFunction`](../browser/reference.md#batchedFunction). +The endpoint receives a batch of requests, processes each request and streams results +back immediately as they become available. You only need to implement the +processing of each request (`onBatchItem` function), everything else is handled. + +`onBatchItem` function is called for each individual request in the batch. +`onBatchItem` function receives a single object argument which is the payload +of one request; and it must return a promise that resolves to an object, too. +`onBatchItem` function is allowed to throw, in that case the error will be forwarded +to the browser only to the individual request, the rest of the batch will still continue +executing. + +```ts +plugins.bfetch.addBatchProcessingRoute( + '/my-plugin/double', + request => ({ + onBatchItem: async (payload) => { + // ... + return {}; + }, + }) +); +``` + +`request` is the `KibanaRequest` object. `addBatchProcessingRoute` together with `batchedFunction` +ensure that errors are handled and that all items in the batch get executed. + + +## `addStreamingResponseRoute` + +`addStreamingResponseRoute` is a lower-level interface that receives and `payload` +message returns and observable which results are streamed back as ND-JSON messages +until the observable completes. `addStreamingResponseRoute` does not know about the +type of the messages, it does not handle errors, and it does not have a concept of +batch size—observable can stream any number of messages until it completes. + +```ts +plugins.bfetch.addStreamingResponseRoute('/my-plugin/foo', request => ({ + getResponseStream: (payload) => { + const subject = new Subject(); + setTimeout(() => { subject.next('123'); }, 100); + setTimeout(() => { subject.complete(); }, 200); + return subject; + }, +})); +``` diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts new file mode 100644 index 0000000000000..064b791327e69 --- /dev/null +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -0,0 +1,521 @@ +/* + * 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 { createStreamingBatchedFunction } from './create_streaming_batched_function'; +import { fetchStreaming as fetchStreamingReal } from '../streaming/fetch_streaming'; +import { defer, of } from '../../../kibana_utils/public'; +import { Subject } from 'rxjs'; + +const getPromiseState = (promise: Promise): Promise<'resolved' | 'rejected' | 'pending'> => + Promise.race<'resolved' | 'rejected' | 'pending'>([ + new Promise(resolve => + promise.then( + () => resolve('resolved'), + () => resolve('rejected') + ) + ), + new Promise<'pending'>(resolve => resolve()).then(() => 'pending'), + ]); + +const isPending = (promise: Promise): Promise => + getPromiseState(promise).then(state => state === 'pending'); + +const setup = () => { + const xhr = ({} as unknown) as XMLHttpRequest; + const { promise, resolve, reject } = defer(); + const stream = new Subject(); + + const fetchStreaming = (jest.fn(() => ({ + xhr, + promise, + stream, + })) as unknown) as jest.SpyInstance & typeof fetchStreamingReal; + + return { + fetchStreaming, + xhr, + promise, + resolve, + reject, + stream, + }; +}; + +describe('createStreamingBatchedFunction()', () => { + test('returns a function', () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + }); + expect(typeof fn).toBe('function'); + }); + + test('returned function is async', () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + }); + const res = fn({}); + expect(typeof res.then).toBe('function'); + }); + + describe('when timeout is reached', () => { + test('dispatches batch', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ foo: 'bar' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ baz: 'quix' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + + await new Promise(r => setTimeout(r, 6)); + expect(fetchStreaming).toHaveBeenCalledTimes(1); + }); + + test('does nothing is buffer is empty', async () => { + const { fetchStreaming } = setup(); + createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + expect(fetchStreaming).toHaveBeenCalledTimes(0); + await new Promise(r => setTimeout(r, 6)); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + }); + + test('sends POST request to correct endpoint', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ foo: 'bar' }); + await new Promise(r => setTimeout(r, 6)); + + expect(fetchStreaming.mock.calls[0][0]).toMatchObject({ + url: '/test', + method: 'POST', + }); + }); + + test('collects calls into an array batch ordered by in same order as calls', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ foo: 'bar' }); + fn({ baz: 'quix' }); + + await new Promise(r => setTimeout(r, 6)); + const { body } = fetchStreaming.mock.calls[0][0]; + expect(JSON.parse(body)).toEqual({ + batch: [{ foo: 'bar' }, { baz: 'quix' }], + }); + }); + }); + + describe('when buffer becomes full', () => { + test('dispatches batch request', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ foo: 'bar' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ baz: 'quix' }); + expect(fetchStreaming).toHaveBeenCalledTimes(0); + fn({ full: 'yep' }); + expect(fetchStreaming).toHaveBeenCalledTimes(1); + }); + + test('sends POST request to correct endpoint with items in array batched sorted in call order', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ a: '1' }); + fn({ b: '2' }); + fn({ c: '3' }); + + expect(fetchStreaming.mock.calls[0][0]).toMatchObject({ + url: '/test', + method: 'POST', + }); + const { body } = fetchStreaming.mock.calls[0][0]; + expect(JSON.parse(body)).toEqual({ + batch: [{ a: '1' }, { b: '2' }, { c: '3' }], + }); + }); + + test('dispatches batch on full buffer and also on timeout', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + fn({ a: '1' }); + fn({ b: '2' }); + fn({ c: '3' }); + expect(fetchStreaming).toHaveBeenCalledTimes(1); + fn({ d: '4' }); + await new Promise(r => setTimeout(r, 6)); + expect(fetchStreaming).toHaveBeenCalledTimes(2); + }); + }); + + describe('when receiving results', () => { + test('does not resolve call promises until request finishes', async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + await new Promise(r => setTimeout(r, 6)); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(true); + }); + + test('resolves only promise of result that was streamed back', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise(r => setTimeout(r, 6)); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(true); + expect(await isPending(promise3)).toBe(true); + + stream.next( + JSON.stringify({ + id: 1, + result: { foo: 'bar' }, + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(true); + + stream.next( + JSON.stringify({ + id: 0, + result: { foo: 'bar 2' }, + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(false); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(true); + }); + + test('resolves each promise with correct data', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = fn({ a: '1' }); + const promise2 = fn({ b: '2' }); + const promise3 = fn({ c: '3' }); + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 1, + result: { foo: 'bar' }, + }) + '\n' + ); + stream.next( + JSON.stringify({ + id: 2, + result: { foo: 'bar 2' }, + }) + '\n' + ); + + expect(await isPending(promise1)).toBe(true); + expect(await isPending(promise2)).toBe(false); + expect(await isPending(promise3)).toBe(false); + expect(await promise2).toEqual({ foo: 'bar' }); + expect(await promise3).toEqual({ foo: 'bar 2' }); + }); + + test('rejects promise on error response', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise = fn({ a: '1' }); + await new Promise(r => setTimeout(r, 6)); + + expect(await isPending(promise)).toBe(true); + + stream.next( + JSON.stringify({ + id: 0, + error: { message: 'oops' }, + }) + '\n' + ); + + expect(await isPending(promise)).toBe(false); + const [, error] = await of(promise); + expect(error).toEqual({ + message: 'oops', + }); + }); + + test('resolves successful requests even after rejected ones', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + const promise3 = of(fn({ a: '3' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 2, + result: { b: '3' }, + }) + '\n' + ); + + await new Promise(r => setTimeout(r, 1)); + + stream.next( + JSON.stringify({ + id: 1, + error: { b: '2' }, + }) + '\n' + ); + + await new Promise(r => setTimeout(r, 1)); + + stream.next( + JSON.stringify({ + id: 0, + result: { b: '1' }, + }) + '\n' + ); + + await new Promise(r => setTimeout(r, 1)); + + const [result1] = await promise1; + const [, error2] = await promise2; + const [result3] = await promise3; + + expect(result1).toEqual({ b: '1' }); + expect(error2).toEqual({ b: '2' }); + expect(result3).toEqual({ b: '3' }); + }); + + describe('when stream closes prematurely', () => { + test('rejects pending promises with CONNECTION error code', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.complete(); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [, error2] = await promise2; + expect(error1).toMatchObject({ + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }); + expect(error2).toMatchObject({ + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }); + }); + + test('rejects with CONNECTION error only pending promises', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 1, + result: { b: '1' }, + }) + '\n' + ); + stream.complete(); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [result1] = await promise2; + expect(error1).toMatchObject({ + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }); + expect(result1).toMatchObject({ + b: '1', + }); + }); + }); + + describe('when stream errors', () => { + test('rejects pending promises with STREAM error code', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.error({ + message: 'something went wrong', + }); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [, error2] = await promise2; + expect(error1).toMatchObject({ + message: 'something went wrong', + code: 'STREAM', + }); + expect(error2).toMatchObject({ + message: 'something went wrong', + code: 'STREAM', + }); + }); + + test('rejects with STREAM error only pending promises', async () => { + const { fetchStreaming, stream } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + }); + + const promise1 = of(fn({ a: '1' })); + const promise2 = of(fn({ a: '2' })); + + await new Promise(r => setTimeout(r, 6)); + + stream.next( + JSON.stringify({ + id: 1, + result: { b: '1' }, + }) + '\n' + ); + stream.error('oops'); + + await new Promise(r => setTimeout(r, 1)); + + const [, error1] = await promise1; + const [result1] = await promise2; + expect(error1).toMatchObject({ + message: 'oops', + code: 'STREAM', + }); + expect(result1).toMatchObject({ + b: '1', + }); + }); + }); + }); +}); diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts new file mode 100644 index 0000000000000..07d5724a2520d --- /dev/null +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -0,0 +1,140 @@ +/* + * 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 { defer, Defer } from '../../../kibana_utils/public'; +import { + ItemBufferParams, + TimedItemBufferParams, + createBatchedFunction, + BatchResponseItem, + ErrorLike, +} from '../../common'; +import { fetchStreaming, split } from '../streaming'; +import { normalizeError } from '../../common'; + +export interface BatchItem { + payload: Payload; + future: Defer; +} + +export type BatchedFunc = (payload: Payload) => Promise; + +export interface BatchedFunctionProtocolError extends ErrorLike { + code: string; +} + +export interface StreamingBatchedFunctionParams { + /** + * URL endpoint that will receive a batch of requests. This endpoint is expected + * to receive batch as a serialized JSON array. It should stream responses back + * in ND-JSON format using `Transfer-Encoding: chunked` HTTP/1 streaming. + */ + url: string; + + /** + * The instance of `fetchStreaming` function that will perform ND-JSON handling. + * There should be a version of this function available in setup contract of `bfetch` + * plugin. + */ + fetchStreaming?: typeof fetchStreaming; + + /** + * The maximum size of function call buffer before sending the batch request. + */ + flushOnMaxItems?: ItemBufferParams['flushOnMaxItems']; + + /** + * The maximum timeout in milliseconds of the oldest item in the batch + * before sending the batch request. + */ + maxItemAge?: TimedItemBufferParams['maxItemAge']; +} + +/** + * Returns a function that does not execute immediately but buffers the call internally until + * `params.flushOnMaxItems` is reached or after `params.maxItemAge` timeout in milliseconds is reached. Once + * one of those thresholds is reached all buffered calls are sent in one batch to the + * server using `params.fetchStreaming` in a POST request. Responses are streamed back + * and each batch item is resolved once corresponding response is received. + */ +export const createStreamingBatchedFunction = ( + params: StreamingBatchedFunctionParams +): BatchedFunc => { + const { + url, + fetchStreaming: fetchStreamingInjected = fetchStreaming, + flushOnMaxItems = 25, + maxItemAge = 10, + } = params; + const [fn] = createBatchedFunction, BatchItem>({ + onCall: (payload: Payload) => { + const future = defer(); + const entry: BatchItem = { + payload, + future, + }; + return [future.promise, entry]; + }, + onBatch: async items => { + try { + let responsesReceived = 0; + const batch = items.map(({ payload }) => payload); + const { stream } = fetchStreamingInjected({ + url, + body: JSON.stringify({ batch }), + method: 'POST', + }); + stream.pipe(split('\n')).subscribe({ + next: (json: string) => { + const response = JSON.parse(json) as BatchResponseItem; + if (response.error) { + responsesReceived++; + items[response.id].future.reject(response.error); + } else if (response.result) { + responsesReceived++; + items[response.id].future.resolve(response.result); + } + }, + error: error => { + const normalizedError = normalizeError(error); + normalizedError.code = 'STREAM'; + for (const { future } of items) future.reject(normalizedError); + }, + complete: () => { + const streamTerminatedPrematurely = responsesReceived !== items.length; + if (streamTerminatedPrematurely) { + const error: BatchedFunctionProtocolError = { + message: 'Connection terminated prematurely.', + code: 'CONNECTION', + }; + for (const { future } of items) future.reject(error); + } + }, + }); + await stream.toPromise(); + } catch (error) { + for (const item of items) item.future.reject(error); + } + }, + flushOnMaxItems, + maxItemAge, + }); + + return fn; +}; diff --git a/src/plugins/bfetch/public/index.ts b/src/plugins/bfetch/public/index.ts index a57dd77fe7e67..8707e5a438159 100644 --- a/src/plugins/bfetch/public/index.ts +++ b/src/plugins/bfetch/public/index.ts @@ -20,7 +20,8 @@ import { PluginInitializerContext } from '../../../core/public'; import { BfetchPublicPlugin } from './plugin'; -export { BfetchPublicSetup, BfetchPublicStart, BfetchPublicApi } from './plugin'; +export { BfetchPublicSetup, BfetchPublicStart, BfetchPublicContract } from './plugin'; +export { split } from './streaming'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchPublicPlugin(initializerContext); diff --git a/src/plugins/bfetch/public/mocks.ts b/src/plugins/bfetch/public/mocks.ts index e8caf5c9cb739..f457b9ae5d671 100644 --- a/src/plugins/bfetch/public/mocks.ts +++ b/src/plugins/bfetch/public/mocks.ts @@ -27,6 +27,7 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { fetchStreaming: jest.fn(), + batchedFunction: jest.fn(), }; return setupContract; }; @@ -34,6 +35,7 @@ const createSetupContract = (): Setup => { const createStartContract = (): Start => { const startContract: Start = { fetchStreaming: jest.fn(), + batchedFunction: jest.fn(), }; return startContract; @@ -56,7 +58,7 @@ const createPlugin = async () => { }; }; -export const uiActionsPluginMock = { +export const bfetchPluginMock = { createSetupContract, createStartContract, createPlugin, diff --git a/src/plugins/bfetch/public/plugin.ts b/src/plugins/bfetch/public/plugin.ts index db18a15afa1e7..783c448c567e5 100644 --- a/src/plugins/bfetch/public/plugin.ts +++ b/src/plugins/bfetch/public/plugin.ts @@ -20,6 +20,11 @@ import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/public'; import { fetchStreaming as fetchStreamingStatic, FetchStreamingParams } from './streaming'; import { removeLeadingSlash } from '../common'; +import { + createStreamingBatchedFunction, + BatchedFunc, + StreamingBatchedFunctionParams, +} from './batching/create_streaming_batched_function'; // eslint-disable-next-line export interface BfetchPublicSetupDependencies {} @@ -27,12 +32,15 @@ export interface BfetchPublicSetupDependencies {} // eslint-disable-next-line export interface BfetchPublicStartDependencies {} -export interface BfetchPublicApi { +export interface BfetchPublicContract { fetchStreaming: (params: FetchStreamingParams) => ReturnType; + batchedFunction: ( + params: StreamingBatchedFunctionParams + ) => BatchedFunc; } -export type BfetchPublicSetup = BfetchPublicApi; -export type BfetchPublicStart = BfetchPublicApi; +export type BfetchPublicSetup = BfetchPublicContract; +export type BfetchPublicStart = BfetchPublicContract; export class BfetchPublicPlugin implements @@ -42,7 +50,7 @@ export class BfetchPublicPlugin BfetchPublicSetupDependencies, BfetchPublicStartDependencies > { - private api!: BfetchPublicApi; + private contract!: BfetchPublicContract; constructor(private readonly initializerContext: PluginInitializerContext) {} @@ -51,16 +59,18 @@ export class BfetchPublicPlugin const basePath = core.http.basePath.get(); const fetchStreaming = this.fetchStreaming(version, basePath); + const batchedFunction = this.batchedFunction(fetchStreaming); - this.api = { + this.contract = { fetchStreaming, + batchedFunction, }; - return this.api; + return this.contract; } public start(core: CoreStart, plugins: BfetchPublicStartDependencies): BfetchPublicStart { - return this.api; + return this.contract; } public stop() {} @@ -78,4 +88,12 @@ export class BfetchPublicPlugin ...(params.headers || {}), }, }); + + private batchedFunction = ( + fetchStreaming: BfetchPublicContract['fetchStreaming'] + ): BfetchPublicContract['batchedFunction'] => params => + createStreamingBatchedFunction({ + ...params, + fetchStreaming: params.fetchStreaming || fetchStreaming, + }); } diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts index e59af71cb76bc..7845616026ea1 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.test.ts @@ -36,14 +36,6 @@ test('returns XHR request', () => { expect(typeof xhr.readyState).toBe('number'); }); -test('returns promise', () => { - setup(); - const { promise } = fetchStreaming({ - url: 'http://example.com', - }); - expect(typeof promise.then).toBe('function'); -}); - test('returns stream', () => { setup(); const { stream } = fetchStreaming({ @@ -54,12 +46,12 @@ test('returns stream', () => { test('promise resolves when request completes', async () => { const env = setup(); - const { promise } = fetchStreaming({ + const { stream } = fetchStreaming({ url: 'http://example.com', }); let resolved = false; - promise.then(() => (resolved = true)); + stream.toPromise().then(() => (resolved = true)); await tick(); expect(resolved).toBe(false); @@ -142,12 +134,12 @@ test('completes stream observable when request finishes', async () => { test('promise throws when request errors', async () => { const env = setup(); - const { promise } = fetchStreaming({ + const { stream } = fetchStreaming({ url: 'http://example.com', }); const spy = jest.fn(); - promise.catch(spy); + stream.toPromise().catch(spy); await tick(); expect(spy).toHaveBeenCalledTimes(0); @@ -168,12 +160,11 @@ test('promise throws when request errors', async () => { test('stream observable errors when request errors', async () => { const env = setup(); - const { promise, stream } = fetchStreaming({ + const { stream } = fetchStreaming({ url: 'http://example.com', }); const spy = jest.fn(); - promise.catch(() => {}); stream.subscribe({ error: spy, }); diff --git a/src/plugins/bfetch/public/streaming/fetch_streaming.ts b/src/plugins/bfetch/public/streaming/fetch_streaming.ts index 44a3693e7010b..899e8a1824a41 100644 --- a/src/plugins/bfetch/public/streaming/fetch_streaming.ts +++ b/src/plugins/bfetch/public/streaming/fetch_streaming.ts @@ -17,7 +17,6 @@ * under the License. */ -import { defer } from '../../../kibana_utils/common'; import { fromStreamingXhr } from './from_streaming_xhr'; export interface FetchStreamingParams { @@ -38,7 +37,6 @@ export function fetchStreaming({ body = '', }: FetchStreamingParams) { const xhr = new window.XMLHttpRequest(); - const { promise, resolve, reject } = defer(); // Begin the request xhr.open(method, url); @@ -49,17 +47,11 @@ export function fetchStreaming({ const stream = fromStreamingXhr(xhr); - stream.subscribe({ - complete: () => resolve(), - error: error => reject(error), - }); - // Send the payload to the server xhr.send(body); return { xhr, - promise, stream, }; } diff --git a/src/plugins/bfetch/server/index.ts b/src/plugins/bfetch/server/index.ts index f1a3f7fd44cf6..06b7c793c537e 100644 --- a/src/plugins/bfetch/server/index.ts +++ b/src/plugins/bfetch/server/index.ts @@ -20,7 +20,7 @@ import { PluginInitializerContext } from '../../../core/server'; import { BfetchServerPlugin } from './plugin'; -export { BfetchServerSetup, BfetchServerStart } from './plugin'; +export { BfetchServerSetup, BfetchServerStart, BatchProcessingRouteParams } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new BfetchServerPlugin(initializerContext); diff --git a/src/plugins/bfetch/server/mocks.ts b/src/plugins/bfetch/server/mocks.ts index 8ec68650a60dc..e0a76ba8da325 100644 --- a/src/plugins/bfetch/server/mocks.ts +++ b/src/plugins/bfetch/server/mocks.ts @@ -26,6 +26,7 @@ export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { + addBatchProcessingRoute: jest.fn(), addStreamingResponseRoute: jest.fn(), }; return setupContract; @@ -54,7 +55,7 @@ const createPlugin = async () => { }; }; -export const uiActionsPluginMock = { +export const bfetchPluginMock = { createSetupContract, createStartContract, createPlugin, diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 75baeafc17669..fd1fe009e93ae 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -17,9 +17,24 @@ * under the License. */ -import { CoreStart, PluginInitializerContext, CoreSetup, Plugin, Logger } from 'src/core/server'; +import { + CoreStart, + PluginInitializerContext, + CoreSetup, + Plugin, + Logger, + KibanaRequest, +} from 'src/core/server'; import { schema } from '@kbn/config-schema'; -import { StreamingResponseHandler, removeLeadingSlash } from '../common'; +import { Subject } from 'rxjs'; +import { + StreamingResponseHandler, + BatchRequestData, + BatchResponseItem, + ErrorLike, + removeLeadingSlash, + normalizeError, +} from '../common'; import { createNDJSONStream } from './streaming'; // eslint-disable-next-line @@ -28,8 +43,19 @@ export interface BfetchServerSetupDependencies {} // eslint-disable-next-line export interface BfetchServerStartDependencies {} +export interface BatchProcessingRouteParams { + onBatchItem: (data: BatchItemData) => Promise; +} + export interface BfetchServerSetup { - addStreamingResponseRoute: (path: string, handler: StreamingResponseHandler) => void; + addBatchProcessingRoute: ( + path: string, + handler: (request: KibanaRequest) => BatchProcessingRouteParams + ) => void; + addStreamingResponseRoute: ( + path: string, + params: (request: KibanaRequest) => StreamingResponseHandler + ) => void; } // eslint-disable-next-line @@ -49,8 +75,10 @@ export class BfetchServerPlugin const logger = this.initializerContext.logger.get(); const router = core.http.createRouter(); const addStreamingResponseRoute = this.addStreamingResponseRoute({ router, logger }); + const addBatchProcessingRoute = this.addBatchProcessingRoute(addStreamingResponseRoute); return { + addBatchProcessingRoute, addStreamingResponseRoute, }; } @@ -76,17 +104,56 @@ export class BfetchServerPlugin }, }, async (context, request, response) => { + const handlerInstance = handler(request); const data = request.body; + const headers = { + 'Content-Type': 'application/x-ndjson', + Connection: 'keep-alive', + 'Transfer-Encoding': 'chunked', + 'Cache-Control': 'no-cache', + }; return response.ok({ - headers: { - 'Content-Type': 'application/x-ndjson', - Connection: 'keep-alive', - 'Transfer-Encoding': 'chunked', - 'Cache-Control': 'no-cache', - }, - body: createNDJSONStream(data, handler, logger), + headers, + body: createNDJSONStream(data, handlerInstance, logger), }); } ); }; + + private addBatchProcessingRoute = ( + addStreamingResponseRoute: BfetchServerSetup['addStreamingResponseRoute'] + ): BfetchServerSetup['addBatchProcessingRoute'] => < + BatchItemData extends object, + BatchItemResult extends object, + E extends ErrorLike = ErrorLike + >( + path: string, + handler: (request: KibanaRequest) => BatchProcessingRouteParams + ) => { + addStreamingResponseRoute< + BatchRequestData, + BatchResponseItem + >(path, request => { + const handlerInstance = handler(request); + return { + getResponseStream: ({ batch }) => { + const subject = new Subject>(); + let cnt = batch.length; + batch.forEach(async (batchItem, id) => { + try { + const result = await handlerInstance.onBatchItem(batchItem); + subject.next({ id, result }); + } catch (err) { + const error = normalizeError(err); + subject.next({ id, error }); + } finally { + cnt--; + if (!cnt) subject.complete(); + } + }); + return subject; + }, + }; + }); + }; } diff --git a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts index b1f39f4acbcb5..82fe31906e8bf 100644 --- a/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts +++ b/src/plugins/bfetch/server/streaming/create_ndjson_stream.ts @@ -29,7 +29,7 @@ export const createNDJSONStream = ( logger: Logger ): Stream => { const stream = new PassThrough(); - const results = handler.onRequest(payload); + const results = handler.getResponseStream(payload); results.subscribe({ next: (message: Response) => { diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts new file mode 100644 index 0000000000000..0527f833b0f8c --- /dev/null +++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreSetup } from 'src/core/public'; +import { QuerySuggestionsGetFn } from './providers/query_suggestion_provider'; +import { + setupValueSuggestionProvider, + ValueSuggestionsGetFn, +} from './providers/value_suggestion_provider'; + +export class AutocompleteService { + private readonly querySuggestionProviders: Map = new Map(); + private getValueSuggestions?: ValueSuggestionsGetFn; + + private addQuerySuggestionProvider = ( + language: string, + provider: QuerySuggestionsGetFn + ): void => { + if (language && provider) { + this.querySuggestionProviders.set(language, provider); + } + }; + + private getQuerySuggestions: QuerySuggestionsGetFn = args => { + const { language } = args; + const provider = this.querySuggestionProviders.get(language); + + if (provider) { + return provider(args); + } + }; + + private hasQuerySuggestions = (language: string) => this.querySuggestionProviders.has(language); + + /** @public **/ + public setup(core: CoreSetup) { + this.getValueSuggestions = setupValueSuggestionProvider(core); + + return { + addQuerySuggestionProvider: this.addQuerySuggestionProvider, + + /** @obsolete **/ + /** please use "getProvider" only from the start contract **/ + getQuerySuggestions: this.getQuerySuggestions, + }; + } + + /** @public **/ + public start() { + return { + getQuerySuggestions: this.getQuerySuggestions, + hasQuerySuggestions: this.hasQuerySuggestions, + getValueSuggestions: this.getValueSuggestions!, + }; + } + + /** @internal **/ + public clearProviders(): void { + this.querySuggestionProviders.clear(); + } +} diff --git a/src/plugins/data/public/autocomplete/index.ts b/src/plugins/data/public/autocomplete/index.ts new file mode 100644 index 0000000000000..5b8f3ae510bfd --- /dev/null +++ b/src/plugins/data/public/autocomplete/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 { AutocompleteService } from './autocomplete_service'; +export { QuerySuggestion, QuerySuggestionType, QuerySuggestionsGetFn } from './types'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts similarity index 59% rename from src/plugins/data/public/autocomplete_provider/types.ts rename to src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts index 389057f94144d..53abdd44c0c3f 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete/providers/query_suggestion_provider.ts @@ -17,56 +17,40 @@ * under the License. */ -import { AutocompleteProviderRegister } from '.'; -import { IIndexPattern, IFieldType } from '../../common'; +import { IFieldType, IIndexPattern } from '../../../common/index_patterns'; -export type AutocompletePublicPluginSetup = Pick< - AutocompleteProviderRegister, - 'addProvider' | 'getProvider' ->; -export type AutocompletePublicPluginStart = Pick; +export type QuerySuggestionType = 'field' | 'value' | 'operator' | 'conjunction' | 'recentSearch'; -/** @public **/ -export type AutocompleteProvider = (args: { - config: { - get(configKey: string): any; - }; - indexPatterns: IIndexPattern[]; - boolFilter?: any; -}) => GetSuggestions; +export type QuerySuggestionsGetFn = ( + args: QuerySuggestionsGetFnArgs +) => Promise | undefined; -/** @public **/ -export type GetSuggestions = (args: { +interface QuerySuggestionsGetFnArgs { + language: string; + indexPatterns: IIndexPattern[]; query: string; selectionStart: number; selectionEnd: number; signal?: AbortSignal; -}) => Promise; - -/** @public **/ -export type AutocompleteSuggestionType = - | 'field' - | 'value' - | 'operator' - | 'conjunction' - | 'recentSearch'; - -// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm -// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the -// TypeScript compiler will narrow the type to the parts of the union that have a field prop. -/** @public **/ -export type AutocompleteSuggestion = BasicAutocompleteSuggestion | FieldAutocompleteSuggestion; + boolFilter?: any; +} -interface BasicAutocompleteSuggestion { +interface BasicQuerySuggestion { + type: QuerySuggestionType; description?: string; end: number; start: number; text: string; - type: AutocompleteSuggestionType; cursorIndex?: number; } -export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & { +interface FieldQuerySuggestion extends BasicQuerySuggestion { type: 'field'; field: IFieldType; -}; +} + +// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm +// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the +// TypeScript compiler will narrow the type to the parts of the union that have a field prop. +/** @public **/ +export type QuerySuggestion = BasicQuerySuggestion | FieldQuerySuggestion; diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts similarity index 51% rename from src/plugins/data/public/suggestions_provider/value_suggestions.test.ts rename to src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts index 9089105b4e3a8..6b0c0f07cf6c9 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.test.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -17,98 +17,121 @@ * under the License. */ -import { stubIndexPattern, stubFields } from '../stubs'; -import { getSuggestionsProvider } from './value_suggestions'; -import { IUiSettingsClient } from 'kibana/public'; +import { stubIndexPattern, stubFields } from '../../stubs'; +import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider'; +import { IUiSettingsClient, CoreSetup } from 'kibana/public'; -describe('getSuggestions', () => { - let getSuggestions: any; +describe('FieldSuggestions', () => { + let getValueSuggestions: ValueSuggestionsGetFn; let http: any; + let shouldSuggestValues: boolean; - describe('with value suggestions disabled', () => { - beforeEach(() => { - const config = { get: (key: string) => false } as IUiSettingsClient; - http = { fetch: jest.fn() }; - getSuggestions = getSuggestionsProvider(config, http); - }); + beforeEach(() => { + const uiSettings = { get: (key: string) => shouldSuggestValues } as IUiSettingsClient; + http = { fetch: jest.fn() }; + getValueSuggestions = setupValueSuggestionProvider({ http, uiSettings } as CoreSetup); + }); + + describe('with value suggestions disabled', () => { it('should return an empty array', async () => { - const index = stubIndexPattern.id; - const [field] = stubFields; - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: stubFields[0], + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); }); describe('with value suggestions enabled', () => { - beforeEach(() => { - const config = { get: (key: string) => true } as IUiSettingsClient; - http = { fetch: jest.fn() }; - getSuggestions = getSuggestionsProvider(config, http); - }); + shouldSuggestValues = true; it('should return true/false for boolean fields', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ type }) => type === 'boolean'); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([true, false]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should return an empty array if the field type is not a string or boolean', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ type }) => type !== 'string' && type !== 'boolean'); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should return an empty array if the field is not aggregatable', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter(({ aggregatable }) => !aggregatable); - const query = ''; - const suggestions = await getSuggestions(index, field, query); + const suggestions = await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(suggestions).toEqual([]); expect(http.fetch).not.toHaveBeenCalled(); }); it('should otherwise request suggestions', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; - await getSuggestions(index, field, query); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field, + query: '', + }); + expect(http.fetch).toHaveBeenCalled(); }); it('should cache results if using the same index/field/query/filter', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; - await getSuggestions(index, field, query); - await getSuggestions(index, field, query); + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + }; + + await getValueSuggestions(args); + await getValueSuggestions(args); + expect(http.fetch).toHaveBeenCalledTimes(1); }); it('should cache results for only one minute', async () => { - const index = stubIndexPattern.id; const [field] = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - const query = ''; + const args = { + indexPattern: stubIndexPattern, + field, + query: '', + }; const { now } = Date; Date.now = jest.fn(() => 0); - await getSuggestions(index, field, query); + + await getValueSuggestions(args); + Date.now = jest.fn(() => 60 * 1000); - await getSuggestions(index, field, query); + await getValueSuggestions(args); Date.now = now; expect(http.fetch).toHaveBeenCalledTimes(2); @@ -118,14 +141,54 @@ describe('getSuggestions', () => { const fields = stubFields.filter( ({ type, aggregatable }) => type === 'string' && aggregatable ); - await getSuggestions('index', fields[0], ''); - await getSuggestions('index', fields[0], 'query'); - await getSuggestions('index', fields[1], ''); - await getSuggestions('index', fields[1], 'query'); - await getSuggestions('logstash-*', fields[0], ''); - await getSuggestions('logstash-*', fields[0], 'query'); - await getSuggestions('logstash-*', fields[1], ''); - await getSuggestions('logstash-*', fields[1], 'query'); + + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: '', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[0], + query: 'query', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: '', + }); + await getValueSuggestions({ + indexPattern: stubIndexPattern, + field: fields[1], + query: 'query', + }); + + const customIndexPattern = { + ...stubIndexPattern, + title: 'customIndexPattern', + }; + + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: '', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[0], + query: 'query', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: '', + }); + await getValueSuggestions({ + indexPattern: customIndexPattern, + field: fields[1], + query: 'query', + }); + expect(http.fetch).toHaveBeenCalledTimes(8); }); }); diff --git a/src/plugins/data/public/suggestions_provider/value_suggestions.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts similarity index 55% rename from src/plugins/data/public/suggestions_provider/value_suggestions.ts rename to src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index e64156c290db1..5df88000edbd5 100644 --- a/src/plugins/data/public/suggestions_provider/value_suggestions.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -18,51 +18,53 @@ */ import { memoize } from 'lodash'; +import { CoreSetup } from 'src/core/public'; +import { IIndexPattern, IFieldType } from '../../../common'; -import { IUiSettingsClient, HttpSetup } from 'src/core/public'; -import { IGetSuggestions } from './types'; -import { IFieldType } from '../../common'; +function resolver(title: string, field: IFieldType, query: string, boolFilter: any) { + // Only cache results for a minute + const ttl = Math.floor(Date.now() / 1000 / 60); + + return [ttl, query, title, field.name, JSON.stringify(boolFilter)].join('|'); +} + +export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; + +interface ValueSuggestionsGetFnArgs { + indexPattern: IIndexPattern; + field: IFieldType; + query: string; + boolFilter?: any[]; + signal?: AbortSignal; +} -export function getSuggestionsProvider( - uiSettings: IUiSettingsClient, - http: HttpSetup -): IGetSuggestions { +export const setupValueSuggestionProvider = (core: CoreSetup): ValueSuggestionsGetFn => { const requestSuggestions = memoize( - ( - index: string, - field: IFieldType, - query: string, - boolFilter: any = [], - signal?: AbortSignal - ) => { - return http.fetch(`/api/kibana/suggestions/values/${index}`, { + (index: string, field: IFieldType, query: string, boolFilter: any = [], signal?: AbortSignal) => + core.http.fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', body: JSON.stringify({ query, field: field.name, boolFilter }), signal, - }); - }, + }), resolver ); - return async ( - index: string, - field: IFieldType, - query: string, - boolFilter?: any, - signal?: AbortSignal - ) => { - const shouldSuggestValues = uiSettings.get('filterEditor:suggestValues'); + return async ({ + indexPattern, + field, + query, + boolFilter, + signal, + }: ValueSuggestionsGetFnArgs): Promise => { + const shouldSuggestValues = core!.uiSettings.get('filterEditor:suggestValues'); + const { title } = indexPattern; + if (field.type === 'boolean') { return [true, false]; } else if (!shouldSuggestValues || !field.aggregatable || field.type !== 'string') { return []; } - return await requestSuggestions(index, field, query, boolFilter, signal); - }; -} -function resolver(index: string, field: IFieldType, query: string, boolFilter: any) { - // Only cache results for a minute - const ttl = Math.floor(Date.now() / 1000 / 60); - return [ttl, query, index, field.name, JSON.stringify(boolFilter)].join('|'); -} + return await requestSuggestions(title, field, query, boolFilter, signal); + }; +}; diff --git a/src/legacy/core_plugins/visualizations/public/expressions/boot.ts b/src/plugins/data/public/autocomplete/types.ts similarity index 67% rename from src/legacy/core_plugins/visualizations/public/expressions/boot.ts rename to src/plugins/data/public/autocomplete/types.ts index c30493e7d4c36..759e2dd25a5bc 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/boot.ts +++ b/src/plugins/data/public/autocomplete/types.ts @@ -17,9 +17,17 @@ * under the License. */ -import { npSetup } from 'ui/new_platform'; -import { visualization as visualizationFunction } from './visualization_function'; -import { visualization as visualizationRenderer } from './visualization_renderer'; +import { AutocompleteService } from './autocomplete_service'; -npSetup.plugins.expressions.registerFunction(visualizationFunction); -npSetup.plugins.expressions.registerRenderer(visualizationRenderer); +/** @public **/ +export type AutocompleteSetup = ReturnType; + +/** @public **/ +export type AutocompleteStart = ReturnType; + +/** @public **/ +export { + QuerySuggestion, + QuerySuggestionsGetFn, + QuerySuggestionType, +} from './providers/query_suggestion_provider'; diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 4b330600417e7..19ba246ce02dd 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -18,6 +18,8 @@ */ import { PluginInitializerContext } from '../../../core/public'; +import * as autocomplete from './autocomplete'; + export function plugin(initializerContext: PluginInitializerContext) { return new DataPublicPlugin(initializerContext); } @@ -49,11 +51,6 @@ export { TimeRange, } from '../common'; -/** - * Static code to be shared externally - * @public - */ -export * from './autocomplete_provider'; export * from './field_formats_provider'; export * from './index_patterns'; export * from './search'; @@ -97,3 +94,5 @@ export { // Export plugin after all other imports import { DataPublicPlugin } from './plugin'; export { DataPublicPlugin as Plugin }; + +export { autocomplete }; diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index 03d3dad61ed05..08a7a3ef11537 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -30,9 +30,9 @@ export type Setup = jest.Mocked>; export type Start = jest.Mocked>; const autocompleteMock: any = { - addProvider: jest.fn(), - getProvider: jest.fn(), - clearProviders: jest.fn(), + getValueSuggestions: jest.fn(), + getQuerySuggestions: jest.fn(), + hasQuerySuggestions: jest.fn(), }; const fieldFormatsMock: PublicMethodsOf = { diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index cd55048ca527f..78abcbcfec467 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -25,8 +25,7 @@ import { DataSetupDependencies, DataStartDependencies, } from './types'; -import { AutocompleteProviderRegister } from './autocomplete_provider'; -import { getSuggestionsProvider } from './suggestions_provider'; +import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; @@ -38,7 +37,7 @@ import { APPLY_FILTER_TRIGGER } from '../../embeddable/public'; import { createSearchBar } from './ui/search_bar/create_search_bar'; export class DataPublicPlugin implements Plugin { - private readonly autocomplete = new AutocompleteProviderRegister(); + private readonly autocomplete = new AutocompleteService(); private readonly searchService: SearchService; private readonly fieldFormatsService: FieldFormatsService; private readonly queryService: QueryService; @@ -62,7 +61,7 @@ export class DataPublicPlugin implements Plugin any; diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 4fd8bdbaae7b8..d2af256302248 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -20,10 +20,9 @@ import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { IUiActionsSetup, IUiActionsStart } from 'src/plugins/ui_actions/public'; -import { AutocompletePublicPluginSetup, AutocompletePublicPluginStart } from '.'; +import { AutocompleteSetup, AutocompleteStart } from './autocomplete/types'; import { FieldFormatsSetup, FieldFormatsStart } from './field_formats_provider'; import { ISearchSetup, ISearchStart } from './search'; -import { IGetSuggestions } from './suggestions_provider/types'; import { QuerySetup, QueryStart } from './query'; import { IndexPatternSelectProps } from './ui/index_pattern_select'; import { IndexPatternsContract } from './index_patterns'; @@ -38,15 +37,14 @@ export interface DataStartDependencies { } export interface DataPublicPluginSetup { - autocomplete: AutocompletePublicPluginSetup; + autocomplete: AutocompleteSetup; search: ISearchSetup; fieldFormats: FieldFormatsSetup; query: QuerySetup; } export interface DataPublicPluginStart { - autocomplete: AutocompletePublicPluginStart; - getSuggestions: IGetSuggestions; + autocomplete: AutocompleteStart; indexPatterns: IndexPatternsContract; search: ISearchStart; fieldFormats: FieldFormatsStart; @@ -57,9 +55,6 @@ export interface DataPublicPluginStart { }; } -export * from './autocomplete_provider/types'; -export { IGetSuggestions } from './suggestions_provider/types'; - export interface IDataPluginServices extends Partial { appName: string; uiSettings: CoreStart['uiSettings']; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts index bb15cffa67b59..13542710eac08 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/lib/filter_operators.ts @@ -18,11 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { esFilters } from '../../../..'; +import { FILTERS } from '../../../../../common/es_query/filters'; export interface Operator { message: string; - type: esFilters.FILTERS; + type: FILTERS; negate: boolean; fieldTypes?: string[]; } @@ -31,7 +31,7 @@ export const isOperator = { message: i18n.translate('data.filter.filterEditor.isOperatorOptionLabel', { defaultMessage: 'is', }), - type: esFilters.FILTERS.PHRASE, + type: FILTERS.PHRASE, negate: false, }; @@ -39,7 +39,7 @@ export const isNotOperator = { message: i18n.translate('data.filter.filterEditor.isNotOperatorOptionLabel', { defaultMessage: 'is not', }), - type: esFilters.FILTERS.PHRASE, + type: FILTERS.PHRASE, negate: true, }; @@ -47,7 +47,7 @@ export const isOneOfOperator = { message: i18n.translate('data.filter.filterEditor.isOneOfOperatorOptionLabel', { defaultMessage: 'is one of', }), - type: esFilters.FILTERS.PHRASES, + type: FILTERS.PHRASES, negate: false, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], }; @@ -56,7 +56,7 @@ export const isNotOneOfOperator = { message: i18n.translate('data.filter.filterEditor.isNotOneOfOperatorOptionLabel', { defaultMessage: 'is not one of', }), - type: esFilters.FILTERS.PHRASES, + type: FILTERS.PHRASES, negate: true, fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape'], }; @@ -65,7 +65,7 @@ export const isBetweenOperator = { message: i18n.translate('data.filter.filterEditor.isBetweenOperatorOptionLabel', { defaultMessage: 'is between', }), - type: esFilters.FILTERS.RANGE, + type: FILTERS.RANGE, negate: false, fieldTypes: ['number', 'date', 'ip'], }; @@ -74,7 +74,7 @@ export const isNotBetweenOperator = { message: i18n.translate('data.filter.filterEditor.isNotBetweenOperatorOptionLabel', { defaultMessage: 'is not between', }), - type: esFilters.FILTERS.RANGE, + type: FILTERS.RANGE, negate: true, fieldTypes: ['number', 'date', 'ip'], }; @@ -83,7 +83,7 @@ export const existsOperator = { message: i18n.translate('data.filter.filterEditor.existsOperatorOptionLabel', { defaultMessage: 'exists', }), - type: esFilters.FILTERS.EXISTS, + type: FILTERS.EXISTS, negate: false, }; @@ -91,7 +91,7 @@ export const doesNotExistOperator = { message: i18n.translate('data.filter.filterEditor.doesNotExistOperatorOptionLabel', { defaultMessage: 'does not exist', }), - type: esFilters.FILTERS.EXISTS, + type: FILTERS.EXISTS, negate: true, }; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 61290cc16b8a8..2b2d83c9f5a8b 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -63,13 +63,19 @@ export class PhraseSuggestorUI extends Component this.updateSuggestions(`${value}`); }; - protected updateSuggestions = debounce(async (value: string = '') => { + protected updateSuggestions = debounce(async (query: string = '') => { const { indexPattern, field } = this.props as PhraseSuggestorProps; if (!field || !this.isSuggestingValues()) { return; } this.setState({ isLoading: true }); - const suggestions = await this.services.data.getSuggestions(indexPattern.title, field, value); + + const suggestions = await this.services.data.autocomplete.getValueSuggestions({ + indexPattern, + field, + query, + }); + this.setState({ suggestions, isLoading: false }); }, 500); } 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 4ec29ca409b80..6432f8049641a 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 @@ -189,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", }, @@ -482,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 4c8edd85eb559..bc08c87304fca 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 @@ -151,9 +151,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -295,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", }, @@ -774,9 +777,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -918,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", }, @@ -1379,9 +1385,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -1523,7 +1529,10 @@ exports[`QueryStringInput Should pass the query language to the language switche "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", }, @@ -1999,9 +2008,9 @@ exports[`QueryStringInput Should pass the query language to the language switche }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -2143,7 +2152,10 @@ exports[`QueryStringInput Should pass the query language to the language switche "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", }, @@ -2604,9 +2616,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -2748,7 +2760,10 @@ exports[`QueryStringInput Should render the given query 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", }, @@ -3224,9 +3239,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` }, "data": Object { "autocomplete": Object { - "addProvider": [MockFunction], - "clearProviders": [MockFunction], - "getProvider": [MockFunction], + "getQuerySuggestions": [MockFunction], + "getValueSuggestions": [MockFunction], + "hasQuerySuggestions": [MockFunction], }, "fieldFormats": Object { "getByFieldType": [MockFunction], @@ -3368,7 +3383,10 @@ exports[`QueryStringInput Should render the given query 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/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 960a843f98ab9..cf219c35bcced 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 @@ -35,8 +35,7 @@ import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { debounce, compact, isEqual } from 'lodash'; import { Toast } from 'src/core/public'; import { - AutocompleteSuggestion, - AutocompleteSuggestionType, + autocomplete, IDataPluginServices, IIndexPattern, PersistedLog, @@ -71,7 +70,7 @@ interface Props { interface State { isSuggestionsVisible: boolean; index: number | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; suggestionLimit: number; selectionStart: number | null; selectionEnd: number | null; @@ -90,7 +89,7 @@ const KEY_CODES = { END: 35, }; -const recentSearchType: AutocompleteSuggestionType = 'recentSearch'; +const recentSearchType: autocomplete.QuerySuggestionType = 'recentSearch'; export class QueryStringInputUI extends Component { public state: State = { @@ -138,15 +137,14 @@ export class QueryStringInputUI extends Component { return; } - const uiSettings = this.services.uiSettings; const language = this.props.query.language; const queryString = this.getQueryString(); const recentSearchSuggestions = this.getRecentSearchSuggestions(queryString); - const autocompleteProvider = this.services.data.autocomplete.getProvider(language); + const hasQuerySuggestions = this.services.data.autocomplete.hasQuerySuggestions(language); if ( - !autocompleteProvider || + !hasQuerySuggestions || !Array.isArray(this.state.indexPatterns) || compact(this.state.indexPatterns).length === 0 ) { @@ -154,10 +152,6 @@ export class QueryStringInputUI extends Component { } const indexPatterns = this.state.indexPatterns; - const getAutocompleteSuggestions = autocompleteProvider({ - config: uiSettings, - indexPatterns, - }); const { selectionStart, selectionEnd } = this.inputRef; if (selectionStart === null || selectionEnd === null) { @@ -167,12 +161,16 @@ export class QueryStringInputUI extends Component { try { if (this.abortController) this.abortController.abort(); this.abortController = new AbortController(); - const suggestions: AutocompleteSuggestion[] = await getAutocompleteSuggestions({ - query: queryString, - selectionStart, - selectionEnd, - signal: this.abortController.signal, - }); + const suggestions = + (await this.services.data.autocomplete.getQuerySuggestions({ + language, + indexPatterns, + query: queryString, + selectionStart, + selectionEnd, + signal: this.abortController.signal, + })) || []; + return [...suggestions, ...recentSearchSuggestions]; } catch (e) { // TODO: Waiting on https://github.com/elastic/kibana/issues/51406 for a properly typed error @@ -321,7 +319,7 @@ export class QueryStringInputUI extends Component { } }; - private selectSuggestion = (suggestion: AutocompleteSuggestion) => { + private selectSuggestion = (suggestion: autocomplete.QuerySuggestion) => { if (!this.inputRef) { return; } @@ -351,7 +349,7 @@ export class QueryStringInputUI extends Component { } }; - private handleNestedFieldSyntaxNotification = (suggestion: AutocompleteSuggestion) => { + private handleNestedFieldSyntaxNotification = (suggestion: autocomplete.QuerySuggestion) => { if ( 'field' in suggestion && suggestion.field.subType && @@ -453,7 +451,7 @@ export class QueryStringInputUI extends Component { } }; - private onClickSuggestion = (suggestion: AutocompleteSuggestion) => { + private onClickSuggestion = (suggestion: autocomplete.QuerySuggestion) => { if (!this.inputRef) { return; } diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx index 591176bf133fa..0c5c701642757 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx @@ -19,14 +19,14 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; const noop = () => { return; }; -const mockSuggestion: AutocompleteSuggestion = { +const mockSuggestion: autocomplete.QuerySuggestion = { description: 'This is not a helpful suggestion', end: 0, start: 42, diff --git a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx index fd29de4573ff0..1d2ac8dee1a8a 100644 --- a/src/plugins/data/public/ui/typeahead/suggestion_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx @@ -20,7 +20,7 @@ import { EuiIcon } from '@elastic/eui'; import classNames from 'classnames'; import React, { FunctionComponent } from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; function getEuiIconType(type: string) { switch (type) { @@ -40,10 +40,10 @@ function getEuiIconType(type: string) { } interface Props { - onClick: (suggestion: AutocompleteSuggestion) => void; + onClick: (suggestion: autocomplete.QuerySuggestion) => void; onMouseEnter: () => void; selected: boolean; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; innerRef: (node: HTMLDivElement) => void; ariaId: string; } diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx index 7fb2fdf25104a..b84f612b6d13a 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx @@ -19,7 +19,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; import { SuggestionsComponent } from './suggestions_component'; @@ -27,7 +27,7 @@ const noop = () => { return; }; -const mockSuggestions: AutocompleteSuggestion[] = [ +const mockSuggestions: autocomplete.QuerySuggestion[] = [ { description: 'This is not a helpful suggestion', end: 0, diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index e4cccbcde4fb8..b37a2e479e874 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,15 +19,15 @@ import { isEmpty } from 'lodash'; import React, { Component } from 'react'; -import { AutocompleteSuggestion } from '../..'; +import { autocomplete } from '../..'; import { SuggestionComponent } from './suggestion_component'; interface Props { index: number | null; - onClick: (suggestion: AutocompleteSuggestion) => void; + onClick: (suggestion: autocomplete.QuerySuggestion) => void; onMouseEnter: (index: number) => void; show: boolean; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; loadMore: () => void; } 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 0d787fa56b400..3ec903d5b18e4 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 2215bd8a95a1d..0c8c2ce48fa84 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/embeddable/public/components/embeddable_panel/__examples__/embeddable_panel.examples.tsx b/src/plugins/embeddable/public/components/embeddable_panel/__examples__/embeddable_panel.examples.tsx new file mode 100644 index 0000000000000..7ec8848b8cebd --- /dev/null +++ b/src/plugins/embeddable/public/components/embeddable_panel/__examples__/embeddable_panel.examples.tsx @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { storiesOf } from '@storybook/react'; +import { EmbeddablePanel } from '..'; + +storiesOf('components/EmbeddablePanel', module).add('default', () => ); diff --git a/src/plugins/embeddable/public/components/embeddable_panel/index.tsx b/src/plugins/embeddable/public/components/embeddable_panel/index.tsx new file mode 100644 index 0000000000000..7089efa4bca88 --- /dev/null +++ b/src/plugins/embeddable/public/components/embeddable_panel/index.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 { EuiPanel } from '@elastic/eui'; +import * as React from 'react'; + +export const EmbeddablePanel = () => { + return ( + + Hello world + + ); +}; diff --git a/src/plugins/embeddable/scripts/storybook.js b/src/plugins/embeddable/scripts/storybook.js new file mode 100644 index 0000000000000..0d7712fe973f4 --- /dev/null +++ b/src/plugins/embeddable/scripts/storybook.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. + */ + +import { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'embeddable', + storyGlobs: [join(__dirname, '..', 'public', 'components', '**', '*.examples.tsx')], +}); 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 0000000000000..81476a65f4215 --- /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 0000000000000..8c63cc8494a8b --- /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 0000000000000..1b5ca5d7f4384 --- /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/legacy/ui/public/vis/editors/default/agg_params.d.ts b/src/plugins/es_ui_shared/public/index.ts similarity index 93% rename from src/legacy/ui/public/vis/editors/default/agg_params.d.ts rename to src/plugins/es_ui_shared/public/index.ts index 89896c0e1be3e..a12c951ad13a8 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_params.d.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -17,6 +17,4 @@ * under the License. */ -export interface AggParams { - [key: string]: unknown; -} +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 5b9a6dc9de002..07fca1a7f7595 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 0443b4ff09e60..c8ba9f5ac4102 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 0f7332e3954e6..e77337e4ecf53 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 b9c6424a00656..c6fccb0c0e383 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/legacy/core_plugins/interpreter/server/lib/create_handlers.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/lowercase_string.ts similarity index 61% rename from src/legacy/core_plugins/interpreter/server/lib/create_handlers.ts rename to src/plugins/es_ui_shared/static/forms/helpers/field_validators/lowercase_string.ts index 6e295d0aecaa5..42de66b930eaa 100644 --- a/src/legacy/core_plugins/interpreter/server/lib/create_handlers.ts +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/lowercase_string.ts @@ -17,16 +17,23 @@ * under the License. */ -export const createHandlers = (request: any, server: any) => { - const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); - const config = server.config(); +import { ValidationFunc } from '../../hook_form_lib'; +import { isLowerCaseString } from '../../../validators/string'; +import { ERROR_CODE } from './types'; - return { - environment: 'server', - serverUri: - config.has('server.rewriteBasePath') && config.get('server.rewriteBasePath') - ? `${server.info.uri}${config.get('server.basePath')}` - : server.info.uri, - elasticsearchClient: async (...args: any) => callWithRequest(request, ...args), - }; +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 0000000000000..767302a8328c1 --- /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 0000000000000..4eab3c5b0f103 --- /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 25cf038ec227d..d5bceac836137 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 06f5c2369df10..a8d24984cec7c 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 b5f1d18ee9e64..1088a3f82aa4f 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 580ec7714027c..021d52fbe9edb 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 0000000000000..b84c5585e017c --- /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 b7c6c39e7b0c5..5dcd076b41533 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 d7ef798bf2e03..80cddb513b20a 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 3902b0615a33d..d8b2f35e117a6 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 4c0169cb526e2..7365f234d39ed 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 62867a0c07a6b..65cd7792a0189 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 9946020132354..8dc1e59b40c34 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 0000000000000..fa9024204e727 --- /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/legacy/core_plugins/timelion/public/__tests__/index.js b/src/plugins/es_ui_shared/static/validators/number/index.ts similarity index 92% rename from src/legacy/core_plugins/timelion/public/__tests__/index.js rename to src/plugins/es_ui_shared/static/validators/number/index.ts index 9cd61c3665a1a..64a0cdd1b5a1d 100644 --- a/src/legacy/core_plugins/timelion/public/__tests__/index.js +++ b/src/plugins/es_ui_shared/static/validators/number/index.ts @@ -17,5 +17,6 @@ * under the License. */ -import './_tick_generator.js'; -describe('Timelion', function() {}); +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 0000000000000..50b43890ebf05 --- /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 0ddf6fdfc33e4..1e80ca0700637 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 0000000000000..3d765a750a81a --- /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/expressions/common/type.ts b/src/plugins/expressions/common/type.ts index de9c43d01a0aa..c9daed9b6785a 100644 --- a/src/plugins/expressions/common/type.ts +++ b/src/plugins/expressions/common/type.ts @@ -30,11 +30,6 @@ export function getType(node: any) { } export function serializeProvider(types: any) { - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; - function provider(key: any) { return (context: any) => { const type = getType(context); @@ -43,6 +38,11 @@ export function serializeProvider(types: any) { return fn(context); }; } + + return { + serialize: provider('serialize'), + deserialize: provider('deserialize'), + }; } export class Type { diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json index ec87b56f3745e..cba693dd4bc20 100644 --- a/src/plugins/expressions/kibana.json +++ b/src/plugins/expressions/kibana.json @@ -1,9 +1,10 @@ { "id": "expressions", "version": "kibana", - "server": false, + "server": true, "ui": true, "requiredPlugins": [ + "bfetch", "inspector" ] } diff --git a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts b/src/plugins/expressions/public/batched_fetch.test.ts similarity index 97% rename from src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts rename to src/plugins/expressions/public/batched_fetch.test.ts index 3da15cf54cda0..7273be872a725 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.test.ts +++ b/src/plugins/expressions/public/batched_fetch.test.ts @@ -18,7 +18,7 @@ */ import { batchedFetch, Request } from './batched_fetch'; -import { defer } from '../../../../../plugins/kibana_utils/public'; +import { defer } from '../../kibana_utils/public'; import { Subject } from 'rxjs'; const serialize = (o: any) => JSON.stringify(o); diff --git a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts b/src/plugins/expressions/public/batched_fetch.ts similarity index 87% rename from src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts rename to src/plugins/expressions/public/batched_fetch.ts index 717a87fc90f9f..6a155b7d42b72 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/batched_fetch.ts +++ b/src/plugins/expressions/public/batched_fetch.ts @@ -20,13 +20,11 @@ import _ from 'lodash'; import { filter, map } from 'rxjs/operators'; // eslint-disable-next-line -import { split } from '../../../../../plugins/bfetch/public/streaming'; -import { BfetchPublicApi } from '../../../../../plugins/bfetch/public'; -import { defer } from '../../../../../plugins/kibana_utils/public'; -import { FUNCTIONS_URL } from './consts'; +import { split, BfetchPublicContract } from '../../bfetch/public'; +import { defer } from '../../kibana_utils/public'; export interface Options { - fetchStreaming: BfetchPublicApi['fetchStreaming']; + fetchStreaming: BfetchPublicContract['fetchStreaming']; serialize: any; ms?: number; } @@ -111,9 +109,9 @@ export function batchedFetch({ fetchStreaming, serialize, ms = 10 }: Options) { * Runs the specified batch of functions on the server, then resolves * the related promises. */ -async function processBatch(fetchStreaming: BfetchPublicApi['fetchStreaming'], batch: Batch) { - const { stream, promise } = fetchStreaming({ - url: FUNCTIONS_URL, +async function processBatch(fetchStreaming: BfetchPublicContract['fetchStreaming'], batch: Batch) { + const { stream } = fetchStreaming({ + url: `/api/interpreter/fns`, body: JSON.stringify({ functions: Object.values(batch).map(({ request }) => request), }), @@ -137,7 +135,7 @@ async function processBatch(fetchStreaming: BfetchPublicApi['fetchStreaming'], b }); try { - await promise; + await stream.toPromise(); } catch (error) { Object.values(batch).forEach(({ future }) => { future.reject(error); diff --git a/src/plugins/expressions/public/execute.ts b/src/plugins/expressions/public/execute.ts index 12e84f677ce3e..89ef272a0d023 100644 --- a/src/plugins/expressions/public/execute.ts +++ b/src/plugins/expressions/public/execute.ts @@ -65,6 +65,7 @@ export class ExpressionDataHandler { getInitialContext, inspectorAdapters: this.inspectorAdapters, abortSignal: this.abortController.signal, + variables: params.variables, }) .then( (v: IInterpreterResult) => { diff --git a/src/plugins/expressions/public/functions/tests/var.test.ts b/src/plugins/expressions/public/functions/tests/var.test.ts new file mode 100644 index 0000000000000..fe5963ec8c509 --- /dev/null +++ b/src/plugins/expressions/public/functions/tests/var.test.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { functionWrapper } from './utils'; +import { variable } from '../var'; +import { FunctionHandlers } from '../../../common/types'; +import { KibanaContext } from '../../../common/expression_types/kibana_context'; + +describe('interpreter/functions#var', () => { + const fn = functionWrapper(variable); + let context: Partial; + let initialContext: KibanaContext; + let handlers: FunctionHandlers; + + beforeEach(() => { + context = { timeRange: { from: '0', to: '1' } }; + initialContext = { + type: 'kibana_context', + query: { language: 'lucene', query: 'geo.src:US' }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + }, + query: { match: {} }, + }, + ], + timeRange: { from: '2', to: '3' }, + }; + handlers = { + getInitialContext: () => initialContext, + variables: { test: 1 } as any, + }; + }); + + it('returns the selected variable', () => { + const actual = fn(context, { name: 'test' }, handlers); + expect(actual).toEqual(1); + }); + + it('returns undefined if variable does not exist', () => { + const actual = fn(context, { name: 'unknown' }, handlers); + expect(actual).toEqual(undefined); + }); +}); diff --git a/src/plugins/expressions/public/functions/tests/var_set.test.ts b/src/plugins/expressions/public/functions/tests/var_set.test.ts new file mode 100644 index 0000000000000..7efa8ebc0dd3f --- /dev/null +++ b/src/plugins/expressions/public/functions/tests/var_set.test.ts @@ -0,0 +1,74 @@ +/* + * 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 { functionWrapper } from './utils'; +import { variableSet } from '../var_set'; +import { FunctionHandlers } from '../../../common/types'; +import { KibanaContext } from '../../../common/expression_types/kibana_context'; + +describe('interpreter/functions#varset', () => { + const fn = functionWrapper(variableSet); + let context: Partial; + let initialContext: KibanaContext; + let handlers: FunctionHandlers; + let variables: Record; + + beforeEach(() => { + context = { timeRange: { from: '0', to: '1' } }; + initialContext = { + type: 'kibana_context', + query: { language: 'lucene', query: 'geo.src:US' }, + filters: [ + { + meta: { + disabled: false, + negate: false, + alias: null, + }, + query: { match: {} }, + }, + ], + timeRange: { from: '2', to: '3' }, + }; + handlers = { + getInitialContext: () => initialContext, + variables: { test: 1 } as any, + }; + + variables = handlers.variables; + }); + + it('updates a variable', () => { + const actual = fn(context, { name: 'test', value: 2 }, handlers); + expect(variables.test).toEqual(2); + expect(actual).toEqual(context); + }); + + it('sets a new variable', () => { + const actual = fn(context, { name: 'new', value: 3 }, handlers); + expect(variables.new).toEqual(3); + expect(actual).toEqual(context); + }); + + it('stores context if value is not set', () => { + const actual = fn(context, { name: 'test' }, handlers); + expect(variables.test).toEqual(context); + expect(actual).toEqual(context); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/schemas.d.ts b/src/plugins/expressions/public/functions/var.ts similarity index 51% rename from src/legacy/ui/public/vis/editors/default/schemas.d.ts rename to src/plugins/expressions/public/functions/var.ts index 236421f30fb09..9410149060216 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.d.ts +++ b/src/plugins/expressions/public/functions/var.ts @@ -17,21 +17,33 @@ * under the License. */ -import { AggParam } from '../../../agg_types'; -import { AggGroupNames } from './agg_groups'; -import { AggControlProps } from './controls/agg_control_props'; +import { i18n } from '@kbn/i18n'; +import { ExpressionFunction } from '../../common/types'; -export interface Schema { - aggFilter: string | string[]; - editor: boolean | string; - group: AggGroupNames; - max: number; - min: number; +interface Arguments { name: string; - params: AggParam[]; - title: string; - defaults: unknown; - hideCustomLabel?: boolean; - mustBeFirst?: boolean; - editorComponent?: React.ComponentType; } + +type Context = any; +type ExpressionFunctionVar = ExpressionFunction<'var', Context, Arguments, any>; + +export const variable = (): ExpressionFunctionVar => ({ + name: 'var', + help: i18n.translate('expressions.functions.var.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + name: { + types: ['string'], + aliases: ['_'], + required: true, + help: i18n.translate('expressions.functions.var.name.help', { + defaultMessage: 'Specify name of the variable', + }), + }, + }, + fn(context, args, handlers) { + const variables: Record = handlers.variables; + return variables[args.name]; + }, +}); diff --git a/src/plugins/expressions/public/functions/var_set.ts b/src/plugins/expressions/public/functions/var_set.ts new file mode 100644 index 0000000000000..a10ee7a00814f --- /dev/null +++ b/src/plugins/expressions/public/functions/var_set.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunction } from '../../common/types'; + +interface Arguments { + name: string; + value?: any; +} + +type Context = any; +type ExpressionFunctionVarSet = ExpressionFunction<'var_set', Context, Arguments, Context>; + +export const variableSet = (): ExpressionFunctionVarSet => ({ + name: 'var_set', + help: i18n.translate('expressions.functions.varset.help', { + defaultMessage: 'Updates kibana global context', + }), + args: { + name: { + types: ['string'], + aliases: ['_'], + required: true, + help: i18n.translate('expressions.functions.varset.name.help', { + defaultMessage: 'Specify name of the variable', + }), + }, + value: { + aliases: ['val'], + help: i18n.translate('expressions.functions.varset.val.help', { + defaultMessage: + 'Specify value for the variable. If not provided input context will be used', + }), + }, + }, + fn(context, args, handlers) { + const variables: Record = handlers.variables; + variables[args.name] = args.value === undefined ? context : args.value; + return context; + }, +}); diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 0342713f7627b..d714282360f71 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -178,6 +178,9 @@ export class ExpressionLoader { if (params.extraHandlers && this.params) { this.params.extraHandlers = params.extraHandlers; } + if (params.variables && this.params) { + this.params.variables = params.variables; + } } } diff --git a/src/plugins/expressions/public/mocks.tsx b/src/plugins/expressions/public/mocks.tsx index 089c324677712..a3476a24dd7ed 100644 --- a/src/plugins/expressions/public/mocks.tsx +++ b/src/plugins/expressions/public/mocks.tsx @@ -23,6 +23,7 @@ import { ExpressionsSetup, ExpressionsStart, plugin as pluginInitializer } from /* eslint-disable */ import { coreMock } from '../../../core/public/mocks'; import { inspectorPluginMock } from '../../inspector/public/mocks'; +import { bfetchPluginMock } from '../../bfetch/public/mocks'; /* eslint-enable */ export type Setup = jest.Mocked; @@ -48,6 +49,7 @@ const createSetupContract = (): Setup => { interpretAst: () => {}, }, }), + loadLegacyServerFunctionWrappers: () => Promise.resolve(), }, }; return setupContract; @@ -71,6 +73,7 @@ const createPlugin = async () => { const coreStart = coreMock.createStart(); const plugin = pluginInitializer(pluginInitializerContext); const setup = await plugin.setup(coreSetup, { + bfetch: bfetchPluginMock.createSetupContract(), inspector: inspectorPluginMock.createSetupContract(), }); @@ -82,6 +85,7 @@ const createPlugin = async () => { setup, doStart: async () => await plugin.start(coreStart, { + bfetch: bfetchPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), }), }; diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index 7471326cdd749..2ba10be76cd92 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -20,6 +20,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { ExpressionInterpretWithHandlers, ExpressionExecutor } from './types'; import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './registries'; +import { BfetchPublicSetup, BfetchPublicStart } from '../../bfetch/public'; import { Setup as InspectorSetup, Start as InspectorStart } from '../../inspector/public'; import { setCoreStart, @@ -32,6 +33,8 @@ import { clog as clogFunction } from './functions/clog'; import { font as fontFunction } from './functions/font'; import { kibana as kibanaFunction } from './functions/kibana'; import { kibanaContext as kibanaContextFunction } from './functions/kibana_context'; +import { variable } from './functions/var'; +import { variableSet } from './functions/var_set'; import { boolean as booleanType, datatable as datatableType, @@ -56,12 +59,15 @@ import { ExpressionLoader, loader } from './loader'; import { ExpressionDataHandler, execute } from './execute'; import { render, ExpressionRenderHandler } from './render'; import { AnyExpressionFunction, AnyExpressionType } from '../common/types'; +import { serializeProvider } from '../common'; export interface ExpressionsSetupDeps { + bfetch: BfetchPublicSetup; inspector: InspectorSetup; } export interface ExpressionsStartDeps { + bfetch: BfetchPublicStart; inspector: InspectorStart; } @@ -74,6 +80,7 @@ export interface ExpressionsSetup { renderers: RenderFunctionsRegistry; types: TypesRegistry; getExecutor: () => ExpressionExecutor; + loadLegacyServerFunctionWrappers: () => Promise; }; } @@ -96,7 +103,7 @@ export class ExpressionsPublicPlugin constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { inspector }: ExpressionsSetupDeps): ExpressionsSetup { + public setup(core: CoreSetup, { inspector, bfetch }: ExpressionsSetupDeps): ExpressionsSetup { const { functions, renderers, types } = this; setRenderersRegistry(renderers); @@ -109,6 +116,8 @@ export class ExpressionsPublicPlugin registerFunction(fontFunction); registerFunction(kibanaFunction); registerFunction(kibanaContextFunction); + registerFunction(variable); + registerFunction(variableSet); types.register(booleanType); types.register(datatableType); @@ -142,6 +151,31 @@ export class ExpressionsPublicPlugin setInterpreter(getExecutor().interpreter); + let cached: Promise | null = null; + const loadLegacyServerFunctionWrappers = async () => { + if (!cached) { + cached = (async () => { + const serverFunctionList = await core.http.get(`/api/interpreter/fns`); + const batchedFunction = bfetch.batchedFunction({ url: `/api/interpreter/fns` }); + const { serialize } = serializeProvider(types.toJS()); + + // For every sever-side function, register a client-side + // function that matches its definition, but which simply + // calls the server-side function endpoint. + Object.keys(serverFunctionList).forEach(functionName => { + const fn = () => ({ + ...serverFunctionList[functionName], + fn: (context: any, args: any) => { + return batchedFunction({ functionName, args, context: serialize(context) }); + }, + }); + registerFunction(fn); + }); + })(); + } + return cached; + }; + const setup: ExpressionsSetup = { registerFunction, registerRenderer: (renderer: any) => { @@ -155,6 +189,7 @@ export class ExpressionsPublicPlugin renderers, types, getExecutor, + loadLegacyServerFunctionWrappers, }, }; diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index 66a3da48dbee9..e094e5e91d006 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -65,6 +65,7 @@ export interface IExpressionLoaderParams { export interface IInterpreterHandlers { getInitialContext: IGetInitialContext; inspectorAdapters?: Adapters; + variables?: Record; abortSignal?: AbortSignal; } diff --git a/src/plugins/expressions/server/index.ts b/src/plugins/expressions/server/index.ts new file mode 100644 index 0000000000000..6718602ccdef5 --- /dev/null +++ b/src/plugins/expressions/server/index.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../core/server'; +import { ExpressionsServerPlugin } from './plugin'; + +export { ExpressionsServerSetup, ExpressionsServerStart } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new ExpressionsServerPlugin(initializerContext); +} diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts new file mode 100644 index 0000000000000..54e2a5a387342 --- /dev/null +++ b/src/plugins/expressions/server/legacy.ts @@ -0,0 +1,135 @@ +/* + * 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 max-classes-per-file */ + +// TODO: Remove this file once https://github.com/elastic/kibana/issues/46906 is complete. + +// @ts-ignore +import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common'; + +import Boom from 'boom'; +import { schema } from '@kbn/config-schema'; +import { CoreSetup, Logger } from 'src/core/server'; +import { ExpressionsServerSetupDependencies } from './plugin'; +import { typeSpecs as types, Type } from '../common'; +import { serializeProvider } from '../common'; + +export class TypesRegistry extends Registry { + wrapper(obj: any) { + return new (Type as any)(obj); + } +} + +export class FunctionsRegistry extends Registry { + wrapper(obj: any) { + return new Fn(obj); + } +} + +export const registries = { + types: new TypesRegistry(), + serverFunctions: new FunctionsRegistry(), +}; + +export interface LegacyInterpreterServerApi { + registries(): typeof registries; + register(specs: Record): typeof registries; +} + +export const createLegacyServerInterpreterApi = (): LegacyInterpreterServerApi => { + const api = registryFactory(registries); + + register(registries, { + types, + }); + + return api; +}; + +export const createLegacyServerEndpoints = ( + api: LegacyInterpreterServerApi, + logger: Logger, + core: CoreSetup, + plugins: ExpressionsServerSetupDependencies +) => { + const router = core.http.createRouter(); + + /** + * Register the endpoint that returns the list of server-only functions. + */ + router.get( + { + path: `/api/interpreter/fns`, + validate: { + body: schema.any(), + }, + }, + async (context, request, response) => { + const functions = api.registries().serverFunctions.toJS(); + const body = JSON.stringify(functions); + return response.ok({ + body, + }); + } + ); + + /** + * Run a single Canvas function. + * + * @param {*} server - The Kibana server object + * @param {*} handlers - The Canvas handlers + * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` + */ + async function runFunction(handlers: any, fnCall: any) { + const { functionName, args, context } = fnCall; + const { deserialize } = serializeProvider(registries.types.toJS()); + const fnDef = registries.serverFunctions.toJS()[functionName]; + if (!fnDef) throw Boom.notFound(`Function "${functionName}" could not be found.`); + const deserialized = deserialize(context); + const result = fnDef.fn(deserialized, args, handlers); + return result; + } + + /** + * Register an endpoint that executes a batch of functions, and streams the + * results back using ND-JSON. + */ + plugins.bfetch.addBatchProcessingRoute(`/api/interpreter/fns`, request => { + const scopedClient = core.elasticsearch.dataClient.asScoped(request); + const handlers = { + environment: 'server', + elasticsearchClient: async ( + endpoint: string, + clientParams: Record = {}, + options?: any + ) => scopedClient.callAsCurrentUser(endpoint, clientParams, options), + }; + + return { + onBatchItem: async (fnCall: any) => { + const result = await runFunction(handlers, fnCall); + if (typeof result === 'undefined') { + throw new Error(`Function ${fnCall.functionName} did not return anything.`); + } + return result; + }, + }; + }); +}; diff --git a/src/plugins/expressions/server/mocks.ts b/src/plugins/expressions/server/mocks.ts new file mode 100644 index 0000000000000..4510ae6dc0b4a --- /dev/null +++ b/src/plugins/expressions/server/mocks.ts @@ -0,0 +1,73 @@ +/* + * 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 { ExpressionsServerSetup, ExpressionsServerStart } from '.'; +import { plugin as pluginInitializer } from '.'; +import { coreMock } from '../../../core/server/mocks'; + +/* eslint-disable */ +import { bfetchPluginMock } from '../../bfetch/server/mocks'; +/* eslint-enable */ + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = { + __LEGACY: { + register: jest.fn(), + registries: jest.fn(), + }, + }; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = {}; + + return startContract; +}; + +const createPlugin = async () => { + const pluginInitializerContext = coreMock.createPluginInitializerContext(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + const plugin = pluginInitializer(pluginInitializerContext); + const setup = await plugin.setup(coreSetup, { + bfetch: bfetchPluginMock.createSetupContract(), + }); + + return { + pluginInitializerContext, + coreSetup, + coreStart, + plugin, + setup, + doStart: async () => + await plugin.start(coreStart, { + bfetch: bfetchPluginMock.createStartContract(), + }), + }; +}; + +export const expressionsPluginMock = { + createSetupContract, + createStartContract, + createPlugin, +}; diff --git a/src/plugins/expressions/server/plugin.ts b/src/plugins/expressions/server/plugin.ts new file mode 100644 index 0000000000000..84c780b5ca226 --- /dev/null +++ b/src/plugins/expressions/server/plugin.ts @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CoreStart, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server'; +import { BfetchServerSetup, BfetchServerStart } from '../../bfetch/server'; +import { + LegacyInterpreterServerApi, + createLegacyServerInterpreterApi, + createLegacyServerEndpoints, +} from './legacy'; + +// eslint-disable-next-line +export interface ExpressionsServerSetupDependencies { + bfetch: BfetchServerSetup; +} + +// eslint-disable-next-line +export interface ExpressionsServerStartDependencies { + bfetch: BfetchServerStart; +} + +export interface ExpressionsServerSetup { + __LEGACY: LegacyInterpreterServerApi; +} + +// eslint-disable-next-line +export interface ExpressionsServerStart {} + +export class ExpressionsServerPlugin + implements + Plugin< + ExpressionsServerSetup, + ExpressionsServerStart, + ExpressionsServerSetupDependencies, + ExpressionsServerStartDependencies + > { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public setup( + core: CoreSetup, + plugins: ExpressionsServerSetupDependencies + ): ExpressionsServerSetup { + const logger = this.initializerContext.logger.get(); + + const legacyApi = createLegacyServerInterpreterApi(); + createLegacyServerEndpoints(legacyApi, logger, core, plugins); + + return { + __LEGACY: legacyApi, + }; + } + + public start( + core: CoreStart, + plugins: ExpressionsServerStartDependencies + ): ExpressionsServerStart { + return {}; + } + + public stop() {} +} diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts index 25e94c20c347b..ca05c8b5f760e 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 a48ea8f795136..461930ddfb80f 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 fad6e8cf47bfe..34502d7d2c6cd 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 40f2047ef0016..39a7f23826900 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(), }; - style: Style; -} + return setup; +}; -export interface GaugeVisParams { - type: 'gauge'; - addTooltip: boolean; - addLegend: boolean; - isDisplayWarning: boolean; - gauge: Gauge; -} +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 0000000000000..f42eba782a760 --- /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 0000000000000..36c1afbca5e73 --- /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/legacy/core_plugins/interpreter/public/canvas/consts.ts b/src/plugins/home/public/services/environment/index.ts similarity index 86% rename from src/legacy/core_plugins/interpreter/public/canvas/consts.ts rename to src/plugins/home/public/services/environment/index.ts index 2600ada36afdc..ed20f6adb96c6 100644 --- a/src/legacy/core_plugins/interpreter/public/canvas/consts.ts +++ b/src/plugins/home/public/services/environment/index.ts @@ -17,5 +17,9 @@ * under the License. */ -// The server endpoint for retrieiving and running Canvas functions. -export const FUNCTIONS_URL = '/api/interpreter/fns'; +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 3621b0912393a..a6542dd066a67 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 fb56bf0e4255e..870dbdc533267 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 0c5d2b0c24831..7c44fe89d0e7f 100644 --- a/src/plugins/kibana_react/public/field_icon/field_icon.tsx +++ b/src/plugins/kibana_react/public/field_icon/field_icon.tsx @@ -36,6 +36,7 @@ interface FieldIconProps { | 'number' | '_source' | 'string' + | 'nested' | string; label?: string; size?: IconSize; @@ -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/saved_object_save_modal.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx index 33d4dcfd6606a..cdbc2bb9b5473 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_save_modal.tsx @@ -39,7 +39,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/kibana/public/visualize_embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../legacy/core_plugins/visualizations/public/embeddable/constants'; export interface OnSaveProps { newTitle: string; diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index eb3bb96c8e874..bfb45b88964d8 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -18,4 +18,5 @@ */ export * from './defer'; +export * from './of'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; diff --git a/src/plugins/kibana_utils/common/of.test.ts b/src/plugins/kibana_utils/common/of.test.ts new file mode 100644 index 0000000000000..6c3f0ec1592bd --- /dev/null +++ b/src/plugins/kibana_utils/common/of.test.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { of } from './of'; + +describe('of()', () => { + describe('when promise resolves', () => { + const promise = new Promise(resolve => resolve()).then(() => 123); + + test('first member of 3-tuple is the promise value', async () => { + const [result] = await of(promise); + expect(result).toBe(123); + }); + + test('second member of 3-tuple is undefined', async () => { + const [, error] = await of(promise); + expect(error).toBe(undefined); + }); + + test('third, flag member, of 3-tuple is true', async () => { + const [, , resolved] = await of(promise); + expect(resolved).toBe(true); + }); + }); + + describe('when promise rejects', () => { + const promise = new Promise(resolve => resolve()).then(() => { + // eslint-disable-next-line no-throw-literal + throw 123; + }); + + test('first member of 3-tuple is undefined', async () => { + const [result] = await of(promise); + expect(result).toBe(undefined); + }); + + test('second member of 3-tuple is thrown error', async () => { + const [, error] = await of(promise); + expect(error).toBe(123); + }); + + test('third, flag member, of 3-tuple is false', async () => { + const [, , resolved] = await of(promise); + expect(resolved).toBe(false); + }); + }); +}); diff --git a/src/plugins/kibana_utils/common/of.ts b/src/plugins/kibana_utils/common/of.ts new file mode 100644 index 0000000000000..fa0ec8b0ce306 --- /dev/null +++ b/src/plugins/kibana_utils/common/of.ts @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Given a promise awaits it and returns a 3-tuple, with the following members: + * + * - First entry is either the resolved value of the promise or `undefined`. + * - Second entry is either the error thrown by promise or `undefined`. + * - Third entry is a boolean, truthy if promise was resolved and falsy if rejected. + * + * @param promise Promise to convert to 3-tuple. + */ +export const of = async ( + promise: Promise +): Promise<[T | undefined, E | undefined, boolean]> => { + try { + return [await promise, undefined, true]; + } catch (error) { + return [undefined, error, false]; + } +}; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 0ba444c4e9395..fa58a61e51232 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -17,7 +17,7 @@ * under the License. */ -export { defer } from '../common'; +export { defer, Defer, of } from '../common'; export * from './core'; export * from './errors'; export * from './field_mapping'; diff --git a/src/plugins/testbed/server/index.ts b/src/plugins/testbed/server/index.ts index 4873fe0926472..96d5612508a96 100644 --- a/src/plugins/testbed/server/index.ts +++ b/src/plugins/testbed/server/index.ts @@ -23,7 +23,6 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { CoreSetup, CoreStart, - LegacyRenderOptions, Logger, PluginInitializerContext, PluginConfigDescriptor, @@ -78,29 +77,6 @@ class Plugin { } ); - router.get( - { - path: '/requestcontext/render/{id}', - validate: { - params: schema.object({ - id: schema.maybe(schema.string()), - }), - }, - }, - async (context, req, res) => { - const { id } = req.params; - const options: Partial = { app: { getId: () => id! } }; - const body = await context.core.rendering.render(options); - - return res.ok({ - body, - headers: { - 'content-securty-policy': core.http.csp.header, - }, - }); - } - ); - return { data$: this.initializerContext.config.create().pipe( map(configValue => { diff --git a/test/api_integration/apis/core/index.js b/test/api_integration/apis/core/index.js index 766faf067e4ac..be1ecf9b9c497 100644 --- a/test/api_integration/apis/core/index.js +++ b/test/api_integration/apis/core/index.js @@ -33,11 +33,6 @@ export default function({ getService }) { 200, 'SavedObjects client: {"page":1,"per_page":20,"total":0,"saved_objects":[]}' )); - - it('provides access to application rendering client', async () => { - await supertest.get('/requestcontext/render/core').expect(200, /app:core/); - await supertest.get('/requestcontext/render/testbed').expect(200, /app:testbed/); - }); }); describe('compression', () => { 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 25a533d39dd81..555056173ec62 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/home/_navigation.ts b/test/functional/apps/home/_navigation.ts index 96285901289b6..6fd631baa27d7 100644 --- a/test/functional/apps/home/_navigation.ts +++ b/test/functional/apps/home/_navigation.ts @@ -64,8 +64,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { } }); - // FLAKY: https://github.com/elastic/kibana/issues/33468 - it.skip('detect navigate back issues', async () => { + it('detect navigate back issues', async () => { let currUrl; // Detects bug described in issue #31238 - where back navigation would get stuck to URL encoding handling in Angular. // Navigate to home app 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 0000000000000..eb83e80e2bbe1 --- /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/_inspector.js b/test/functional/apps/visualize/_inspector.js index 84f955d9c7879..d989f8e2539a0 100644 --- a/test/functional/apps/visualize/_inspector.js +++ b/test/functional/apps/visualize/_inspector.js @@ -37,6 +37,7 @@ export default function({ getService, getPageObjects }) { it('should update table header when columns change', async function() { await inspector.open(); await inspector.expectTableHeaders(['Count']); + await inspector.close(); log.debug('Add Average Metric on machine.ram field'); await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); @@ -45,6 +46,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableHeaders(['Count', 'Average machine.ram']); + await inspector.close(); }); describe('filtering on inspector table values', function() { diff --git a/test/functional/apps/visualize/_markdown_vis.js b/test/functional/apps/visualize/_markdown_vis.js index 51c03c90f507b..fee6c074af5d2 100644 --- a/test/functional/apps/visualize/_markdown_vis.js +++ b/test/functional/apps/visualize/_markdown_vis.js @@ -63,7 +63,7 @@ export default function({ getPageObjects, getService }) { }); it('should resize the editor', async function() { - const editorSidebar = await find.byCssSelector('.visEditor__sidebar'); + const editorSidebar = await find.byCssSelector('.visEditor__collapsibleSidebar'); const initialSize = await editorSidebar.getSize(); await PageObjects.visEditor.sizeUpEditor(); const afterSize = await editorSidebar.getSize(); diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index e7ce5808554b4..d0f7810b6f8bb 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -57,7 +57,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.selectField('machine.ram', 'metrics'); // go to options page log.debug('Going to axis options'); - await pointSeriesVis.clickAxisOptions(); + await PageObjects.visEditor.clickMetricsAndAxes(); // add another value axis log.debug('adding axis'); await pointSeriesVis.clickAddAxis(); diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js index 10cbd9913c70c..2467a54061643 100644 --- a/test/functional/apps/visualize/_region_map.js +++ b/test/functional/apps/visualize/_region_map.js @@ -57,6 +57,7 @@ export default function({ getService, getPageObjects }) { ]; await inspector.open(); await inspector.expectTableData(expectedData); + await inspector.close(); }); it('should change results after changing layer to world', async function() { @@ -94,6 +95,8 @@ export default function({ getService, getPageObjects }) { ['BR', '415'], ]; expect(actualData).to.eql(expectedData); + + await inspector.close(); }); it('should contain a dropdown with the default road_map base layer as an option', async () => { diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js index 4f921cec1fdf1..a527e9bcad42f 100644 --- a/test/functional/apps/visualize/_tag_cloud.js +++ b/test/functional/apps/visualize/_tag_cloud.js @@ -77,12 +77,12 @@ export default function({ getService, getPageObjects }) { }); it('should collapse the sidebar', async function() { - const editorSidebar = await find.byCssSelector('.collapsible-sidebar'); + const editorSidebar = await find.byCssSelector('.visEditorSidebar'); await PageObjects.visEditor.clickEditorSidebarCollapse(); // Give d3 tag cloud some time to rearrange tags await PageObjects.common.sleep(1000); - const afterSize = await editorSidebar.getSize(); - expect(afterSize.width).to.be(0); + const isDisplayed = await editorSidebar.isDisplayed(); + expect(isDisplayed).to.be(false); await PageObjects.visEditor.clickEditorSidebarCollapse(); }); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 2a13b6fea9158..68285971e5c4a 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/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 75a15cc16db2e..347923670c413 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -371,6 +371,12 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo await browser.pressKeys(browser.keys.ENTER); } + // Pause the browser at a certain place for debugging + // Not meant for usage in CI, only for dev-usage + async pause() { + return browser.pause(); + } + /** * Clicks cancel button on modal * @param overlayWillStay pass in true if your test will show multiple modals in succession diff --git a/test/functional/page_objects/point_series_page.js b/test/functional/page_objects/point_series_page.js index 74bf07b59bc38..594facb8b74b5 100644 --- a/test/functional/page_objects/point_series_page.js +++ b/test/functional/page_objects/point_series_page.js @@ -23,10 +23,6 @@ export function PointSeriesPageProvider({ getService }) { const find = getService('find'); class PointSeriesVis { - async clickAxisOptions() { - return await testSubjects.click('visEditorTabadvanced'); - } - async clickAddAxis() { return await testSubjects.click('visualizeAddYAxisButton'); } diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js index 7c67678429478..2394abc9c2185 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'); } diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 30e13d551fa28..1e098e86216e3 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -37,19 +37,19 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP class VisualizeEditorPage { public async clickDataTab() { - await testSubjects.click('visualizeEditDataLink'); + await testSubjects.click('visEditorTab__data'); } public async clickOptionsTab() { - await testSubjects.click('visEditorTaboptions'); + await testSubjects.click('visEditorTab__options'); } public async clickMetricsAndAxes() { - await testSubjects.click('visEditorTabadvanced'); + await testSubjects.click('visEditorTab__advanced'); } public async clickVisEditorTab(tabName: string) { - await testSubjects.click('visEditorTab' + tabName); + await testSubjects.click(`visEditorTab__${tabName}`); await header.waitUntilLoadingHasFinished(); } @@ -134,7 +134,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP public async getBucketErrorMessage() { const error = await find.byCssSelector( - '[group-name="buckets"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' + '[data-test-subj="bucketsAggGroup"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' ); const errorMessage = await error.getAttribute('innerText'); log.debug(errorMessage); @@ -152,7 +152,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP ) { log.debug(`selectField ${fieldValue}`); const selector = ` - [group-name="${groupName}"] + [data-test-subj="${groupName}AggGroup"] [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen [data-test-subj="visAggEditorParams"] ${childAggregationType ? '.visEditorAgg__subAgg' : ''} @@ -180,7 +180,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP childAggregationType = false ) { const comboBoxElement = await find.byCssSelector(` - [group-name="${groupName}"] + [data-test-subj="${groupName}AggGroup"] [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen ${childAggregationType ? '.visEditorAgg__subAgg' : ''} [data-test-subj="defaultEditorAggSelect"] @@ -291,8 +291,9 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async sizeUpEditor() { - await testSubjects.click('visualizeEditorResizer'); - await browser.pressKeys(browser.keys.ARROW_RIGHT); + const resizerPanel = await testSubjects.find('splitPanelResizer'); + // Drag panel 100 px left + await browser.dragAndDrop({ location: resizerPanel }, { location: { x: -100, y: 0 } }); } public async toggleDisabledAgg(agg: string) { @@ -320,7 +321,10 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async toggleAutoMode() { - await testSubjects.click('visualizeEditorAutoButton'); + // this is a temporary solution, should be replaced with initial after fixing the EuiToggleButton + // passing the data-test-subj attribute to a checkbox + await find.clickByCssSelector('.visEditorSidebar__controls input[type="checkbox"]'); + // await testSubjects.click('visualizeEditorAutoButton'); } public async isApplyEnabled() { @@ -428,7 +432,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickMetricEditor() { - await find.clickByCssSelector('[group-name="metrics"] .euiAccordion__button'); + await find.clickByCssSelector('[data-test-subj="metricsAggGroup"] .euiAccordion__button'); } public async clickMetricByIndex(index: number) { diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 0071b8d993f70..e54e3d1d01154 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -200,7 +200,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide } public async getSideEditorExists() { - return await find.existsByCssSelector('.collapsible-sidebar'); + return await find.existsByCssSelector('.visEditor__collapsibleSidebar'); } public async clickSavedSearch(savedSearchName: string) { diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 72bcc07ab98cf..2d799b7daca73 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -199,6 +199,14 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { return await driver.get(url); } + /** + * Pauses the execution in the browser, similar to setting a breakpoint for debugging. + * @return {Promise} + */ + public async pause() { + await driver.executeAsyncScript(`(async () => { debugger; return Promise.resolve(); })()`); + } + /** * Moves the remote environment’s mouse cursor to the specified point {x, y} which is * offset to browser page top left corner. @@ -232,8 +240,8 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { * @return {Promise} */ public async dragAndDrop( - from: { offset: { x: any; y: any }; location: any }, - to: { offset: { x: any; y: any }; location: any } + from: { offset?: { x: any; y: any }; location: any }, + to: { offset?: { x: any; y: any }; location: any } ) { if (this.isW3CEnabled) { // The offset should be specified in pixels relative to the center of the element's bounding box diff --git a/test/functional/services/dashboard/add_panel.js b/test/functional/services/dashboard/add_panel.js index 7a26f8d447981..e390fd918eaee 100644 --- a/test/functional/services/dashboard/add_panel.js +++ b/test/functional/services/dashboard/add_panel.js @@ -28,6 +28,8 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { async clickOpenAddPanel() { log.debug('DashboardAddPanel.clickOpenAddPanel'); await testSubjects.click('dashboardAddPanelButton'); + // Give some time for the animation to complete + await PageObjects.common.sleep(500); } async clickAddNewEmbeddableLink(type) { @@ -96,8 +98,8 @@ export function DashboardAddPanelProvider({ getService, getPageObjects }) { if (!isOpen) { await retry.try(async () => { await this.clickOpenAddPanel(); - const isOpen = await this.isAddPanelOpen(); - if (!isOpen) { + const isNowOpen = await this.isAddPanelOpen(); + if (!isNowOpen) { throw new Error('Add panel still not open, trying again.'); } }); diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 1a7abc6317075..1bd6358749e11 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -43,6 +43,7 @@ import { Browsers } from './browsers'; const throttleOption: string = process.env.TEST_THROTTLE_NETWORK as string; const headlessBrowser: string = process.env.TEST_BROWSER_HEADLESS as string; +const remoteDebug: string = process.env.TEST_REMOTE_DEBUG as string; const SECOND = 1000; const MINUTE = 60 * SECOND; const NO_QUEUE_COMMANDS = ['getLog', 'getStatus', 'newSession', 'quit']; @@ -97,6 +98,10 @@ async function attemptToCreateCommand( // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md chromeOptions.push('headless', 'disable-gpu'); } + if (remoteDebug === '1') { + // Visit chrome://inspect in chrome to remotely view/debug + chromeOptions.push('headless', 'disable-gpu', 'remote-debugging-port=9222'); + } chromeCapabilities.set('goog:chromeOptions', { w3c: false, args: chromeOptions, diff --git a/test/functional/services/test_subjects.ts b/test/functional/services/test_subjects.ts index 8ef008d5dee50..d47b838c8d72a 100644 --- a/test/functional/services/test_subjects.ts +++ b/test/functional/services/test_subjects.ts @@ -169,10 +169,14 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { }); } - public async getAttribute(selector: string, attribute: string): Promise { + public async getAttribute( + selector: string, + attribute: string, + timeout?: number + ): Promise { return await retry.try(async () => { log.debug(`TestSubjects.getAttribute(${selector}, ${attribute})`); - const element = await this.find(selector); + const element = await this.find(selector, timeout); return await element.getAttribute(attribute); }); } 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 1eac93c8538e4..d96febee7b06d 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": "18.0.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0", "react-dom": "^16.12.0" } diff --git a/test/interpreter_functional/screenshots/baseline/metric_percentage.png b/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png similarity index 100% rename from test/interpreter_functional/screenshots/baseline/metric_percentage.png rename to test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json similarity index 100% rename from test/interpreter_functional/snapshots/baseline/metric_percentage.json rename to test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json diff --git a/test/interpreter_functional/snapshots/session/metric_percentage.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json similarity index 100% rename from test/interpreter_functional/snapshots/session/metric_percentage.json rename to test/interpreter_functional/snapshots/session/metric_percentage_mode.json diff --git a/test/interpreter_functional/test_suites/run_pipeline/metric.ts b/test/interpreter_functional/test_suites/run_pipeline/metric.ts index c238bedfa28ce..5f685037d4fad 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/metric.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/metric.ts @@ -81,11 +81,15 @@ export default function({ ).toMatchScreenshot(); }); - it('with percentage option', async () => { + it('with percentageMode option', async () => { const expression = - 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}'; + 'metricVis metric={visdimension 0} percentageMode=true colorRange={range from=0 to=1000}'; await ( - await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot() + await expectExpression( + 'metric_percentage_mode', + expression, + dataContext + ).toMatchSnapshot() ).toMatchScreenshot(); }); }); diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index e9a4f3bcc4b1a..e63054f1b6912 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -38,6 +38,7 @@ export default async function({ readConfigFile }) { require.resolve('./test_suites/embeddable_explorer'), require.resolve('./test_suites/core_plugins'), require.resolve('./test_suites/management'), + require.resolve('./test_suites/bfetch_explorer'), ], services: { ...functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/kibana.json b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/kibana.json new file mode 100644 index 0000000000000..1acc7df871c94 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "kbn_tp_bfetch_explorer", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["kbn_tp_bfetch_explorer"], + "server": true, + "ui": true, + "requiredPlugins": ["bfetch"], + "optionalPlugins": [] +} diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/package.json new file mode 100644 index 0000000000000..e396489a1ffc4 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/package.json @@ -0,0 +1,17 @@ +{ + "name": "kbn_tp_bfetch_explorer", + "version": "1.0.0", + "main": "target/examples/kbn_tp_bfetch_explorer", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/public/index.ts b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/public/index.ts new file mode 100644 index 0000000000000..547dfe2aa38d2 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/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 '../../../../../examples/bfetch_explorer/public'; diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/server/index.ts b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/server/index.ts new file mode 100644 index 0000000000000..b4370eb53311e --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/server/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 '../../../../../examples/bfetch_explorer/server'; diff --git a/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/tsconfig.json new file mode 100644 index 0000000000000..994f81e396763 --- /dev/null +++ b/test/plugin_functional/plugins/kbn_tp_bfetch_explorer/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true, + "types": [ + "node", + "jest", + "react" + ] + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "server/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} 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 1bfb1e8ba4bca..170cc77ca37cc 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": "18.0.0", + "@elastic/eui": "18.2.1", "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 6d6b04fba889c..85c76071d1e94 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": "18.0.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts index 57b5ad086e6a7..6d125bc3002e0 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts @@ -22,6 +22,20 @@ import 'ui/autoload/all'; import 'uiExports/interpreter'; import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableActions'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; import { npSetup, npStart } from 'ui/new_platform'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; 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 964adacb2ac09..ade93c9f50099 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": "18.0.0", + "@elastic/eui": "18.2.1", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/rendering_plugin/kibana.json b/test/plugin_functional/plugins/rendering_plugin/kibana.json new file mode 100644 index 0000000000000..886eca2bdde1d --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "rendering_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["rendering_plugin"], + "server": true, + "ui": true +} diff --git a/test/plugin_functional/plugins/rendering_plugin/package.json b/test/plugin_functional/plugins/rendering_plugin/package.json new file mode 100644 index 0000000000000..9ab6eccbe31be --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "rendering_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/rendering_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/rendering_plugin/public/index.ts b/test/plugin_functional/plugins/rendering_plugin/public/index.ts new file mode 100644 index 0000000000000..c32f3c03e8c63 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/public/index.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { RenderingPlugin } from './plugin'; + +export const plugin: PluginInitializer = () => new RenderingPlugin(); diff --git a/test/plugin_functional/plugins/rendering_plugin/public/plugin.tsx b/test/plugin_functional/plugins/rendering_plugin/public/plugin.tsx new file mode 100644 index 0000000000000..6e80b56953ca0 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/public/plugin.tsx @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { Plugin, CoreSetup } from 'kibana/public'; + +export class RenderingPlugin implements Plugin { + public setup(core: CoreSetup) { + core.application.register({ + id: 'rendering', + title: 'Rendering', + appRoute: '/render', + async mount(context, { element }) { + render(

rendering service

, element); + + return () => unmountComponentAtNode(element); + }, + }); + } + + public start() {} + + public stop() {} +} diff --git a/src/legacy/ui/public/vis/editor_size.js b/test/plugin_functional/plugins/rendering_plugin/server/index.ts similarity index 89% rename from src/legacy/ui/public/vis/editor_size.js rename to test/plugin_functional/plugins/rendering_plugin/server/index.ts index 5383772b3ad00..90ffdebb29f29 100644 --- a/src/legacy/ui/public/vis/editor_size.js +++ b/test/plugin_functional/plugins/rendering_plugin/server/index.ts @@ -17,8 +17,6 @@ * under the License. */ -export const DefaultEditorSize = { - SMALL: 'small', - MEDIUM: 'medium', - LARGE: 'large', -}; +import { RenderingPlugin } from './plugin'; + +export const plugin = () => new RenderingPlugin(); diff --git a/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts b/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts new file mode 100644 index 0000000000000..fad19728b7514 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/server/plugin.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup, IRenderOptions } from 'kibana/server'; + +import { schema } from '@kbn/config-schema'; + +export class RenderingPlugin implements Plugin { + public setup(core: CoreSetup) { + const router = core.http.createRouter(); + + router.get( + { + path: '/render/{id}', + validate: { + query: schema.object( + { + includeUserSettings: schema.boolean({ defaultValue: true }), + }, + { allowUnknowns: true } + ), + params: schema.object({ + id: schema.maybe(schema.string()), + }), + }, + }, + async (context, req, res) => { + const { id } = req.params; + const { includeUserSettings } = req.query; + const app = { getId: () => id! }; + const options: Partial = { app, includeUserSettings }; + const body = await context.core.rendering.render(options); + + return res.ok({ + body, + headers: { + 'content-security-policy': core.http.csp.header, + }, + }); + } + ); + } + + public start() {} + + public stop() {} +} diff --git a/test/plugin_functional/plugins/rendering_plugin/tsconfig.json b/test/plugin_functional/plugins/rendering_plugin/tsconfig.json new file mode 100644 index 0000000000000..1ba21f11b7de2 --- /dev/null +++ b/test/plugin_functional/plugins/rendering_plugin/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/test_suites/bfetch_explorer/batched_function.ts b/test/plugin_functional/test_suites/bfetch_explorer/batched_function.ts new file mode 100644 index 0000000000000..cb2a0b41694c2 --- /dev/null +++ b/test/plugin_functional/test_suites/bfetch_explorer/batched_function.ts @@ -0,0 +1,93 @@ +/* + * 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 '../../../functional/ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const appsMenu = getService('appsMenu'); + + describe('batchedFunction', () => { + beforeEach(async () => { + await appsMenu.clickLink('bfetch explorer'); + await testSubjects.click('count-until'); + await testSubjects.click('double-integers'); + }); + + it('executes all requests in a batch', async () => { + const form = await testSubjects.find('DoubleIntegers'); + const btn = await form.findByCssSelector('button'); + await btn.click(); + await new Promise(r => setTimeout(r, 4000)); + const pre = await form.findByCssSelector('pre'); + const text = await pre.getVisibleText(); + const json = JSON.parse(text); + + expect(json).to.eql([ + { + num: -1, + error: { + message: 'Invalid number', + }, + }, + { + num: 300, + result: { + num: 600, + }, + }, + { + num: 1000, + result: { + num: 2000, + }, + }, + { + num: 2000, + result: { + num: 4000, + }, + }, + ]); + }); + + it('streams results back', async () => { + const form = await testSubjects.find('DoubleIntegers'); + const btn = await form.findByCssSelector('button'); + await btn.click(); + + await new Promise(r => setTimeout(r, 500)); + const pre = await form.findByCssSelector('pre'); + + const text1 = await pre.getVisibleText(); + const json1 = JSON.parse(text1); + + expect(json1.length > 0).to.be(true); + expect(json1.length < 4).to.be(true); + + await new Promise(r => setTimeout(r, 3500)); + + const text2 = await pre.getVisibleText(); + const json2 = JSON.parse(text2); + + expect(json2.length).to.be(4); + }); + }); +} diff --git a/test/plugin_functional/test_suites/bfetch_explorer/index.ts b/test/plugin_functional/test_suites/bfetch_explorer/index.ts new file mode 100644 index 0000000000000..54f127d6de89a --- /dev/null +++ b/test/plugin_functional/test_suites/bfetch_explorer/index.ts @@ -0,0 +1,36 @@ +/* + * 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 { FtrProviderContext } from '../../../functional/ftr_provider_context'; + +export default function({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { + const browser = getService('browser'); + const appsMenu = getService('appsMenu'); + const PageObjects = getPageObjects(['common', 'header']); + + describe('bfetch explorer', function() { + before(async () => { + await browser.setWindowSize(1300, 900); + await PageObjects.common.navigateToApp('settings'); + await appsMenu.clickLink('bfetch explorer'); + }); + + loadTestFile(require.resolve('./batched_function')); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index 231458fad155b..f50d460532556 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -121,13 +121,13 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider expect(wrapperWidth).to.be.below(windowWidth); }); - it.skip('can navigate from NP apps to legacy apps', async () => { + it('can navigate from NP apps to legacy apps', async () => { await appsMenu.clickLink('Management'); await loadingScreenShown(); await testSubjects.existOrFail('managementNav'); }); - it.skip('can navigate from legacy apps to NP apps', async () => { + it('can navigate from legacy apps to NP apps', async () => { await appsMenu.clickLink('Foo'); await loadingScreenShown(); await testSubjects.existOrFail('fooAppHome'); diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts index d66e2e7dc5da7..d57ad11ea004e 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.ts +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -29,5 +29,6 @@ export default function({ loadTestFile }: PluginFunctionalProviderContext) { loadTestFile(require.resolve('./top_nav')); loadTestFile(require.resolve('./application_leave_confirm')); loadTestFile(require.resolve('./application_status')); + loadTestFile(require.resolve('./rendering')); }); } diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts new file mode 100644 index 0000000000000..d0025c82a7ba5 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -0,0 +1,127 @@ +/* + * 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 '../../plugins/core_provider_plugin/types'; +import { PluginFunctionalProviderContext } from '../../services'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + function navigate(path: string) { + return browser.get(`${PageObjects.common.getHostPort()}${path}`); + } + + function getLegacyMode() { + return browser.execute(() => { + return JSON.parse(document.querySelector('kbn-injected-metadata')!.getAttribute('data')!) + .legacyMode; + }); + } + + function getUserSettings() { + return browser.execute(() => { + return JSON.parse(document.querySelector('kbn-injected-metadata')!.getAttribute('data')!) + .legacyMetadata.uiSettings.user; + }); + } + + async function init() { + const loading = await testSubjects.find('kbnLoadingMessage', 5000); + + return () => find.waitForElementStale(loading); + } + + describe('rendering service', () => { + it('renders "core" application', async () => { + await navigate('/render/core'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(false); + expect(userSettings).to.not.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('renderingHeader')).to.be(true); + }); + + it('renders "core" application without user settings', async () => { + await navigate('/render/core?includeUserSettings=false'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(false); + expect(userSettings).to.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('renderingHeader')).to.be(true); + }); + + it('renders "legacy" application', async () => { + await navigate('/render/core_plugin_legacy'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(true); + expect(userSettings).to.not.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); + expect(await testSubjects.exists('renderingHeader')).to.be(false); + }); + + it('renders "legacy" application without user settings', async () => { + await navigate('/render/core_plugin_legacy?includeUserSettings=false'); + + const [loaded, legacyMode, userSettings] = await Promise.all([ + init(), + getLegacyMode(), + getUserSettings(), + ]); + + expect(legacyMode).to.be(true); + expect(userSettings).to.be.empty(); + + await loaded(); + + expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); + expect(await testSubjects.exists('renderingHeader')).to.be(false); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts index 3f89036fc2b65..eb232b1458991 100644 --- a/test/plugin_functional/test_suites/core_plugins/server_plugins.ts +++ b/test/plugin_functional/test_suites/core_plugins/server_plugins.ts @@ -54,15 +54,5 @@ export default function({ getService }: PluginFunctionalProviderContext) { statusCode: 400, }); }); - - it('renders core application explicitly', async () => { - await supertest.get('/requestcontext/render/core').expect(200, /app:core/); - }); - - it('renders legacy application', async () => { - await supertest - .get('/requestcontext/render/core_plugin_legacy') - .expect(200, /app:core_plugin_legacy/); - }); }); } diff --git a/tsconfig.json b/tsconfig.json index a2da9c127e7ba..811b05abeb648 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ ], "test_utils/*": [ "src/test_utils/public/*" - ] + ], + "fixtures/*": ["src/fixtures/*"] }, // Support .tsx files and transform JSX into calls to React.createElement "jsx": "react", @@ -51,7 +52,8 @@ "types": [ "node", "jest", - "react" + "react", + "flot" ] }, "include": [ diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index f38181ce56a2f..b746f0ae258cd 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -17,6 +17,7 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { moduleFileExtensions: ['js', 'json', 'ts', 'tsx'], moduleNameMapper: { '^ui/(.*)': `${kibanaDirectory}/src/legacy/ui/public/$1`, + '^fixtures/(.*)': `${kibanaDirectory}/src/fixtures/$1`, 'uiExports/(.*)': fileMockPath, '^src/core/(.*)': `${kibanaDirectory}/src/core/$1`, '^src/legacy/(.*)': `${kibanaDirectory}/src/legacy/$1`, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts index e0568a36f8743..33f2b21799814 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts @@ -8,26 +8,28 @@ import { ActionTypeRegistry } from '../action_type_registry'; import { ActionsConfigurationUtilities } from '../actions_config'; import { Logger } from '../../../../../../src/core/server'; -import { getActionType as getServerLogActionType } from './server_log'; -import { getActionType as getSlackActionType } from './slack'; import { getActionType as getEmailActionType } from './email'; import { getActionType as getIndexActionType } from './es_index'; import { getActionType as getPagerDutyActionType } from './pagerduty'; +import { getActionType as getServerLogActionType } from './server_log'; +import { getActionType as getServiceNowActionType } from './servicenow'; +import { getActionType as getSlackActionType } from './slack'; import { getActionType as getWebhookActionType } from './webhook'; export function registerBuiltInActionTypes({ - logger, - actionTypeRegistry, actionsConfigUtils: configurationUtilities, + actionTypeRegistry, + logger, }: { - logger: Logger; - actionTypeRegistry: ActionTypeRegistry; actionsConfigUtils: ActionsConfigurationUtilities; + actionTypeRegistry: ActionTypeRegistry; + logger: Logger; }) { - actionTypeRegistry.register(getServerLogActionType({ logger })); - actionTypeRegistry.register(getSlackActionType({ configurationUtilities })); actionTypeRegistry.register(getEmailActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getIndexActionType({ logger })); actionTypeRegistry.register(getPagerDutyActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getServerLogActionType({ logger })); + actionTypeRegistry.register(getServiceNowActionType({ configurationUtilities })); + actionTypeRegistry.register(getSlackActionType({ configurationUtilities })); actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); } diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts new file mode 100644 index 0000000000000..cfd3a9d70dc93 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/lib/post_servicenow.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import axios, { AxiosResponse } from 'axios'; +import { Services } from '../../types'; +import { ParamsType, SecretsType } from '../servicenow'; + +interface PostServiceNowOptions { + apiUrl: string; + data: ParamsType; + headers: Record; + services?: Services; + secrets: SecretsType; +} + +// post an event to serviceNow +export async function postServiceNow(options: PostServiceNowOptions): Promise { + const { apiUrl, data, headers, secrets } = options; + const axiosOptions = { + headers, + validateStatus: () => true, + auth: secrets, + }; + return axios.post(`${apiUrl}/api/now/v1/table/incident`, data, axiosOptions); +} diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts new file mode 100644 index 0000000000000..a445c6afde4d5 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.test.ts @@ -0,0 +1,279 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('./lib/post_servicenow', () => ({ + postServiceNow: jest.fn(), +})); + +import { getActionType } from './servicenow'; +import { ActionType, Services, ActionTypeExecutorOptions } from '../types'; +import { validateConfig, validateSecrets, validateParams } from '../lib'; +import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; +import { postServiceNow } from './lib/post_servicenow'; +import { createActionTypeRegistry } from './index.test'; +import { configUtilsMock } from '../actions_config.mock'; + +const postServiceNowMock = postServiceNow as jest.Mock; + +const ACTION_TYPE_ID = '.servicenow'; + +const services: Services = { + callCluster: async (path: string, opts: any) => {}, + savedObjectsClient: savedObjectsClientMock.create(), +}; + +let actionType: ActionType; + +const mockServiceNow = { + config: { + apiUrl: 'www.servicenowisinkibanaactions.com', + }, + secrets: { + password: 'secret-password', + username: 'secret-username', + }, + params: { + comments: 'hello cool service now incident', + short_description: 'this is a cool service now incident', + }, +}; + +beforeAll(() => { + const { actionTypeRegistry } = createActionTypeRegistry(); + actionType = actionTypeRegistry.get(ACTION_TYPE_ID); +}); + +describe('get()', () => { + test('should return correct action type', () => { + expect(actionType.id).toEqual(ACTION_TYPE_ID); + expect(actionType.name).toEqual('servicenow'); + }); +}); + +describe('validateConfig()', () => { + test('should validate and pass when config is valid', () => { + const { config } = mockServiceNow; + expect(validateConfig(actionType, config)).toEqual(config); + }); + + test('should validate and throw error when config is invalid', () => { + expect(() => { + validateConfig(actionType, { shouldNotBeHere: true }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: [apiUrl]: expected value of type [string] but got [undefined]"` + ); + }); + + test('should validate and pass when the servicenow url is whitelisted', () => { + actionType = getActionType({ + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: url => { + expect(url).toEqual('https://events.servicenow.com/v2/enqueue'); + }, + }, + }); + + expect( + validateConfig(actionType, { apiUrl: 'https://events.servicenow.com/v2/enqueue' }) + ).toEqual({ apiUrl: 'https://events.servicenow.com/v2/enqueue' }); + }); + + test('config validation returns an error if the specified URL isnt whitelisted', () => { + actionType = getActionType({ + configurationUtilities: { + ...configUtilsMock, + ensureWhitelistedUri: _ => { + throw new Error(`target url is not whitelisted`); + }, + }, + }); + + expect(() => { + validateConfig(actionType, { apiUrl: 'https://events.servicenow.com/v2/enqueue' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type config: error configuring servicenow action: target url is not whitelisted"` + ); + }); +}); + +describe('validateSecrets()', () => { + test('should validate and pass when secrets is valid', () => { + const { secrets } = mockServiceNow; + expect(validateSecrets(actionType, secrets)).toEqual(secrets); + }); + + test('should validate and throw error when secrets is invalid', () => { + expect(() => { + validateSecrets(actionType, { username: false }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [password]: expected value of type [string] but got [undefined]"` + ); + + expect(() => { + validateSecrets(actionType, { username: false, password: 'hello' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action type secrets: [username]: expected value of type [string] but got [boolean]"` + ); + }); +}); + +describe('validateParams()', () => { + test('should validate and pass when params is valid', () => { + const { params } = mockServiceNow; + expect(validateParams(actionType, params)).toEqual(params); + }); + + test('should validate and throw error when params is invalid', () => { + expect(() => { + validateParams(actionType, { eventAction: 'ackynollage' }); + }).toThrowErrorMatchingInlineSnapshot( + `"error validating action params: [short_description]: expected value of type [string] but got [undefined]"` + ); + }); +}); + +describe('execute()', () => { + beforeEach(() => { + postServiceNowMock.mockReset(); + }); + const { config, params, secrets } = mockServiceNow; + test('should succeed with valid params', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 201, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + const { apiUrl, data, headers } = postServiceNowMock.mock.calls[0][0]; + expect({ apiUrl, data, headers, secrets }).toMatchInlineSnapshot(` + Object { + "apiUrl": "www.servicenowisinkibanaactions.com", + "data": Object { + "comments": "hello cool service now incident", + "short_description": "this is a cool service now incident", + }, + "headers": Object { + "Accept": "application/json", + "Content-Type": "application/json", + }, + "secrets": Object { + "password": "secret-password", + "username": "secret-username", + }, + } + `); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "data": "data-here", + "status": "ok", + } + `); + }); + + test('should fail when postServiceNow throws', async () => { + postServiceNowMock.mockImplementation(() => { + throw new Error('doing some testing'); + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event", + "serviceMessage": "doing some testing", + "status": "error", + } + `); + }); + + test('should fail when postServiceNow returns 429', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 429, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event: http status 429, retry later", + "retry": true, + "status": "error", + } + `); + }); + + test('should fail when postServiceNow returns 501', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 501, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event: http status 501, retry later", + "retry": true, + "status": "error", + } + `); + }); + + test('should fail when postServiceNow returns 418', async () => { + postServiceNowMock.mockImplementation(() => { + return { status: 418, data: 'data-here' }; + }); + + const actionId = 'some-action-id'; + const executorOptions: ActionTypeExecutorOptions = { + actionId, + config, + params, + secrets, + services, + }; + const actionResponse = await actionType.executor(executorOptions); + expect(actionResponse).toMatchInlineSnapshot(` + Object { + "actionId": "some-action-id", + "message": "error posting servicenow event: unexpected status 418", + "status": "error", + } + `); + }); +}); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts new file mode 100644 index 0000000000000..2d5c18207def3 --- /dev/null +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/servicenow.ts @@ -0,0 +1,169 @@ +/* + * 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 { curry } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { + ActionType, + ActionTypeExecutorOptions, + ActionTypeExecutorResult, + ExecutorType, +} from '../types'; +import { ActionsConfigurationUtilities } from '../actions_config'; +import { postServiceNow } from './lib/post_servicenow'; + +// config definition +export type ConfigType = TypeOf; + +const ConfigSchemaProps = { + apiUrl: schema.string(), +}; + +const ConfigSchema = schema.object(ConfigSchemaProps); + +function validateConfig( + configurationUtilities: ActionsConfigurationUtilities, + configObject: ConfigType +) { + if (configObject.apiUrl == null) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiNullError', { + defaultMessage: 'ServiceNow [apiUrl] is required', + }); + } + try { + configurationUtilities.ensureWhitelistedUri(configObject.apiUrl); + } catch (whitelistError) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiWhitelistError', { + defaultMessage: 'error configuring servicenow action: {message}', + values: { + message: whitelistError.message, + }, + }); + } +} +// secrets definition +export type SecretsType = TypeOf; +const SecretsSchemaProps = { + password: schema.string(), + username: schema.string(), +}; + +const SecretsSchema = schema.object(SecretsSchemaProps); + +function validateSecrets( + configurationUtilities: ActionsConfigurationUtilities, + secrets: SecretsType +) { + if (secrets.username == null) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiUserError', { + defaultMessage: 'error configuring servicenow action: no secrets [username] provided', + }); + } + if (secrets.password == null) { + return i18n.translate('xpack.actions.builtin.servicenow.servicenowApiPasswordError', { + defaultMessage: 'error configuring servicenow action: no secrets [password] provided', + }); + } +} + +// params definition + +export type ParamsType = TypeOf; + +const ParamsSchema = schema.object({ + comments: schema.maybe(schema.string()), + short_description: schema.string(), +}); + +// action type definition +export function getActionType({ + configurationUtilities, + executor = serviceNowExecutor, +}: { + configurationUtilities: ActionsConfigurationUtilities; + executor?: ExecutorType; +}): ActionType { + return { + id: '.servicenow', + name: 'servicenow', + validate: { + config: schema.object(ConfigSchemaProps, { + validate: curry(validateConfig)(configurationUtilities), + }), + secrets: schema.object(SecretsSchemaProps, { + validate: curry(validateSecrets)(configurationUtilities), + }), + params: ParamsSchema, + }, + executor, + }; +} + +// action executor + +async function serviceNowExecutor( + execOptions: ActionTypeExecutorOptions +): Promise { + const actionId = execOptions.actionId; + const config = execOptions.config as ConfigType; + const secrets = execOptions.secrets as SecretsType; + const params = execOptions.params as ParamsType; + const headers = { + Accept: 'application/json', + 'Content-Type': 'application/json', + }; + let response; + try { + response = await postServiceNow({ apiUrl: config.apiUrl, data: params, headers, secrets }); + } catch (err) { + const message = i18n.translate('xpack.actions.builtin.servicenow.postingErrorMessage', { + defaultMessage: 'error posting servicenow event', + }); + return { + status: 'error', + actionId, + message, + serviceMessage: err.message, + }; + } + if (response.status === 200 || response.status === 201 || response.status === 204) { + return { + status: 'ok', + actionId, + data: response.data, + }; + } + + if (response.status === 429 || response.status >= 500) { + const message = i18n.translate('xpack.actions.builtin.servicenow.postingRetryErrorMessage', { + defaultMessage: 'error posting servicenow event: http status {status}, retry later', + values: { + status: response.status, + }, + }); + + return { + status: 'error', + actionId, + message, + retry: true, + }; + } + + const message = i18n.translate('xpack.actions.builtin.servicenow.postingUnexpectedErrorMessage', { + defaultMessage: 'error posting servicenow event: unexpected status {status}', + values: { + status: response.status, + }, + }); + + return { + status: 'error', + actionId, + message, + }; +} diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index 30d34bd3b436d..d5e9dcb76caa4 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/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 2af66059d9fed..9b8cfe9b69ed0 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -883,7 +883,6 @@ describe('enable()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - scheduledTaskId: 'task-123', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -892,6 +891,9 @@ describe('enable()', () => { version: '123', } ); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { @@ -964,7 +966,6 @@ describe('enable()', () => { schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, - scheduledTaskId: 'task-123', apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', @@ -973,6 +974,9 @@ describe('enable()', () => { version: '123', } ); + expect(savedObjectsClient.update).toHaveBeenCalledWith('alert', '1', { + scheduledTaskId: 'task-123', + }); expect(taskManager.schedule).toHaveBeenCalledWith({ taskType: `alerting:2`, params: { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index fe96a233b8663..7801e8f478712 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -362,7 +362,6 @@ export class AlertsClient { }); if (attributes.enabled === false) { - const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); const username = await this.getUserName(); await this.savedObjectsClient.update( 'alert', @@ -372,11 +371,11 @@ export class AlertsClient { enabled: true, ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), updatedBy: username, - - scheduledTaskId: scheduledTask.id, }, { version } ); + const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); + await this.savedObjectsClient.update('alert', id, { scheduledTaskId: scheduledTask.id }); await this.invalidateApiKey({ apiKey: attributes.apiKey }); } } 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 bc020815cc9cb..d69fa5d895b9e 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/PlatinumLicensePrompt.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx new file mode 100644 index 0000000000000..80281c1a0a8fc --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.stories.tsx @@ -0,0 +1,33 @@ +/* + * 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 { storiesOf } from '@storybook/react'; +import React from 'react'; +import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; +import { + ApmPluginContext, + ApmPluginContextValue +} from '../../../context/ApmPluginContext'; + +storiesOf('app/ServiceMap/PlatinumLicensePrompt', module).add( + 'example', + () => { + const contextMock = ({ + core: { http: { basePath: { prepend: () => {} } } } + } as unknown) as ApmPluginContextValue; + + return ( + + + + ); + }, + { + info: { + source: false + } + } +); 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 0000000000000..a8c45c83a382a --- /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 0000000000000..1c5443e404f9b --- /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 0000000000000..8ce6d9d57c4ac --- /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 0000000000000..dfb78aaa0214c --- /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 d4e792ccf761b..1a6247388a655 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 c9caa27af41c5..106e9a1d82f29 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 d5cfb49e458c6..722f64c6a7e58 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 0000000000000..b0648d14f20ba --- /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 d3cc2b14e2c68..a8e6f964f4d0c 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 9b2a2c8f2490a..0ddf23cb932fb 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 c95855c117047..5b1b9be33c375 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 @@ -809,7 +809,7 @@ Object { }, "serviceColors": Object { "opbeans-node": "#6092c0", - "opbeans-ruby": "#5bbaa0", + "opbeans-ruby": "#54b399", }, } `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 67bff86c8ccdf..32432b7b85ef6 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -18,8 +18,7 @@ import { history } from '../../../utils/history'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; import { - AutocompleteProvider, - AutocompleteSuggestion, + autocomplete, esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; @@ -29,7 +28,7 @@ const Container = styled.div` `; interface State { - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; isLoadingSuggestions: boolean; } @@ -38,32 +37,6 @@ function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { return esKuery.toElasticsearchQuery(ast, indexPattern); } -function getSuggestions( - query: string, - selectionStart: number, - indexPattern: IIndexPattern, - boolFilter: unknown, - autocompleteProvider?: AutocompleteProvider -) { - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true - }; - - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter - }); - return getAutocompleteSuggestions({ - query, - selectionStart, - selectionEnd: selectionStart - }); -} - export function KueryBar() { const [state, setState] = useState({ suggestions: [], @@ -72,7 +45,6 @@ export function KueryBar() { const { urlParams } = useUrlParams(); const location = useLocation(); const { data } = useApmPluginContext().plugins; - const autocompleteProvider = data.autocomplete.getProvider('kuery'); let currentRequestCheck; @@ -100,16 +72,16 @@ export function KueryBar() { const currentRequest = uniqueId(); currentRequestCheck = currentRequest; - const boolFilter = getBoolFilter(urlParams); try { const suggestions = ( - await getSuggestions( - inputValue, + (await data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: getBoolFilter(urlParams), + query: inputValue, selectionStart, - indexPattern, - boolFilter, - autocompleteProvider - ) + selectionEnd: selectionStart + })) || [] ) .filter(suggestion => !startsWith(suggestion.text, 'span.')) .slice(0, 15); 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 1bf125c301644..4c7d21d968088 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 @@ -14,7 +14,7 @@ Array [ , }, Object { - "color": "#fae181", + "color": "#d6bf57", "disabled": undefined, "onClick": [Function], "text": @@ -22,7 +22,7 @@ Array [ , }, Object { - "color": "#f19f58", + "color": "#da8b45", "disabled": undefined, "onClick": [Function], "text": @@ -442,7 +442,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -463,9 +463,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -480,9 +480,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -497,9 +497,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -514,9 +514,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -531,9 +531,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -548,9 +548,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -565,9 +565,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -582,9 +582,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -599,9 +599,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -616,9 +616,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -633,9 +633,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -650,9 +650,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -667,9 +667,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -684,9 +684,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -701,9 +701,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -718,9 +718,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -735,9 +735,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -752,9 +752,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -769,9 +769,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -786,9 +786,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -803,9 +803,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -820,9 +820,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -837,9 +837,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -854,9 +854,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -871,9 +871,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -888,9 +888,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -905,9 +905,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -922,9 +922,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -939,9 +939,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -956,9 +956,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -973,9 +973,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -995,7 +995,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -1016,9 +1016,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1033,9 +1033,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1050,9 +1050,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1067,9 +1067,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1084,9 +1084,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1101,9 +1101,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1118,9 +1118,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1135,9 +1135,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1152,9 +1152,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1169,9 +1169,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1186,9 +1186,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1203,9 +1203,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1220,9 +1220,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1237,9 +1237,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1254,9 +1254,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1271,9 +1271,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1288,9 +1288,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1305,9 +1305,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1322,9 +1322,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1339,9 +1339,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1356,9 +1356,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1373,9 +1373,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1390,9 +1390,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1407,9 +1407,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1424,9 +1424,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1441,9 +1441,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1458,9 +1458,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1475,9 +1475,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1492,9 +1492,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1509,9 +1509,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1526,9 +1526,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -2659,7 +2659,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #fae181; + background: #d6bf57; border-radius: 100%; } @@ -2667,7 +2667,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #f19f58; + background: #da8b45; border-radius: 100%; } @@ -2764,14 +2764,14 @@ Array [ onClick={[Function]} > @@ -2798,14 +2798,14 @@ Array [ onClick={[Function]} > @@ -2854,12 +2854,12 @@ Array [ "value": 438704.4, }, Object { - "color": "#fae181", + "color": "#d6bf57", "text": "95th", "value": 1557383.999999999, }, Object { - "color": "#f19f58", + "color": "#da8b45", "text": "99th", "value": 1820377.1200000006, }, @@ -2899,7 +2899,7 @@ Array [ width: 8px; height: 8px; margin-right: 4px; - background: #fae181; + background: #d6bf57; border-radius: 100%; } @@ -2907,7 +2907,7 @@ Array [ width: 8px; height: 8px; margin-right: 4px; - background: #f19f58; + background: #da8b45; border-radius: 100%; } @@ -3378,7 +3378,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -3399,9 +3399,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3416,9 +3416,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3433,9 +3433,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3450,9 +3450,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3467,9 +3467,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3484,9 +3484,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3501,9 +3501,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3518,9 +3518,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3535,9 +3535,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3552,9 +3552,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3569,9 +3569,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3586,9 +3586,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3603,9 +3603,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3620,9 +3620,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3637,9 +3637,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3654,9 +3654,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3671,9 +3671,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3688,9 +3688,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3705,9 +3705,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3722,9 +3722,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3739,9 +3739,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3756,9 +3756,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3773,9 +3773,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3790,9 +3790,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3807,9 +3807,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3824,9 +3824,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3841,9 +3841,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3858,9 +3858,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3875,9 +3875,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3892,9 +3892,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3909,9 +3909,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3931,7 +3931,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -3952,9 +3952,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -3969,9 +3969,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -3986,9 +3986,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4003,9 +4003,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4020,9 +4020,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4037,9 +4037,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4054,9 +4054,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4071,9 +4071,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4088,9 +4088,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4105,9 +4105,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4122,9 +4122,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4139,9 +4139,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4156,9 +4156,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4173,9 +4173,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4190,9 +4190,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4207,9 +4207,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4224,9 +4224,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4241,9 +4241,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4258,9 +4258,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4275,9 +4275,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4292,9 +4292,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4309,9 +4309,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4326,9 +4326,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4343,9 +4343,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4360,9 +4360,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4377,9 +4377,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4394,9 +4394,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4411,9 +4411,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4428,9 +4428,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4445,9 +4445,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4462,9 +4462,9 @@ Array [ r={0.5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -5072,9 +5072,9 @@ Array [ r={5} style={ Object { - "fill": "#f19f58", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f19f58", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -5089,9 +5089,9 @@ Array [ r={5} style={ Object { - "fill": "#fae181", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#fae181", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -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" > @@ -5838,7 +5838,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #fae181; + background: #d6bf57; border-radius: 100%; } @@ -5846,7 +5846,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #f19f58; + background: #da8b45; border-radius: 100%; } @@ -5943,14 +5943,14 @@ Array [ onClick={[Function]} > @@ -5977,14 +5977,14 @@ Array [ onClick={[Function]} > 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 6e98d0784bee1..f12865399054e 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 216af91fbb591..de6cbc7d7a335 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 1218bc726c3b7..252c49cc09fb9 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 @@ -67,7 +67,7 @@ describe('chartSelectors', () => { type: 'linemark' }, { - color: '#fae181', + color: '#d6bf57', data: [ { x: 0, y: 200 }, { x: 1000, y: 300 } @@ -77,7 +77,7 @@ describe('chartSelectors', () => { type: 'linemark' }, { - color: '#f19f58', + color: '#da8b45', data: [ { x: 0, y: 300 }, { x: 1000, y: 400 } diff --git a/x-pack/legacy/plugins/apm/readme.md b/x-pack/legacy/plugins/apm/readme.md index 6b21f08b7695e..2106243d12aea 100644 --- a/x-pack/legacy/plugins/apm/readme.md +++ b/x-pack/legacy/plugins/apm/readme.md @@ -40,7 +40,6 @@ For testing purposes APM uses 3 custom users: **kibana_write_user** Apps: read/write. Indices: None - To create the users with the correct roles run the following script: ```sh @@ -88,6 +87,12 @@ yarn prettier "./x-pack/legacy/plugins/apm/**/*.{tsx,ts,js}" --write yarn eslint ./x-pack/legacy/plugins/apm --fix ``` +#### Storybook + +Start the [Storybook](https://storybook.js.org/) development environment with +`yarn storybook apm`. All files with a .stories.tsx extension will be loaded. +You can access the development environment at http://localhost:9001. + #### Further resources - [Cypress integration tests](cypress/README.md) diff --git a/x-pack/legacy/plugins/apm/scripts/storybook.js b/x-pack/legacy/plugins/apm/scripts/storybook.js new file mode 100644 index 0000000000000..846ee1f301e30 --- /dev/null +++ b/x-pack/legacy/plugins/apm/scripts/storybook.js @@ -0,0 +1,15 @@ +/* + * 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 { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'apm', + storyGlobs: [ + join(__dirname, '..', 'public', 'components', '**', '*.stories.tsx') + ] +}); 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 8c6ed2ebcec75..870660c429ca3 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 ea9af12ac7f9a..d3711e9582d15 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 0000000000000..6c4d540103cec --- /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 acd5dc119b737..bbf2a6882c3c7 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 8e578a839ae56..2f44b9231eae2 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 18ce29982b616..580cafff95e0c 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 e33255b5baa55..58fbe664cccab 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 2d8b16e95f769..66b805ab2efc1 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 b08bdc334fc87..a4885f2884976 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 2632cc6e94b93..bc61f1cab149e 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 6acd34af24353..709fa3afdc128 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 58a952baa8233..0a03a88cbf4a2 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 870b02fa7ba6d..476928a5bcb63 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,7 +70,7 @@ describe('getTransactionBreakdown', () => { expect(response.kpis[0]).toEqual({ name: 'app', - color: '#5bbaa0', + color: '#54b399', percentage: 0.5408550899466306 }); 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 a9a8241da39d1..cf27d20c24360 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 94b176147f7a1..584598805f8b3 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/beats_management/public/components/autocomplete_field/index.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx index 1f49711ae5e4a..f3e0f3dfbdae7 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/index.tsx @@ -13,7 +13,7 @@ import { import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { composeStateUpdaters } from '../../utils/typed_react'; import { SuggestionItem } from './suggestion_item'; @@ -25,7 +25,7 @@ interface AutocompleteFieldProps { onSubmit?: (value: string) => void; onChange?: (value: string) => void; placeholder?: string; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx index a753a944a2ecb..0132667b9e510 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/autocomplete_field/suggestion_item.tsx @@ -9,13 +9,13 @@ import { tint } from 'polished'; import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; interface SuggestionItemProps { isSelected?: boolean; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; - suggestion: AutocompleteSuggestion; + suggestion: autocomplete.QuerySuggestion; } export const SuggestionItem: React.FC = props => { diff --git a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx index 26ddd682405cb..d1cbc0888dca8 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/table/table.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eu import { i18n } from '@kbn/i18n'; import React from 'react'; import styled from 'styled-components'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; import { TABLE_CONFIG } from '../../../common/constants'; import { AutocompleteField } from '../autocomplete_field/index'; import { ControlSchema } from './action_schema'; @@ -31,7 +31,7 @@ export interface KueryBarProps { loadSuggestions: (value: string, cursorPosition: number, maxCount?: number) => void; onChange?: (value: string) => void; onSubmit?: (value: string) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; value: string; } diff --git a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx index 2ac20438536c8..db73a7cb38c11 100644 --- a/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx +++ b/x-pack/legacy/plugins/beats_management/public/containers/with_kuery_autocompletion.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../src/plugins/data/public'; import { FrontendLibs } from '../lib/types'; import { RendererFunction } from '../utils/typed_react'; @@ -17,7 +17,7 @@ interface WithKueryAutocompletionLifecycleProps { children: RendererFunction<{ isLoadingSuggestions: boolean; loadSuggestions: (expression: string, cursorPosition: number, maxSuggestions?: number) => void; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; }>; } @@ -28,7 +28,7 @@ interface WithKueryAutocompletionLifecycleState { expression: string; cursorPosition: number; } | null; - suggestions: AutocompleteSuggestion[]; + suggestions: autocomplete.QuerySuggestion[]; } export class WithKueryAutocompletion extends React.Component< diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts index 4f4ce70e817c6..12898027d5fb5 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/adapter_types.ts @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; export interface ElasticsearchAdapter { convertKueryToEsQuery: (kuery: string) => Promise; - getSuggestions: (kuery: string, selectionStart: any) => Promise; + getSuggestions: (kuery: string, selectionStart: any) => Promise; isKueryValid(kuery: string): boolean; } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts index e001bf6c6e844..111255b55c99b 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/memory.ts @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapter_types'; export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { constructor( private readonly mockIsKueryValid: (kuery: string) => boolean, private readonly mockKueryToEsQuery: (kuery: string) => string, - private readonly suggestions: AutocompleteSuggestion[] + private readonly suggestions: autocomplete.QuerySuggestion[] ) {} public isKueryValid(kuery: string): boolean { @@ -23,7 +23,7 @@ export class MemoryElasticsearchAdapter implements ElasticsearchAdapter { public async getSuggestions( kuery: string, selectionStart: any - ): Promise { + ): Promise { return this.suggestions; } } diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index 8771181639f4d..fc400c600e575 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -7,10 +7,7 @@ import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; -import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; - -const getAutocompleteProvider = (language: string) => - npStart.plugins.data.autocomplete.getProvider(language); +import { autocomplete, esKuery } from '../../../../../../../../src/plugins/data/public'; export class RestElasticsearchAdapter implements ElasticsearchAdapter { private cachedIndexPattern: any = null; @@ -33,30 +30,23 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { const indexPattern = await this.getIndexPattern(); return JSON.stringify(esKuery.toElasticsearchQuery(ast, indexPattern)); } + public async getSuggestions( kuery: string, selectionStart: any - ): Promise { - const autocompleteProvider = getAutocompleteProvider('kuery'); - if (!autocompleteProvider) { - return []; - } - const config = { - get: () => true, - }; + ): Promise { const indexPattern = await this.getIndexPattern(); - const getAutocompleteSuggestions = autocompleteProvider({ - config, - indexPatterns: [indexPattern], - boolFilter: null, - }); - const results = getAutocompleteSuggestions({ - query: kuery || '', - selectionStart, - selectionEnd: selectionStart, - }); - return results; + return ( + (await npStart.plugins.data.autocomplete.getQuerySuggestions({ + language: 'kuery', + indexPatterns: [indexPattern], + boolFilter: [], + query: kuery || '', + selectionStart, + selectionEnd: selectionStart, + })) || [] + ); } private async getIndexPattern() { diff --git a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts index f357e698afc3a..47df51dea8620 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/compose/memory.ts @@ -24,14 +24,14 @@ import { TagsLib } from '../tags'; import { FrontendLibs } from '../types'; import { MemoryElasticsearchAdapter } from './../adapters/elasticsearch/memory'; import { ElasticsearchLib } from './../elasticsearch'; -import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; const onKibanaReady = uiModules.get('kibana').run; export function compose( mockIsKueryValid: (kuery: string) => boolean, mockKueryToEsQuery: (kuery: string) => string, - suggestions: AutocompleteSuggestion[] + suggestions: autocomplete.QuerySuggestion[] ): FrontendLibs { const esAdapter = new MemoryElasticsearchAdapter( mockIsKueryValid, diff --git a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts index 0897dfd9c1392..d71512e80d3d5 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/elasticsearch.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AutocompleteSuggestion } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../src/plugins/data/public'; import { ElasticsearchAdapter } from './adapters/elasticsearch/adapter_types'; interface HiddenFields { @@ -35,7 +35,7 @@ export class ElasticsearchLib { kuery: string, selectionStart: any, fieldPrefix?: string - ): Promise { + ): Promise { const suggestions = await this.adapter.getSuggestions(kuery, selectionStart); const filteredSuggestions = suggestions.filter(suggestion => { diff --git a/x-pack/legacy/plugins/canvas/.storybook/config.js b/x-pack/legacy/plugins/canvas/.storybook/config.js index d3cd8e8057ae8..725a3b12666d1 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/config.js @@ -10,7 +10,6 @@ import { withKnobs } from '@storybook/addon-knobs/react'; import { withInfo } from '@storybook/addon-info'; import { create } from '@storybook/theming'; -import { coreMock } from 'src/core/public/mocks'; import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; // If we're running Storyshots, be sure to register the require context hook. @@ -38,9 +37,12 @@ if (process.env.NODE_ENV === 'test') { } // Add New Platform Context for any stories that need it -addDecorator(fn => ( - {fn()} -)); +const settings = new Map(); +settings.set('darkMode', true); +const platform = { + uiSettings: settings, +}; +addDecorator(fn => {fn()}); function loadStories() { require('./dll_contexts'); @@ -55,7 +57,7 @@ function loadStories() { css.keys().forEach(filename => css(filename)); // Find all files ending in *.examples.ts - const req = require.context('./..', true, /.examples.tsx$/); + const req = require.context('./..', true, /.(stories|examples).tsx$/); req.keys().forEach(filename => req(filename)); } diff --git a/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js b/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js index 66ef1a192d403..273323d065ecf 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js +++ b/x-pack/legacy/plugins/canvas/.storybook/dll_contexts.js @@ -26,3 +26,10 @@ const uiStyles = require.context( /[\/\\](?!mixins|variables|_|\.|bootstrap_(light|dark))[^\/\\]+\.less/ ); uiStyles.keys().forEach(key => uiStyles(key)); + +const json = require.context( + '../shareable_runtime/test/workpads', + false, + /\.json$/ +); +json.keys().forEach(key => json(key)); diff --git a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js index b7924486c34cb..76240d212da15 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js +++ b/x-pack/legacy/plugins/canvas/.storybook/storyshots.test.js @@ -52,7 +52,7 @@ jest.mock('plugins/interpreter/registries', () => ({})); // Disabling this test due to https://github.com/elastic/eui/issues/2242 jest.mock( - '../public/components/workpad_header/workpad_export/__examples__/disabled_panel.examples', + '../public/components/workpad_header/workpad_export/__examples__/disabled_panel.stories', () => { return 'Disabled Panel'; } @@ -60,7 +60,7 @@ jest.mock( // Disabling this test due to https://github.com/elastic/eui/issues/2242 jest.mock( - '../public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.examples', + '../public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories', () => { return 'Disabled Panel'; } diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index 662078585422f..019194716b230 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -47,15 +47,18 @@ module.exports = async ({ config }) => { }); // Parse props data for .tsx files - config.module.rules.push({ - test: /\.tsx$/, - // Exclude example files, as we don't display props info for them - exclude: /\.examples.tsx$/, - use: [ - // Parse TS comments to create Props tables in the UI - require.resolve('react-docgen-typescript-loader'), - ], - }); + // This is notoriously slow, and is making Storybook unusable. Disabling for now. + // See: https://github.com/storybookjs/storybook/issues/7998 + // + // config.module.rules.push({ + // test: /\.tsx$/, + // // Exclude example files, as we don't display props info for them + // exclude: /\.examples.tsx$/, + // use: [ + // // Parse TS comments to create Props tables in the UI + // require.resolve('react-docgen-typescript-loader'), + // ], + // }); // Enable SASS, but exclude CSS Modules in Storybook config.module.rules.push({ diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js index 12f2195c067ca..0a648e861b386 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.dll.config.js @@ -24,9 +24,7 @@ module.exports = { entry: [ '@elastic/eui/dist/eui_theme_light.css', '@kbn/ui-framework/dist/kui_light.css', - '@storybook/addon-actions', '@storybook/addon-actions/register', - '@storybook/addon-info', '@storybook/addon-knobs', '@storybook/addon-knobs/react', '@storybook/addon-knobs/register', diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index 8f5ad859d28ba..cc92d864282e7 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -6,7 +6,7 @@ // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize_embeddable/constants'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/visualizations/public/embeddable/constants'; import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/np_ready/embeddable/constants'; export const EmbeddableTypes: { map: string; search: string; visualization: string } = { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts index f6eb377e2698b..d3b1bbe31c715 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts @@ -5,7 +5,7 @@ */ import { ExpressionFunction } from 'src/plugins/expressions/common/types'; -import { VisualizeInput } from 'src/legacy/core_plugins/kibana/public/visualize_embeddable'; +import { VisualizeInput } from 'src/legacy/core_plugins/visualizations/public/embeddable'; import { EmbeddableTypes, EmbeddableExpressionType, diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/__snapshots__/metric.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/metric/component/__examples__/metric.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/__snapshots__/datetime_calendar.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_calendar/__examples__/datetime_calendar.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/__snapshots__/datetime_input.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_input/__examples__/datetime_input.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/__snapshots__/datetime_quick_list.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/__examples__/datetime_quick_list.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx index fc4603b5be6e5..9d5a3893dbcb8 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_quick_list/datetime_quick_list.tsx @@ -8,7 +8,7 @@ import React, { ReactNode, FunctionComponent } from 'react'; import PropTypes from 'prop-types'; import { EuiButton, EuiButtonEmpty } from '@elastic/eui'; import 'react-datetime/css/react-datetime.css'; -import { UnitStrings } from '../../../../../i18n'; +import { UnitStrings } from '../../../../../i18n/units'; const { quickRanges: strings } = UnitStrings; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/__snapshots__/datetime_range_absolute.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/datetime_range_absolute/__examples__/datetime_range_absolute.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/__snapshots__/pretty_duration.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/__examples__/pretty_duration.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts index f52465fd3da70..1c436d3630b53 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/pretty_duration/lib/quick_ranges.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UnitStrings } from '../../../../../../i18n'; +import { UnitStrings } from '../../../../../../i18n/units'; export interface QuickRange { /** Start date string of range */ diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/__snapshots__/time_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/__examples__/time_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx index a03e64ce3a02e..599b15524ddda 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx @@ -11,7 +11,7 @@ import { EuiButton } from '@elastic/eui'; import moment from 'moment'; import { DatetimeQuickList } from '../datetime_quick_list'; import { DatetimeRangeAbsolute } from '../datetime_range_absolute'; -import { ComponentStrings } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; const { TimePicker: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx index 1dafd7ba648c3..8f6061b688319 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker_popover/time_picker_popover.tsx @@ -42,7 +42,16 @@ export const TimePickerPopover: FunctionComponent = ({ from, to, onSelect anchorClassName="canvasTimePickerPopover__anchor" button={button} > - {() => } + {({ closePopover }) => ( + { + onSelect(...args); + closePopover(); + }} + /> + )} ); }; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/extended_template.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/__snapshots__/simple_template.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/extended_template.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/__examples__/simple_template.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx index 0f01860520b50..806a61042494f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/axis_config/extended_template.tsx @@ -10,7 +10,7 @@ import { EuiSelect, EuiFormRow, EuiSpacer, EuiText } from '@elastic/eui'; import immutable from 'object-path-immutable'; import { get } from 'lodash'; import { ExpressionAST } from '../../../../types'; -import { ArgumentStrings } from '../../../../i18n'; +import { ArgumentStrings } from '../../../../i18n/ui'; const { AxisConfig: strings } = ArgumentStrings; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/__snapshots__/date_format.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/__examples__/date_format.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.examples.storyshot rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/__snapshots__/number_format.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.examples.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.examples.tsx rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/__examples__/number_format.stories.tsx 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 aca30780d77cd..4c535a42c3c44 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 d3d251026e9b0..5249856dec271 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 325a531b219ee..36d66801ef681 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 ac6447ffd9dc0..6a59a6795d45a 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 e538b4bf53103..4d37ecfaa367a 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 5df30581cd070..8dfbe1cb21614 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/common/lib/constants.ts b/x-pack/legacy/plugins/canvas/common/lib/constants.ts index 7494ea13e6c08..40e143b9ec589 100644 --- a/x-pack/legacy/plugins/canvas/common/lib/constants.ts +++ b/x-pack/legacy/plugins/canvas/common/lib/constants.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SHAREABLE_RUNTIME_NAME } from '../../shareable_runtime/constants'; +import { SHAREABLE_RUNTIME_NAME } from '../../shareable_runtime/constants_static'; export const CANVAS_TYPE = 'canvas-workpad'; export const CUSTOM_ELEMENT_TYPE = 'canvas-element'; diff --git a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx index 9cc6f870b9bde..9c66043454585 100644 --- a/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { MouseEvent } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { EuiButtonIcon } from '@elastic/eui'; // @ts-ignore untyped local @@ -28,7 +28,7 @@ interface Props { } export const ArgAddPopover = ({ options }: Props) => { - const button = (handleClick: (ev: MouseEvent) => void) => ( + const button = (handleClick: React.MouseEventHandler) => (
@@ -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 579470649582d..c1a2b0f0bf372 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 5b281129f533f..c8ab1323557bb 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_dot/__examples__/__snapshots__/color_dot.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/__snapshots__/color_dot.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/color_dot/__examples__/color_dot.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/__snapshots__/color_manager.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/color_manager/__examples__/color_manager.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/color_manager/color_manager.tsx b/x-pack/legacy/plugins/canvas/public/components/color_manager/color_manager.tsx index e9d20dfe07b63..c9db9a9ba3468 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_manager/color_manager.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/color_manager/color_manager.tsx @@ -10,7 +10,7 @@ import React, { FunctionComponent } from 'react'; import tinycolor from 'tinycolor2'; import { ColorDot } from '../color_dot/color_dot'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { ColorManager: strings } = ComponentStrings; 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.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/color_palette.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/__snapshots__/color_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/color_picker.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/color_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/color_picker/__examples__/color_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/__snapshots__/color_picker_popover.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/__snapshots__/color_picker_popover.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/__snapshots__/color_picker_popover.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/__snapshots__/color_picker_popover.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/color_picker_popover/__examples__/color_picker_popover.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx index 95d6144f3e3c3..9e8a6e88b649b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/color_picker_popover/color_picker_popover.tsx @@ -6,7 +6,7 @@ import { EuiLink, PopoverAnchorPosition } from '@elastic/eui'; import PropTypes from 'prop-types'; -import React, { FunctionComponent, MouseEvent } from 'react'; +import React, { FunctionComponent } from 'react'; import tinycolor from 'tinycolor2'; import { ColorDot } from '../color_dot'; import { ColorPicker, Props as ColorPickerProps } from '../color_picker'; @@ -19,7 +19,7 @@ export interface Props extends ColorPickerProps { export const ColorPickerPopover: FunctionComponent = (props: Props) => { const { value, anchorPosition, ariaLabel, ...rest } = props; - const button = (handleClick: (ev: MouseEvent) => void) => ( + const button = (handleClick: React.MouseEventHandler) => ( -
-
- - - -
-
- - - -
-
-
-
- -
-
- - - -
-
- - - -
-
-
-
- -
-
- - - -
-
- - - -
-
-
-
-`; - -exports[`Storyshots components/ElementTypes/ElementGrid with controls and filter 1`] = ` -
-
- -
-
- - - -
-
- - - -
-
-
-
-`; - -exports[`Storyshots components/ElementTypes/ElementGrid with filter 1`] = ` -
-
- -
-
-`; - -exports[`Storyshots components/ElementTypes/ElementGrid without controls 1`] = ` -
-
- -
-
- -
-
- -
-
-`; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/element_card/__examples__/element_card.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_controls.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.stories.storyshot similarity index 99% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.stories.storyshot index c9fb77061572d..3a52402400822 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/__snapshots__/element_grid.stories.storyshot @@ -525,7 +525,7 @@ exports[`Storyshots components/Elements/ElementGrid with tags filter 1`] = ` style={ Object { "backgroundColor": "#666666", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -605,7 +605,7 @@ exports[`Storyshots components/Elements/ElementGrid with text filter 1`] = ` style={ Object { "backgroundColor": "#666666", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -685,7 +685,7 @@ exports[`Storyshots components/Elements/ElementGrid without controls 1`] = ` style={ Object { "backgroundColor": "#666666", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -750,7 +750,7 @@ exports[`Storyshots components/Elements/ElementGrid without controls 1`] = ` style={ Object { "backgroundColor": "#666666", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -815,7 +815,7 @@ exports[`Storyshots components/Elements/ElementGrid without controls 1`] = ` style={ Object { "backgroundColor": "#666666", - "color": "#FFFFFF", + "color": "#fff", } } > diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_controls.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/element_types/__examples__/element_grid.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx index fd8836f5ac120..a23274296f64f 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/element_types/element_controls.tsx @@ -7,7 +7,7 @@ import React, { FunctionComponent, MouseEvent } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { ElementControls: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js index 1934eab85d034..60c7e731691fa 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js @@ -58,7 +58,9 @@ function selectorFactory(dispatch) { export const ElementWrapper = compose( connectAdvanced(selectorFactory), withPropsOnChange( - (props, nextProps) => !isEqual(props.element, nextProps.element), + (props, nextProps) => + !isEqual(props.element, nextProps.element) || + !isEqual(props.selectedPage, nextProps.selectedPage), props => { const { element, createHandlers } = props; const handlers = createHandlers(element, props.selectedPage); diff --git a/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/file_upload/__snapshots__/file_upload.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/file_upload/file_upload.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/font_picker/font_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/__snapshots__/item_grid.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/item_grid/__examples__/item_grid.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/__snapshots__/keyboard_shortcuts_doc.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/__examples__/keyboard_shortcuts_doc.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx index 27c951d1757f8..1c94969292b59 100644 --- a/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/keyboard_shortcuts_doc/keyboard_shortcuts_doc.tsx @@ -17,11 +17,11 @@ import { EuiTitle, } from '@elastic/eui'; import { keymap } from '../../lib/keymap'; -import { ShortcutMap, ShortcutNameSpace } from '../../../types'; +import { ShortcutMap, ShortcutNameSpace } from '../../../types/shortcuts'; import { getClientPlatform } from '../../lib/get_client_platform'; import { getId } from '../../lib/get_id'; import { getPrettyShortcut } from '../../lib/get_pretty_shortcut'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { KeyboardShortcutsDoc: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx b/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx index a5d7ce1109447..25b2e6587c869 100644 --- a/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/popover/popover.tsx @@ -5,25 +5,23 @@ */ /* eslint react/no-did-mount-set-state: 0, react/forbid-elements: 0 */ -import React, { ReactNode, ReactElement, Component, MouseEvent } from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { EuiToolTip, ToolTipPositions, EuiPopover, PropsOf } from '@elastic/eui'; +import { EuiPopover, EuiToolTip } from '@elastic/eui'; -export interface PopoverChildrenProps { - closePopover: () => void; -} - -type EuiPopoverProps = PropsOf; -interface Props extends Omit { +interface Props { + button: (handleClick: React.MouseEventHandler) => React.ReactElement; + tooltipPosition?: 'top' | 'bottom' | 'left' | 'right'; + children: (arg: { closePopover: () => void }) => React.ReactNode; isOpen?: boolean; ownFocus?: boolean; - - button: (handleClick: (ev: MouseEvent) => void) => ReactElement; - - children: (props: PopoverChildrenProps) => ReactNode; - - tooltip: string; - tooltipPosition: ToolTipPositions; + tooltip?: string; + panelClassName?: string; + anchorClassName?: string; + anchorPosition?: string; + panelPaddingSize?: 'none' | 's' | 'm' | 'l'; + id?: string; + className?: string; } interface State { @@ -47,8 +45,8 @@ export class Popover extends Component { tooltipPosition: 'top', }; - state = { - isPopoverOpen: false, + state: State = { + isPopoverOpen: !!this.props.isOpen, }; componentDidMount() { @@ -58,7 +56,7 @@ export class Popover extends Component { } handleClick = () => { - this.setState(state => ({ + this.setState((state: any) => ({ isPopoverOpen: !state.isPopoverOpen, })); }; @@ -72,9 +70,9 @@ export class Popover extends Component { render() { const { button, children, tooltip, tooltipPosition, ...rest } = this.props; - const wrappedButton = (handleClick: (ev: MouseEvent) => void) => { + const wrappedButton = (handleClick: any) => { // wrap button in tooltip, if tooltip text is provided - if (!this.state.isPopoverOpen && tooltip.length) { + if (!this.state.isPopoverOpen && tooltip && tooltip.length) { return ( {button(handleClick)} @@ -86,17 +84,18 @@ export class Popover extends Component { }; const appWrapper = document.querySelector('.app-wrapper'); + const EuiPopoverAny = (EuiPopover as any) as React.FC; return ( - {children({ closePopover: this.closePopover })} - + ); } } diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/__snapshots__/shape_picker.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/shape_picker/__examples__/shape_picker.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/__snapshots__/shape_picker_popover.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/__examples__/shape_picker_popover.stories.tsx 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 717ec6d0faecc..d42e08d2bc852 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 @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { MouseEvent } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { EuiLink, EuiPanel } from '@elastic/eui'; import { Popover } from '../popover'; @@ -21,7 +21,7 @@ interface Props { } export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props) => { - const button = (handleClick: (ev: MouseEvent) => void) => ( + const button = (handleClick: React.MouseEventHandler) => ( diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/__snapshots__/shape_preview.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/shape_preview/__examples__/shape_preview.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/group_settings.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/__snapshots__/multi_element_settings.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/group_settings.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/sidebar/__examples__/multi_element_settings.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx index 95d9035774a6a..e1ce539256daa 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/group_settings.tsx @@ -6,7 +6,7 @@ import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { GroupSettings: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx index 999c1c2daaf5b..cae5671bf901b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar/multi_element_settings.tsx @@ -6,7 +6,7 @@ import React, { FunctionComponent } from 'react'; import { EuiText } from '@elastic/eui'; -import { ComponentStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; const { MultiElementSettings: strings } = ComponentStrings; diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/__snapshots__/sidebar_header.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/sidebar_header/__examples__/sidebar_header.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx index 3a0e191b9f4b5..d4dec6cca064b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/sidebar_header/sidebar_header.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, Fragment, MouseEvent } from 'react'; +import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, @@ -20,7 +20,8 @@ import { import { Popover } from '../popover'; import { CustomElementModal } from '../custom_element_modal'; import { ToolTipShortcut } from '../tool_tip_shortcut/'; -import { ComponentStrings, ShortcutStrings } from '../../../i18n'; +import { ComponentStrings } from '../../../i18n/components'; +import { ShortcutStrings } from '../../../i18n/shortcuts'; const topBorderClassName = 'canvasContextMenu--topBorder'; @@ -138,7 +139,7 @@ interface MenuTuple { panel: EuiContextMenuPanelDescriptor; } -const contextMenuButton = (handleClick: (event: MouseEvent) => void) => ( +const contextMenuButton = (handleClick: React.MouseEventHandler) => ( @@ -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/__examples__/tag.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/tag.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/tag/__examples__/tag.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/tag/__examples__/tag.stories.tsx 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.stories.storyshot similarity index 94% rename from x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.stories.storyshot index 9dcf55642c66f..7671b0bfb4937 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.stories.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/tag_list/__examples__/tag_list.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/tag_list.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/tag_list.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/tag_list.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/__snapshots__/tool_tip_shortcut.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/__snapshots__/tool_tip_shortcut.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/__snapshots__/tool_tip_shortcut.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/__snapshots__/tool_tip_shortcut.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/tool_tip_shortcut/__examples__/tool_tip_shortcut.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.stories.storyshot similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.examples.storyshot rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/__snapshots__/pdf_panel.stories.storyshot diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/disabled_panel.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/disabled_panel.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/disabled_panel.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/disabled_panel.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/__examples__/pdf_panel.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.examples.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories.tsx similarity index 100% rename from x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.examples.tsx rename to x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/__examples__/share_website_flyout.stories.tsx diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts index 1cc56eabaebd5..2bf3e1f0ef1f4 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/index.ts @@ -29,7 +29,7 @@ import { fetch, arrayBufferFetch } from '../../../../../common/lib/fetch'; import { API_ROUTE_SHAREABLE_ZIP } from '../../../../../common/lib/constants'; import { renderFunctionNames } from '../../../../../shareable_runtime/supported_renderers'; -import { ComponentStrings } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public/'; import { OnCloseFn } from '../workpad_export'; import { WithKibanaProps } from '../../../../index'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/runtime_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/runtime_step.tsx index dafca394a5037..ea8aba688b2a6 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/runtime_step.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/runtime_step.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { EuiText, EuiSpacer, EuiButton } from '@elastic/eui'; -import { ComponentStrings } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; import { OnDownloadFn } from './share_website_flyout'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx index f5730ea2eab84..8dcbb18ffed86 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/share_website_flyout.tsx @@ -22,7 +22,8 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ComponentStrings, ZIP, CANVAS, HTML } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; +import { ZIP, CANVAS, HTML } from '../../../../../i18n/constants'; import { OnCloseFn } from '../workpad_export'; import { WorkpadStep } from './workpad_step'; import { RuntimeStep } from './runtime_step'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx index a65ec1ddba081..c19ad6d77b131 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/snippets_step.tsx @@ -16,7 +16,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; -import { ComponentStrings } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; import { Clipboard } from '../../../clipboard'; import { OnCopyFn } from './share_website_flyout'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/workpad_step.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/workpad_step.tsx index 6874090702b2d..1a5884d89d066 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/workpad_step.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/flyout/workpad_step.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { EuiText, EuiSpacer, EuiButton } from '@elastic/eui'; -import { ComponentStrings } from '../../../../../i18n'; +import { ComponentStrings } from '../../../../../i18n/components'; import { OnDownloadFn } from './share_website_flyout'; diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts index a8ae785adafc1..2b2a582fb4526 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/index.ts @@ -62,12 +62,8 @@ export const WorkpadExport = compose( enabled, getExportUrl: type => { if (type === 'pdf') { - const { createPdfUri } = getPdfUrl( - workpad, - { pageCount }, - kibana.services.http.basePath.prepend - ); - return getAbsoluteUrl(createPdfUri); + const pdfUrl = getPdfUrl(workpad, { pageCount }, kibana.services.http.basePath.prepend); + return getAbsoluteUrl(pdfUrl); } throw new Error(strings.getUnknownExportErrorMessage(type)); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx index ae85a6d89ca67..ef70079cf697b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/pdf_panel.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import { Clipboard } from '../../clipboard'; -import { ComponentStrings } from '../../../../i18n'; +import { ComponentStrings } from '../../../../i18n/components'; const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; interface Props { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts new file mode 100644 index 0000000000000..ceaf82c1c07d6 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../../../../common/lib/fetch'); + +import { getPdfUrl, createPdf } from './utils'; +import { workpads } from '../../../../__tests__/fixtures/workpads'; +import { fetch } from '../../../../common/lib/fetch'; + +const addBasePath = jest.fn().mockImplementation(s => `basepath/${s}`); +const workpad = workpads[0]; + +test('getPdfUrl returns the correct url', () => { + const url = getPdfUrl(workpad, { pageCount: 2 }, addBasePath); + + expect(url).toMatchInlineSnapshot( + `"basepath//api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FPhoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas%20workpad',relativeUrls:!(%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F1,%2Fapp%2Fcanvas%23%2Fexport%2Fworkpad%2Fpdf%2Fbase-workpad%2Fpage%2F2),title:'base%20workpad')"` + ); +}); + +test('createPdf posts to create the pdf', () => { + createPdf(workpad, { pageCount: 2 }, addBasePath); + + expect(fetch.post).toBeCalled(); + + const args = (fetch.post as jest.MockedFunction).mock.calls[0]; + + expect(args[0]).toMatchInlineSnapshot(`"basepath//api/reporting/generate/printablePdf"`); + expect(args[1]).toMatchInlineSnapshot(` + Object { + "jobParams": "(browserTimezone:America/Phoenix,layout:(dimensions:(height:0,width:0),id:preserve_layout),objectType:'canvas workpad',relativeUrls:!(/app/canvas#/export/workpad/pdf/base-workpad/page/1,/app/canvas#/export/workpad/pdf/base-workpad/page/2),title:'base workpad')", + } + `); +}); diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts index f0ca5fac1d271..f7f191a48de82 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts @@ -7,6 +7,7 @@ import rison from 'rison-node'; // @ts-ignore Untyped local. import { fetch } from '../../../../common/lib/fetch'; +import { getStartPlugins } from '../../../legacy'; import { CanvasWorkpad } from '../../../../types'; // type of the desired pdf output (print or preserve_layout) @@ -25,7 +26,7 @@ interface PdfUrlData { createPdfPayload: { jobParams: string }; } -export function getPdfUrl( +function getPdfUrlParts( { id, name: title, width, height }: CanvasWorkpad, { pageCount }: PageCount, addBasePath: (path: string) => string @@ -68,7 +69,16 @@ export function getPdfUrl( }; } +export function getPdfUrl(...args: Arguments): string { + const urlParts = getPdfUrlParts(...args); + + return `${urlParts.createPdfUri}?${getStartPlugins().__LEGACY.QueryString.param( + 'jobParams', + urlParts.createPdfPayload.jobParams + )}`; +} + export function createPdf(...args: Arguments) { - const { createPdfUri, createPdfPayload } = getPdfUrl(...args); + const { createPdfUri, createPdfPayload } = getPdfUrlParts(...args); return fetch.post(createPdfUri, createPdfPayload); } diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx index 20f48c2092766..0558652fb6029 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/workpad_export.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FunctionComponent, useState, MouseEvent } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; import { EuiButtonIcon, EuiContextMenu, EuiIcon } from '@elastic/eui'; // @ts-ignore Untyped local @@ -13,7 +13,7 @@ import { DisabledPanel } from './disabled_panel'; import { PDFPanel } from './pdf_panel'; import { ShareWebsiteFlyout } from './flyout'; -import { ComponentStrings } from '../../../../i18n'; +import { ComponentStrings } from '../../../../i18n/components'; const { WorkpadHeaderWorkpadExport: strings } = ComponentStrings; type ClosePopoverFn = () => void; @@ -129,7 +129,7 @@ export const WorkpadExport: FunctionComponent = ({ ], }); - const exportControl = (togglePopover: (ev: MouseEvent) => void) => ( + const exportControl = (togglePopover: React.MouseEventHandler) => ( notify.success( - `Custom element '${customElement.displayName || customElement.id}' was saved` + `Custom element '${customElement.displayName || customElement.id}' was saved`, + { + 'data-test-subj': 'canvasCustomElementCreate-success', + } ) ) .catch((result: Http2ServerResponse) => diff --git a/x-pack/legacy/plugins/canvas/public/lib/keymap.ts b/x-pack/legacy/plugins/canvas/public/lib/keymap.ts index 8c3e739393031..7976c8ed5c5da 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/keymap.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/keymap.ts @@ -6,8 +6,8 @@ import { mapValues } from 'lodash'; -import { ShortcutMap, ShortcutNameSpace } from '../../types'; -import { ShortcutStrings as strings } from '../../i18n/'; +import { ShortcutMap, ShortcutNameSpace } from '../../types/shortcuts'; +import { ShortcutStrings as strings } from '../../i18n/shortcuts'; const shortcutHelp = strings.getShortcutHelp(); const namespaceDisplayNames = strings.getNamespaceDisplayNames(); diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index 9828845d9ffa9..155eef99632a0 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -39,6 +39,7 @@ export interface CanvasStartDeps { __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; formatMsg: any; + QueryString: any; setRootController: Chrome['setRootController']; storage: typeof Storage; trackSubUrlForApp: Chrome['trackSubUrlForApp']; diff --git a/x-pack/legacy/plugins/canvas/scripts/storybook_new.js b/x-pack/legacy/plugins/canvas/scripts/storybook_new.js new file mode 100644 index 0000000000000..4871898b73a45 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/scripts/storybook_new.js @@ -0,0 +1,13 @@ +/* + * 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 { join } from 'path'; + +// eslint-disable-next-line +require('@kbn/storybook').runStorybookCli({ + name: 'canvas', + storyGlobs: [join(__dirname, '..', '**', '*.stories.tsx')], +}); diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js b/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js index 605591167c499..2ac92f57a90ed 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/constants.js @@ -5,9 +5,7 @@ */ const path = require('path'); - -const LIBRARY_NAME = 'KbnCanvas'; -const SHAREABLE_RUNTIME_NAME = 'kbn_canvas'; +const { LIBRARY_NAME, SHAREABLE_RUNTIME_NAME } = require('./constants_static'); const KIBANA_ROOT_PATH = '../../../../..'; const CANVAS_ROOT_PATH = 'x-pack/legacy/plugins/canvas'; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.d.ts b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.d.ts new file mode 100644 index 0000000000000..fec4da996ba8f --- /dev/null +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.d.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const LIBRARY_NAME: string; +export const SHAREABLE_RUNTIME_NAME: string; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.js b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.js new file mode 100644 index 0000000000000..914a7070920e4 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/constants_static.js @@ -0,0 +1,13 @@ +/* + * 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. + */ + +const LIBRARY_NAME = 'KbnCanvas'; +const SHAREABLE_RUNTIME_NAME = 'kbn_canvas'; + +module.exports = { + LIBRARY_NAME, + SHAREABLE_RUNTIME_NAME, +}; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 470fa00734d27..391973f6d909b 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -30,7 +30,7 @@ import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; import 'ui/kbn_top_nav'; -import 'ui/vislib'; +import 'ui/color_maps'; import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index 360561df71957..95b7dd22e9fcf 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -21,6 +21,7 @@ import { ReactWrapper } from 'enzyme'; import { createMockGraphStore } from '../state_management/mocks'; import { Provider } from 'react-redux'; +jest.mock('ui/new_platform'); jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); const waitForIndexPatternFetch = () => new Promise(r => setTimeout(r)); @@ -51,7 +52,7 @@ function wrapSearchBarInContext(testProps: OuterSearchBarProps) { savedQueries: {}, }, autocomplete: { - getProvider: () => undefined, + hasQuerySuggestions: () => false, }, }, }; diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/constants.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/constants.ts index db29ed844b606..ef5cffc05d8d7 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/constants.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/constants.ts @@ -26,16 +26,5 @@ export const ALIASES = { }; export const MAPPINGS = { - _source: { - enabled: false, - }, - properties: { - host_name: { - type: 'keyword', - }, - created_at: { - type: 'date', - format: 'EEE MMM dd HH:mm:ss Z yyyy', - }, - }, + properties: {}, }; diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts index 1b75f4e190934..48ae51b711f9c 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts @@ -4,26 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TestBed, SetupFunc } from '../../../../../../test_utils'; +import { TestBed, SetupFunc, UnwrapPromise } from '../../../../../../test_utils'; import { Template } from '../../../common/types'; import { nextTick } from './index'; -export interface TemplateFormTestBed extends TestBed { - actions: { - clickNextButton: () => void; - clickBackButton: () => void; - clickSubmitButton: () => void; - completeStepOne: ({ name, indexPatterns, order, version }: Partial