diff --git a/.backportrc.json b/.backportrc.json index f921512df2d0e..56a6ddc254d13 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,7 +1,9 @@ { "upstream": "elastic/kibana", "targetBranchChoices": [ + { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.10", "7.9", "7.8", "7.7", @@ -25,7 +27,8 @@ ], "targetPRLabels": ["backport"], "branchLabelMapping": { - "^v7.10.0$": "7.x", + "^v8.0.0$": "master", + "^v7.11.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" } -} +} \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index 93c69b4f9b207..7f3e3ef597cbb 100644 --- a/.eslintignore +++ b/.eslintignore @@ -19,19 +19,14 @@ target # plugin overrides /src/core/lib/kbn_internal_native_observable /src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken -/src/legacy/ui/public/flot-charts /src/plugins/data/common/es_query/kuery/ast/_generated_/** /src/plugins/vis_type_timelion/public/_generated_/** -/src/plugins/vis_type_timelion/public/flot/jquery.flot.* -/src/plugins/timelion/public/flot/jquery.flot.* /x-pack/legacy/plugins/**/__tests__/fixtures/** /x-pack/plugins/apm/e2e/**/snapshots.js /x-pack/plugins/apm/e2e/tmp/* /x-pack/plugins/canvas/canvas_plugin -/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts /x-pack/plugins/canvas/shareable_runtime/build /x-pack/plugins/canvas/storybook/build -/x-pack/plugins/monitoring/public/lib/jquery_flot /x-pack/plugins/reporting/server/export_types/printable_pdf/server/lib/pdf/assets/** /x-pack/legacy/plugins/infra/common/graphql/types.ts /x-pack/legacy/plugins/infra/public/graphql/types.ts @@ -48,4 +43,4 @@ target /packages/kbn-ui-framework/dist /packages/kbn-ui-framework/doc_site/build /packages/kbn-ui-framework/generator-kui/*/templates/ - +/packages/kbn-ui-shared-deps/flot_charts diff --git a/.eslintrc.js b/.eslintrc.js index ef9731481ec4e..1597431d4b393 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1182,13 +1182,7 @@ module.exports = { }, }, { - files: ['x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/**/*.js'], - env: { - jquery: true, - }, - }, - { - files: ['x-pack/plugins/monitoring/public/lib/jquery_flot/**/*.js'], + files: ['packages/kbn-ui-shared-deps/flot_charts/**/*.js'], env: { jquery: true, }, diff --git a/.i18nrc.json b/.i18nrc.json index e0281b0a5bc21..68e38d3976a68 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -11,6 +11,7 @@ "uiActionsExamples": "examples/ui_action_examples", "share": "src/plugins/share", "home": "src/plugins/home", + "flot": "packages/kbn-ui-shared-deps/flot_charts", "charts": "src/plugins/charts", "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index 4b4f4541dd42f..26fabe709a84a 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -10,6 +10,7 @@ This section summarizes the changes in each release. +* <> * <> * <> * <> @@ -43,6 +44,39 @@ This section summarizes the changes in each release. -- +[[release-notes-7.9.3]] +== {kib} 7.9.3 + +For breaking changes, refer to <>. + +[float] +[[enhancement-v7.9.3]] +=== Enhancement +Reporting:: +* Increases openUrl timeout config default to 1min {kibana-pull}78322[#78322] + +[float] +[[bug-v7.9.3]] +=== Bug fixes +Discover:: +* Fixes scripted field error handling for non OSS env {kibana-pull}76476[#76476] +Lens and visualizations:: +* Shows tooltip on external pointer events {kibana-pull}77306[#77306] +Management:: +* Fixes a bug in the index template wizard, which resulted in an error when the user tries to save an existing +template or attempts to view the Elasticsearch request when the template does not have any mappings {kibana-pull}78653[#78653] +* Fixes index pattern creation when using cross cluster search with clusters running <7.9 {kibana-pull}80006[#80006] +Monitoring:: +* Fixes UX around an unavailable remote cluster {kibana-pull}79202[#79202] +* Synchronous `setup` and `start` methods {kibana-pull}79457[#79457] +* Removes license refresh from setup {kibana-pull}79518[#79518] +Platform:: +* Invalid `searchSourceJSON` causes saved object migration to fail {kibana-pull}78535[#78535] +* Changes legacy import 'version' field to optional {kibana-pull}79706[#79706] +Security:: +* Adds `xpack.security.sameSiteCookies` to docker allow list {kibana-pull}78192[#78192] +* Updates user table after user is deleted {kibana-pull}79491[#79491] + [[release-notes-7.9.2]] == {kib} 7.9.2 @@ -150,8 +184,6 @@ Operations:: [float] [[enhancement-7.9.0]] === Enhancements -Alerting:: -* Actions add proxy support {kibana-pull}74289[#74289] APM:: * Shows `trace.id`, `transaction.id` and/or `error.id` in metadata table {kibana-pull}66376[#66376] * Adds error rate chart to Transaction overview and detail views {kibana-pull}67327[#67327] diff --git a/docs/api/dashboard/export-dashboard.asciidoc b/docs/api/dashboard/export-dashboard.asciidoc index 2099fb599ba67..d33b9603fae73 100644 --- a/docs/api/dashboard/export-dashboard.asciidoc +++ b/docs/api/dashboard/export-dashboard.asciidoc @@ -11,6 +11,8 @@ experimental[] Export dashboards and corresponding saved objects. `GET :/api/kibana/dashboards/export` +`GET :/s//api/kibana/dashboards/export` + [[dashboard-api-export-params]] ==== Query parameters diff --git a/docs/api/dashboard/import-dashboard.asciidoc b/docs/api/dashboard/import-dashboard.asciidoc index 56bd4abbc8023..5d1fab41a2a14 100644 --- a/docs/api/dashboard/import-dashboard.asciidoc +++ b/docs/api/dashboard/import-dashboard.asciidoc @@ -11,6 +11,8 @@ experimental[] Import dashboards and corresponding saved objects. `POST :/api/kibana/dashboards/import` +`POST :/s//api/kibana/dashboards/import` + [[dashboard-api-import-params]] ==== Query parameters diff --git a/docs/api/saved-objects.asciidoc b/docs/api/saved-objects.asciidoc index a4e9fa32f8a5c..0d8ceefb47e91 100644 --- a/docs/api/saved-objects.asciidoc +++ b/docs/api/saved-objects.asciidoc @@ -28,6 +28,8 @@ The following saved objects APIs are available: * <> to resolve errors from the import API +* <> to rotate the encryption key for encrypted saved objects + include::saved-objects/get.asciidoc[] include::saved-objects/bulk_get.asciidoc[] include::saved-objects/find.asciidoc[] @@ -38,3 +40,4 @@ include::saved-objects/delete.asciidoc[] include::saved-objects/export.asciidoc[] include::saved-objects/import.asciidoc[] include::saved-objects/resolve_import_errors.asciidoc[] +include::saved-objects/rotate_encryption_key.asciidoc[] diff --git a/docs/api/saved-objects/rotate_encryption_key.asciidoc b/docs/api/saved-objects/rotate_encryption_key.asciidoc new file mode 100644 index 0000000000000..0a66ed2b4b361 --- /dev/null +++ b/docs/api/saved-objects/rotate_encryption_key.asciidoc @@ -0,0 +1,110 @@ +[role="xpack"] +[[saved-objects-api-rotate-encryption-key]] +=== Rotate encryption key API +++++ +Rotate encryption key +++++ + +experimental[] Rotate the encryption key for encrypted saved objects. + +If a saved object cannot be decrypted using the primary encryption key, then {kib} will attempt to decrypt it using the specified <>. In most of the cases this overhead is negligible, but if you're dealing with a large number of saved objects and experiencing performance issues, you may want to rotate the encryption key. + +[IMPORTANT] +============================================================================ +Bulk key rotation can consume a considerable amount of resources and hence only user with a `superuser` role can trigger it. +============================================================================ + +[[saved-objects-api-rotate-encryption-key-request]] +==== Request + +`POST :/api/encrypted_saved_objects/_rotate_key` + +[[saved-objects-api-rotate-encryption-key-request-query-params]] +==== Query parameters + +`type`:: +(Optional, string) Limits encryption key rotation only to the saved objects with the specified type. By default, {kib} tries to rotate the encryption key for all saved object types that may contain encrypted attributes. + +`batchSize`:: +(Optional, number) Specifies a maximum number of saved objects that {kib} can process in a single batch. Bulk key rotation is an iterative process since {kib} may not be able to fetch and process all required saved objects in one go and splits processing into consequent batches. By default, the batch size is 10000, which is also a maximum allowed value. + +[[saved-objects-api-rotate-encryption-key-response-body]] +==== Response body + +`total`:: +(number) Indicates the total number of _all_ encrypted saved objects (optionally filtered by the requested `type`), regardless of the key {kib} used for encryption. + +`successful`:: +(number) Indicates the total number of _all_ encrypted saved objects (optionally filtered by the requested `type`), regardless of the key {kib} used for encryption. ++ +NOTE: In most cases, `total` will be greater than `successful` even if `failed` is zero. The reason is that {kib} may not need or may not be able to rotate encryption keys for all encrypted saved objects. + +`failed`:: +(number) Indicates the number of the saved objects that were still encrypted with one of the old encryption keys that {kib} failed to re-encrypt with the primary key. + +[[saved-objects-api-rotate-encryption-key-response-codes]] +==== Response code + +`200`:: +Indicates a successful call. + +`400`:: +Indicates that either query parameters are wrong or <> aren't configured. + +`429`:: +Indicates that key rotation is already in progress. + +[[saved-objects-api-rotate-encryption-key-example]] +==== Examples + +[[saved-objects-api-rotate-encryption-key-example-1]] +===== Encryption key rotation with default parameters + +[source,sh] +-------------------------------------------------- +$ curl -X POST /api/encrypted_saved_objects/_rotate_key +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "total": 1000, + "successful": 300, + "failed": 0 +} +-------------------------------------------------- + +The result indicates that the encryption key was successfully rotated for 300 out of 1000 saved objects with encrypted attributes, and 700 of the saved objects either didn't require key rotation, or were encrypted with an unknown encryption key. + +[[saved-objects-api-rotate-encryption-key-example-2]] +===== Encryption key rotation for the specific type with reduce batch size + +[IMPORTANT] +============================================================================ +Default parameters are optimized for speed. Change the parameters only when necessary. However, if you're experiencing any issues with this API, you may want to decrease a batch size or rotate the encryption keys for the specific types only. In this case, you may need to run key rotation multiple times in a row. +============================================================================ + +In this example, key rotation is performed for all saved objects with the `alert` type in batches of 5000. + +[source,sh] +-------------------------------------------------- +$ curl -X POST /api/encrypted_saved_objects/_rotate_key?type=alert&batchSize=5000 +-------------------------------------------------- +// KIBANA + +The API returns the following: + +[source,sh] +-------------------------------------------------- +{ + "total": 100, + "successful": 100, + "failed": 0 +} +-------------------------------------------------- + +The result indicates that the encryption key was successfully rotated for all 100 saved objects with the `alert` type. + diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc index bc5e1ccc1dd55..7bdfe80b42177 100644 --- a/docs/apm/apm-alerts.asciidoc +++ b/docs/apm/apm-alerts.asciidoc @@ -18,12 +18,22 @@ image::apm/images/apm-alert.png[Create an alert in the APM app] For a walkthrough of the alert flyout panel, including detailed information on each configurable property, see Kibana's <>. -The APM app supports two different types of threshold alerts: transaction duration, and error rate. -Below, we'll create one of each. +The APM app supports four different types of alerts: + +* Transaction duration anomaly: +alerts when the service's transaction duration reaches a certain anomaly score +* Transaction duration threshold: +alerts when the service's transaction duration exceeds a given time limit over a given time frame +* Transaction error rate threshold: +alerts when the service's transaction error rate is above the selected rate over a given time frame +* Error count threshold: +alerts when service exceeds a selected number of errors over a given time frame + +Below, we'll walk through the creation of two of these alerts. [float] [[apm-create-transaction-alert]] -=== Create a transaction duration alert +=== Example: create a transaction duration alert Transaction duration alerts trigger when the duration of a specific transaction type in a service exceeds a defined threshold. This guide will create an alert for the `opbeans-java` service based on the following criteria: @@ -57,9 +67,9 @@ Enter a name for the connector, and paste the webhook URL. See Slack's webhook documentation if you need to create one. -Add a message body in markdown format. +A default message is provided as a starting point for your alert. You can use the https://mustache.github.io/[Mustache] template syntax, i.e., `{{variable}}` -to pass alert values at the time a condition is detected to an action. +to pass additional alert values at the time a condition is detected to an action. A list of available variables can be accessed by selecting the **add variable** button image:apm/images/add-variable.png[add variable button]. @@ -67,7 +77,7 @@ Select **Save**. The alert has been created and is now active! [float] [[apm-create-error-alert]] -=== Create an error rate alert +=== Example: create an error rate alert Error rate alerts trigger when the number of errors in a service exceeds a defined threshold. This guide creates an alert for the `opbeans-python` service based on the following criteria: @@ -94,9 +104,9 @@ Based on the alert criteria, define the following alert details: Select the **Email** action type and click **Create a connector**. Fill out the required details: sender, host, port, etc., and click **save**. -Add a message body in markdown format. +A default message is provided as a starting point for your alert. You can use the https://mustache.github.io/[Mustache] template syntax, i.e., `{{variable}}` -to pass alert values at the time a condition is detected to an action. +to pass additional alert values at the time a condition is detected to an action. A list of available variables can be accessed by selecting the **add variable** button image:apm/images/add-variable.png[add variable button]. diff --git a/docs/apm/filters.asciidoc b/docs/apm/filters.asciidoc index d53adb439f0c8..c405ea10ade3d 100644 --- a/docs/apm/filters.asciidoc +++ b/docs/apm/filters.asciidoc @@ -69,7 +69,7 @@ the host filter will still be applied. These filters are very useful for quickly and easily removing noise from your data. With just a click, you can filter your transactions by the transaction result, -host, container ID, and more. +host, container ID, Kubernetes pod, and more. [role="screenshot"] image::apm/images/local-filter.png[Local filters available in the APM app in Kibana] \ No newline at end of file diff --git a/docs/apm/images/advanced-discover.png b/docs/apm/images/advanced-discover.png index 56ba58b2c1d41..5291526783a6b 100644 Binary files a/docs/apm/images/advanced-discover.png and b/docs/apm/images/advanced-discover.png differ diff --git a/docs/apm/images/apm-alert.png b/docs/apm/images/apm-alert.png index 350704d8969ae..c68b36f522bfc 100644 Binary files a/docs/apm/images/apm-alert.png and b/docs/apm/images/apm-alert.png differ diff --git a/docs/apm/images/apm-distributed-tracing.png b/docs/apm/images/apm-distributed-tracing.png index e9c6713361c73..0dbffa591d43a 100644 Binary files a/docs/apm/images/apm-distributed-tracing.png and b/docs/apm/images/apm-distributed-tracing.png differ diff --git a/docs/apm/images/apm-error-group.png b/docs/apm/images/apm-error-group.png index ecdf9c20cf4aa..359bdc6b704e9 100644 Binary files a/docs/apm/images/apm-error-group.png and b/docs/apm/images/apm-error-group.png differ diff --git a/docs/apm/images/apm-errors-overview.png b/docs/apm/images/apm-errors-overview.png index 90f16b81e9f50..969a1f19f9f43 100644 Binary files a/docs/apm/images/apm-errors-overview.png and b/docs/apm/images/apm-errors-overview.png differ diff --git a/docs/apm/images/apm-geo-ui.png b/docs/apm/images/apm-geo-ui.png index a767ed7e08e0c..3757127bad9c0 100644 Binary files a/docs/apm/images/apm-geo-ui.png and b/docs/apm/images/apm-geo-ui.png differ diff --git a/docs/apm/images/apm-metrics.png b/docs/apm/images/apm-metrics.png index 60383ef428f2a..ffe5ffc7e1d83 100644 Binary files a/docs/apm/images/apm-metrics.png and b/docs/apm/images/apm-metrics.png differ diff --git a/docs/apm/images/apm-query-bar.png b/docs/apm/images/apm-query-bar.png index 313ee7d4b8fc8..90955fb61016d 100644 Binary files a/docs/apm/images/apm-query-bar.png and b/docs/apm/images/apm-query-bar.png differ diff --git a/docs/apm/images/apm-service-map-anomaly.png b/docs/apm/images/apm-service-map-anomaly.png index b661e8f09d1a1..cd59f86690666 100644 Binary files a/docs/apm/images/apm-service-map-anomaly.png and b/docs/apm/images/apm-service-map-anomaly.png differ diff --git a/docs/apm/images/apm-services-overview.png b/docs/apm/images/apm-services-overview.png index 48236522ddfbb..85d14cc7dfc6e 100644 Binary files a/docs/apm/images/apm-services-overview.png and b/docs/apm/images/apm-services-overview.png differ diff --git a/docs/apm/images/apm-settings.png b/docs/apm/images/apm-settings.png index 4eaef9ec15ac5..14cf32877b720 100644 Binary files a/docs/apm/images/apm-settings.png and b/docs/apm/images/apm-settings.png differ diff --git a/docs/apm/images/apm-traces.png b/docs/apm/images/apm-traces.png index 6219be5b6d6e4..bf1f7e783bb11 100644 Binary files a/docs/apm/images/apm-traces.png and b/docs/apm/images/apm-traces.png differ diff --git a/docs/apm/images/apm-transaction-response-dist.png b/docs/apm/images/apm-transaction-response-dist.png index ecf5a4af2c25d..1d268bbaac465 100644 Binary files a/docs/apm/images/apm-transaction-response-dist.png and b/docs/apm/images/apm-transaction-response-dist.png differ diff --git a/docs/apm/images/apm-transaction-sample.png b/docs/apm/images/apm-transaction-sample.png index 73668b094f9cf..bfdb6a5abe65b 100644 Binary files a/docs/apm/images/apm-transaction-sample.png and b/docs/apm/images/apm-transaction-sample.png differ diff --git a/docs/apm/images/apm-transactions-overview.png b/docs/apm/images/apm-transactions-overview.png index b3b6ca22c4f63..53d7637b18647 100644 Binary files a/docs/apm/images/apm-transactions-overview.png and b/docs/apm/images/apm-transactions-overview.png differ diff --git a/docs/apm/images/example-metadata.png b/docs/apm/images/example-metadata.png index 0e35f90691723..2a5bda7f088f6 100644 Binary files a/docs/apm/images/example-metadata.png and b/docs/apm/images/example-metadata.png differ diff --git a/docs/apm/images/jvm-metrics-overview.png b/docs/apm/images/jvm-metrics-overview.png index 9c8ba4a12a262..586836c6cfe3e 100644 Binary files a/docs/apm/images/jvm-metrics-overview.png and b/docs/apm/images/jvm-metrics-overview.png differ diff --git a/docs/apm/images/jvm-metrics.png b/docs/apm/images/jvm-metrics.png index 1720e1370ff90..52a1ca5bea8d8 100644 Binary files a/docs/apm/images/jvm-metrics.png and b/docs/apm/images/jvm-metrics.png differ diff --git a/docs/apm/images/local-filter.png b/docs/apm/images/local-filter.png index faac5c143a7d8..8657e39f430aa 100644 Binary files a/docs/apm/images/local-filter.png and b/docs/apm/images/local-filter.png differ diff --git a/docs/apm/images/service-maps-java.png b/docs/apm/images/service-maps-java.png index e1a42f4c76e12..b3726bdc00ab6 100644 Binary files a/docs/apm/images/service-maps-java.png and b/docs/apm/images/service-maps-java.png differ diff --git a/docs/apm/images/service-maps.png b/docs/apm/images/service-maps.png index 078fabcfa2879..878a31adc69ca 100644 Binary files a/docs/apm/images/service-maps.png and b/docs/apm/images/service-maps.png differ diff --git a/docs/apm/images/service-quick-health.png b/docs/apm/images/service-quick-health.png new file mode 100644 index 0000000000000..aab1332513079 Binary files /dev/null and b/docs/apm/images/service-quick-health.png differ diff --git a/docs/apm/images/specific-transaction.png b/docs/apm/images/specific-transaction.png index 9911dbd879f41..52073bf76520a 100644 Binary files a/docs/apm/images/specific-transaction.png and b/docs/apm/images/specific-transaction.png differ diff --git a/docs/apm/machine-learning.asciidoc b/docs/apm/machine-learning.asciidoc index db2a1ef6e2da0..b31d717a6932e 100644 --- a/docs/apm/machine-learning.asciidoc +++ b/docs/apm/machine-learning.asciidoc @@ -14,7 +14,12 @@ Machine learning jobs are created per environment, and are based on a service's Because jobs are created at the environment level, you can add new services to your existing environments without the need for additional machine learning jobs. -After a machine learning job is created, results are shown in two places: +Results from machine learning jobs are shown in multiple places throughout the APM app: + +* The **Services overview** provides a quick-glance view of the general health of all of your services. ++ +[role="screenshot"] +image::apm/images/service-quick-health.png[Example view of anomaly scores on response times in the APM app] * The transaction duration chart will show the expected bounds and add an annotation when the anomaly score is 75 or above. + diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc index d629a95073a74..d44c4ff6caa5c 100644 --- a/docs/apm/service-maps.asciidoc +++ b/docs/apm/service-maps.asciidoc @@ -33,7 +33,7 @@ distributed tracing will not work, and the connection will not be drawn on the m Select the **Service Map** tab to get started. By default, all instrumented services and connections are shown. Whether you're onboarding a new engineer, or just trying to grasp the big picture, -click around, zoom in and out, and begin to visualize how your services are connected. +drag things around, zoom in and out, and begin to visualize how your services are connected. If there's a specific service that interests you, select that service to highlight its connections. Clicking **Focus map** will refocus the map on that specific service and lock the connection highlighting. diff --git a/docs/apm/services.asciidoc b/docs/apm/services.asciidoc index 395e23c379306..2bf2e35c21cd8 100644 --- a/docs/apm/services.asciidoc +++ b/docs/apm/services.asciidoc @@ -2,8 +2,13 @@ [[services]] === Services overview -The *Services* overview gives you quick insights into the health and general performance of all of your instrumented services. -Services are sorted by the `service.name` configured in each of the {apm-agents-ref}[APM agents] you’ve installed. +The *Services* overview page provides a quick, high-level overview of the health and general +performance of all instrumented services. + +To help surface potential issues, services are sorted by their health status: +**critical** > **warning** > **healthy** > **unknown**. +Health status is powered by machine learning and requires anomaly detection to be enabled. +Learn more in <>. [role="screenshot"] -image::apm/images/apm-services-overview.png[Example view of services table the APM app in Kibana] \ No newline at end of file +image::apm/images/apm-services-overview.png[Example view of services table the APM app in Kibana] diff --git a/docs/apm/spans.asciidoc b/docs/apm/spans.asciidoc index c35fb115d2db4..7f29b1f003f1c 100644 --- a/docs/apm/spans.asciidoc +++ b/docs/apm/spans.asciidoc @@ -3,7 +3,7 @@ === Trace sample timeline The trace sample timeline visualization is a bird's-eye view of what your application was doing while it was trying to respond to a request. -This makes it useful for visualizing where the selected transaction spent most of its time. +This makes it useful for visualizing where a selected transaction spent most of its time. [role="screenshot"] image::apm/images/apm-transaction-sample.png[Example of distributed trace colors in the APM app in Kibana] @@ -43,9 +43,12 @@ this makes finding possible bottlenecks throughout your application much easier image::apm/images/apm-distributed-tracing.png[Example view of the distributed tracing in APM app in Kibana] Don't forget; by definition, a distributed trace includes more than one transaction. -When viewing these distributed traces in the timeline waterfall, you'll see this image:apm/images/transaction-icon.png[APM icon] icon, +When viewing distributed traces in the timeline waterfall, +you'll see this icon: image:apm/images/transaction-icon.png[APM icon], which indicates the next transaction in the trace. -These transactions can be expanded and viewed in detail by clicking on them. +For easier problem isolation, transactions can be collapsed in the waterfall by clicking +the icon to the left of the transactions. +Transactions can also be expanded and viewed in detail by clicking on them. After exploring these traces, you can return to the full trace by clicking *View full trace*. diff --git a/docs/apm/traces.asciidoc b/docs/apm/traces.asciidoc index 52b4b618de466..3bafebd733159 100644 --- a/docs/apm/traces.asciidoc +++ b/docs/apm/traces.asciidoc @@ -7,7 +7,8 @@ and which services were part of it. In addition to the Traces overview, you can view your application traces in the <>. The *Traces* overview displays the entry transaction for all traces in your application. -If you're using <>, this view is key to finding the critical paths within your application. +If you're using <>, +this view is key to finding the critical paths within your application. Transactions with the same name are grouped together and only shown once in this table. By default, transactions are sorted by _Impact_. diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 84ab6b2a58579..fef98a86de1d0 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -10,17 +10,8 @@ Selecting a <> brings you to the *transactions* overview. [role="screenshot"] image::apm/images/apm-transactions-overview.png[Example view of transactions table in the APM app in Kibana] -The *time spent by span type*, *transaction duration*, and *requests per minute* chart display information on all transactions associated with the selected service: - -*Time spent by span type*:: -Visualize where your application is spending most of its time. -For example, is your app spending time in external calls, database processing, or application code execution? -+ -The time a transaction took to complete is also recorded and displayed on the chart under the "app" label. -"app" indicates that something was happening within the application, but we're not sure exactly what. -This could be a sign that the agent does not have auto-instrumentation for whatever was happening during that time. -+ -It's important to note that if you have asynchronous spans, the sum of all span times may exceed the duration of the transaction. +The *transaction duration*, *transactions per minute*, *transaction error rate*, and *time spent by span type* +charts display information on all transactions associated with the selected service: *Transaction duration*:: Response times for this service, broken down into average, 95th, and 99th percentile. @@ -28,11 +19,26 @@ If there's a weird spike that you'd like to investigate, you can simply zoom in on the graph - this will adjust the specific time range, and all of the data on the page will update accordingly. -*Requests per minute*:: +*Transactions per minute*:: Visualize response codes: `2xx`, `3xx`, `4xx`, etc., and is useful for determining if you're serving more of one code than you typically do. Like in the Transaction duration graph, you can zoom in on anomalies to further investigate them. +*Transaction error rate*:: +Visualize the total number of transactions with errors divided by the total number of transactions. +Any unexpected increases, decreases, or irregular patterns can be investigated further +with the <>. + +*Time spent by span type*:: +Visualize where your application is spending most of its time. +For example, is your app spending time in external calls, database processing, or application code execution? ++ +The time a transaction took to complete is also recorded and displayed on the chart under the "app" label. +"app" indicates that something was happening within the application, but we're not sure exactly what. +This could be a sign that the agent does not have auto-instrumentation for whatever was happening during that time. ++ +It's important to note that if you have asynchronous spans, the sum of all span times may exceed the duration of the transaction. + [[transactions-table]] ==== Transactions table @@ -61,42 +67,45 @@ refer to the documentation for each {apm-agents-ref}[APM Agent] you've implement ==== RUM Transaction overview The transaction overview page is customized for the JavaScript RUM Agent. -This page highlights things like *page load times*, *transactions per minute*, and even the *average page load duration distribution by country*. +Specifically, the page highlights *page load times* for your service: [role="screenshot"] image::apm/images/apm-geo-ui.png[average page load duration distribution] -This data is available due to the geo-ip and user agent pipelines being enabled by default, -which allows for the capture of geo-location and user agent data. -These visualizations make it easy for you to visualize performance information about your -end-users' experience based on their location. +Additional RUM goodies, like core vitals, and visitor breakdown by browser, location, and device, +are available in the Observability User Experience tab. +// To do +// Add link to the Observability UE docs when complete [[transaction-details]] ==== Transaction details Selecting a transaction group will bring you to the *transaction* details. -Transaction details include a high-level overview of the time spent by span type, -transaction group duration, requests per minute, and transaction group duration distribution. -It's important to note that all of these graphs show data from every transaction within the selected transaction group. +This page is visually similar to the transaction overview, but it shows data from all transactions within +the selected transaction group. [role="screenshot"] image::apm/images/apm-transaction-response-dist.png[Example view of response time distribution] Up to ten sampled transactions are also displayed. -These sampled transactions are based on your selection in the *Transactions duration distribution*. -You can update the sampled transactions by selecting a new _bucket_ in the transactions duration distribution graph. -The number of requests per bucket is displayed when hovering over the graph, and the selected bucket is highlighted to stand out. +These sampled transactions are based on the _bucket_ selection in the *Transactions duration distribution* chart. +You can update the sampled transactions by selecting a new _bucket_. +The number of requests per bucket is displayed when hovering over the graph, +and the selected bucket is highlighted to stand out. + +The screenshot below shows a typical distribution, and indicates most of our requests were served quickly--awesome! +It's the requests on the right, the ones taking longer than average, that we probably want to focus on. [role="screenshot"] image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph] -This graph shows a typical distribution, and indicates most of our requests were served quickly--awesome! -It's the requests on the right, the ones taking longer than average, that we probably want to focus on. - -When you select one of these buckets, +When you select a bucket, you're presented with up to ten trace samples. -Each sample has a trace timeline waterfall that shows what a typical request in that bucket was doing. -By investigating this timeline waterfall, we can hopefully determine _why_ this request was slow and then implement a fix. +Each sample has a trace timeline waterfall that shows how a typical request in that bucket executed. +This waterfall is useful for understanding the parent/child hierarchy of transactions and spans, +and ultimately determining _why_ a request was slow. +For large waterfalls, expand problematic transactions and collapse well-performing ones +for easier problem isolation and troubleshooting. [role="screenshot"] image::apm/images/apm-transaction-sample.png[Example view of transactions sample] diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index 7ed2f57caeadd..f911ec0be8bca 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -14,6 +14,7 @@ Also, check out the https://discuss.elastic.co/c/apm[APM discussion forum]. * <> * <> * <> +* <> [float] [[no-apm-data-found]] @@ -156,7 +157,7 @@ the values in `http.request.cookies` are not indexed and thus not searchable. *Ensure an index pattern exists* As a first step, you should ensure the correct index pattern exists. -In Kibana, navigate to *Management > Kibana > Index Patterns*. +Open the main menu, then click *Stack Management > Index Patterns*. In the pattern list, you should see an apm index pattern; The default is `apm-*`. If you don't, the index pattern doesn't exist. See <> for information on how to fix this problem. @@ -180,3 +181,19 @@ setup.template.append_fields: type: object dynamic: true ---- + +[float] +[[service-map-rum-connections]] +=== Service maps: no connection between client and server + +If the service map is not showing an expected connection between the client and server, +it's likely because you haven't configured +{apm-agent-rum}/configuration.html#distributed-tracing-origins[`distributedTracingOrigins`]. + + +This setting is necessary, for example, for cross-origin requests. +If you have a basic web application that provides data via an API on `localhost:4000`, +and serves HTML from `localhost:4001`, you'd need to set `distributedTracingOrigins: ['https://localhost:4000']` +to ensure the origin is monitored as a part of distributed tracing. +In other words, `distributedTracingOrigins` is consulted prior to the agent adding the +distributed tracing `traceparent` header to each request. diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index 4887eb6ca870d..f49e2a944c900 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -10,7 +10,7 @@ When you've finished your workpad, you can share it outside of {kib}. Create a JSON file of your workpad that you can export outside of {kib}. -Click *Share > Download as JSON*. +To begin, click *Share > Download as JSON*. [role="screenshot"] image::images/canvas-export-workpad.png[Export single workpad through JSON, from Share dropdown] @@ -23,7 +23,7 @@ Want to export multiple workpads? Go to the *Canvas* home page, select the workp If you have a subscription that supports the {report-features}, you can create a PDF copy of your workpad that you can save and share outside {kib}. -Click *Share > PDF reports > Generate PDF*. +To begin, click *Share > PDF reports > Generate PDF*. [role="screenshot"] image::images/canvas-generate-pdf.gif[Image showing how to generate a PDF] @@ -36,7 +36,7 @@ For more information, refer to <> or a script. -Click *Share > PDF reports > Copy POST URL*. +To begin, click *Share > PDF reports > Copy POST URL*. [role="screenshot"] image::images/canvas-create-URL.gif[Image showing how to create POST URL] diff --git a/docs/canvas/canvas-tutorial.asciidoc b/docs/canvas/canvas-tutorial.asciidoc index ea4d2c8cc6a83..6456ba02bb8a8 100644 --- a/docs/canvas/canvas-tutorial.asciidoc +++ b/docs/canvas/canvas-tutorial.asciidoc @@ -2,7 +2,7 @@ [[canvas-tutorial]] == Tutorial: Create a workpad for monitoring sales -To get up and running with Canvas, use the following tutorial where you'll create a workpad for monitoring sales at an eCommerce store. +To get up and running with Canvas, add the Sample eCommerce orders data, then use the data to create a workpad for monitoring sales at an eCommerce store. [float] === Before you begin @@ -14,7 +14,7 @@ For this tutorial, you'll need to add the <>. - -* Learn more about <> — the building blocks of your workpad. +* Build presentations of your own data with <>. * Deep dive into the {kibana-ref}/canvas-function-reference.html[expression language and functions] that drive Canvas. diff --git a/docs/canvas/images/canvas-zoom-controls.png b/docs/canvas/images/canvas-zoom-controls.png new file mode 100644 index 0000000000000..1407ca3cd8627 Binary files /dev/null and b/docs/canvas/images/canvas-zoom-controls.png differ diff --git a/docs/dev-tools/console/console.asciidoc b/docs/dev-tools/console/console.asciidoc index fdb235d528e89..79f2b6c0902e0 100644 --- a/docs/dev-tools/console/console.asciidoc +++ b/docs/dev-tools/console/console.asciidoc @@ -7,7 +7,7 @@ Console enables you to interact with the REST API of {es}. You can: * View API documentation * Get your request history -To get started, open the menu, go to *Dev Tools*, then click *Console*. +To get started, open the main menu, click *Dev Tools*, then click *Console*. [role="screenshot"] image::dev-tools/console/images/console.png["Console"] diff --git a/docs/dev-tools/grokdebugger/index.asciidoc b/docs/dev-tools/grokdebugger/index.asciidoc index 994836de7a1a2..82ae724f705f6 100644 --- a/docs/dev-tools/grokdebugger/index.asciidoc +++ b/docs/dev-tools/grokdebugger/index.asciidoc @@ -35,7 +35,7 @@ is automatically enabled in {kib}. NOTE: If you're using {stack-security-features}, you must have the `manage_pipeline` permission to use the Grok Debugger. -. Open the menu, go to *Dev Tools*, then click *Grok Debugger*. +. Open the main menu, click *Dev Tools*, then click *Grok Debugger*. . In *Sample Data*, enter a message that is representative of the data that you want to parse. For example: + diff --git a/docs/dev-tools/painlesslab/index.asciidoc b/docs/dev-tools/painlesslab/index.asciidoc index 09545548c82b6..5e329843843ec 100644 --- a/docs/dev-tools/painlesslab/index.asciidoc +++ b/docs/dev-tools/painlesslab/index.asciidoc @@ -12,6 +12,6 @@ process {ref}/docs-reindex.html[reindexed data], define complex <>, and work with data in other contexts. -To get started, open the menu, go to *Dev Tools*, then click *Painless Lab*. +To get started, open the main menu, click *Dev Tools*, then click *Painless Lab*. image::dev-tools/painlesslab/images/painless-lab.png[Painless Lab] diff --git a/docs/dev-tools/searchprofiler/getting-started.asciidoc b/docs/dev-tools/searchprofiler/getting-started.asciidoc index eaa7fea6c7f8d..7cd54db5562b7 100644 --- a/docs/dev-tools/searchprofiler/getting-started.asciidoc +++ b/docs/dev-tools/searchprofiler/getting-started.asciidoc @@ -2,7 +2,7 @@ [[profiler-getting-started]] === Getting Started -The {searchprofiler} is automatically enabled in {kib}. From the menu, go to *Dev Tools*, then click *Search Profiler* +The {searchprofiler} is automatically enabled in {kib}. Open the main menu, click *Dev Tools*, then click *Search Profiler* to get started. {searchprofiler} displays the names of the indices searched, the shards in each index, diff --git a/docs/developer/contributing/index.asciidoc b/docs/developer/contributing/index.asciidoc index ecb37ffe9c97b..ba4ab89d17c27 100644 --- a/docs/developer/contributing/index.asciidoc +++ b/docs/developer/contributing/index.asciidoc @@ -49,7 +49,7 @@ The Release Notes summarize what the PRs accomplish in language that is meaningf The text that appears in the Release Notes is pulled directly from your PR title, or a single paragraph of text that you specify in the PR description. -To use a single paragraph of text, enter `Release note:` or a `## Release note` header in the PR description, followed by your text. For example, refer to this https://github.com/elastic/kibana/pull/65796[PR] that uses the `## Release note` header. +To use a single paragraph of text, enter a `Release note:` or `## Release note` header in the PR description ("dev docs" works too), followed by your text. For example, refer to this https://github.com/elastic/kibana/pull/65796[PR] that uses the `## Release note` header. When you create the Release Notes text, use the following best practices: diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7bdbf5cb75fda..fd01dac1132ed 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -416,8 +416,9 @@ using the CURL scripts in the scripts folder. |This plugin provides access to the detailed tile map services from Elastic. -|{kib-repo}blob/{branch}/x-pack/plugins/ml[ml] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/ml/readme.md[ml] +|This plugin provides access to the machine learning features provided by +Elastic. |{kib-repo}blob/{branch}/x-pack/plugins/monitoring[monitoring] @@ -479,8 +480,8 @@ routes, etc. |Gathers all usage collection, retrieving them from both: OSS and X-Pack plugins. -|{kib-repo}blob/{branch}/x-pack/plugins/transform[transform] -|WARNING: Missing README. +|{kib-repo}blob/{branch}/x-pack/plugins/transform/readme.md[transform] +|This plugin provides access to the transforms features provided by Elastic. |{kib-repo}blob/{branch}/x-pack/plugins/translations[translations] diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md index c5e01715534d1..ad762cae489c8 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.es_field_types.md @@ -43,4 +43,5 @@ export declare enum ES_FIELD_TYPES | STRING | "string" | | | TEXT | "text" | | | TOKEN\_COUNT | "token_count" | | +| UNSIGNED\_LONG | "unsigned_long" | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md index d071955f4f522..545b7b9d27e10 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.es_field_types.md @@ -43,4 +43,5 @@ export declare enum ES_FIELD_TYPES | STRING | "string" | | | TEXT | "text" | | | TOKEN\_COUNT | "token_count" | | +| UNSIGNED\_LONG | "unsigned_long" | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrendererror.original.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrendererror.original.md new file mode 100644 index 0000000000000..45f74a52e6b6f --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrendererror.original.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionRenderError](./kibana-plugin-plugins-expressions-public.expressionrendererror.md) > [original](./kibana-plugin-plugins-expressions-public.expressionrendererror.original.md) + +## ExpressionRenderError.original property + +Signature: + +```typescript +original?: Error; +``` diff --git a/docs/discover/images/time-filter.png b/docs/discover/images/time-filter.png new file mode 100644 index 0000000000000..f6d1d5809d7eb Binary files /dev/null and b/docs/discover/images/time-filter.png differ diff --git a/docs/discover/kuery.asciidoc b/docs/discover/kuery.asciidoc index f306f2b8f763f..35f1160ee834d 100644 --- a/docs/discover/kuery.asciidoc +++ b/docs/discover/kuery.asciidoc @@ -115,7 +115,7 @@ KQL supports `>`, `>=`, `<`, and `<=`. For example: [source,yaml] ------------------- -account_number:>=100 and items_sold:<=200 +account_number >= 100 and items_sold <= 200 ------------------- [discrete] diff --git a/docs/discover/search.asciidoc b/docs/discover/search.asciidoc index ee1e1526f9d6f..3720a5b457d84 100644 --- a/docs/discover/search.asciidoc +++ b/docs/discover/search.asciidoc @@ -104,9 +104,7 @@ To save the current search: . Click *Save* in the Kibana toolbar. . Enter a name for the search and click *Save*. -To import, export and delete saved searches: -. Open the menu, then click *Stack Management. -. From the {kib} menu, click *Saved Ojbects*. +To import, export, and delete saved searches, open the main menu, then click *Stack Management > Saved Ojbects*. ==== Open a saved search To load a saved search into Discover: diff --git a/docs/discover/set-time-filter.asciidoc b/docs/discover/set-time-filter.asciidoc index 93fdf9ffd695a..dcdc8ee791e83 100644 --- a/docs/discover/set-time-filter.asciidoc +++ b/docs/discover/set-time-filter.asciidoc @@ -30,7 +30,7 @@ to the last 15 minutes. * *Refresh every* to specify an automatic refresh rate. + [role="screenshot"] -image::images/Timepicker-View.png[Time filter menu] +image::images/time-filter.png[Time filter menu] . To set the start and end times, click the bar next to the time filter. In the popup, select *Absolute*, *Relative* or *Now*, then specify the required diff --git a/docs/ingest_manager/ingest-manager.asciidoc b/docs/fleet/fleet.asciidoc similarity index 65% rename from docs/ingest_manager/ingest-manager.asciidoc rename to docs/fleet/fleet.asciidoc index 8f6e8036c68cd..06b2b96c0035c 100644 --- a/docs/ingest_manager/ingest-manager.asciidoc +++ b/docs/fleet/fleet.asciidoc @@ -1,11 +1,11 @@ [chapter] [role="xpack"] -[[ingest-manager]] -= {ingest-manager} +[[fleet]] += {fleet} -experimental[] +beta[] -{ingest-manager} in {kib} enables you to add and manage integrations for popular +{fleet} in {kib} enables you to add and manage integrations for popular services and platforms, as well as manage {elastic-agent} installations in standalone or {fleet} mode. @@ -17,11 +17,13 @@ Standalone mode requires you to manually configure and manage the agent locally. * An overview of the data ingest in your {es} cluster. * Multiple integrations to collect and transform data. +//TODO: Redo screen capture. + [role="screenshot"] -image::ingest_manager/images/ingest-manager-start.png[{ingest-manager} app in {kib}] +image::fleet/images/fleet-start.png[{fleet} app in {kib}] [float] == Get started -To get started with {ingest-management}, refer to the +To get started with {fleet}, refer to the {ingest-guide}/index.html[Ingest Management Guide]. diff --git a/docs/fleet/images/fleet-start.png b/docs/fleet/images/fleet-start.png new file mode 100644 index 0000000000000..0d0f7b8feec9c Binary files /dev/null and b/docs/fleet/images/fleet-start.png differ diff --git a/docs/ingest_manager/index.asciidoc b/docs/fleet/index.asciidoc similarity index 100% rename from docs/ingest_manager/index.asciidoc rename to docs/fleet/index.asciidoc diff --git a/docs/getting-started/images/add-sample-data.png b/docs/getting-started/images/add-sample-data.png index 1878550bc3169..9dee27dcde71b 100644 Binary files a/docs/getting-started/images/add-sample-data.png and b/docs/getting-started/images/add-sample-data.png differ diff --git a/docs/getting-started/images/gs_maps_time_filter.png b/docs/getting-started/images/gs_maps_time_filter.png deleted file mode 100644 index 83e20c279906e..0000000000000 Binary files a/docs/getting-started/images/gs_maps_time_filter.png and /dev/null differ diff --git a/docs/getting-started/images/tutorial-discover-4.png b/docs/getting-started/images/tutorial-discover-4.png new file mode 100644 index 0000000000000..945a6155c02cd Binary files /dev/null and b/docs/getting-started/images/tutorial-discover-4.png differ diff --git a/docs/getting-started/images/tutorial-final-dashboard.gif b/docs/getting-started/images/tutorial-final-dashboard.gif new file mode 100644 index 0000000000000..53b7bc04c5f65 Binary files /dev/null and b/docs/getting-started/images/tutorial-final-dashboard.gif differ diff --git a/docs/getting-started/images/tutorial-sample-dashboard.png b/docs/getting-started/images/tutorial-sample-dashboard.png index ccce8c3bb3208..4c95c04c5e43e 100644 Binary files a/docs/getting-started/images/tutorial-sample-dashboard.png and b/docs/getting-started/images/tutorial-sample-dashboard.png differ diff --git a/docs/getting-started/images/tutorial-sample-discover1.png b/docs/getting-started/images/tutorial-sample-discover1.png deleted file mode 100644 index 1bad8774ba584..0000000000000 Binary files a/docs/getting-started/images/tutorial-sample-discover1.png and /dev/null differ diff --git a/docs/getting-started/images/tutorial-sample-discover2.png b/docs/getting-started/images/tutorial-sample-discover2.png deleted file mode 100644 index a439f1d76a991..0000000000000 Binary files a/docs/getting-started/images/tutorial-sample-discover2.png and /dev/null differ diff --git a/docs/getting-started/images/tutorial-sample-edit1.png b/docs/getting-started/images/tutorial-sample-edit1.png deleted file mode 100644 index b5ae56b5c0d83..0000000000000 Binary files a/docs/getting-started/images/tutorial-sample-edit1.png and /dev/null differ diff --git a/docs/getting-started/images/tutorial-sample-edit2.png b/docs/getting-started/images/tutorial-sample-edit2.png deleted file mode 100644 index 17a029a17e1b4..0000000000000 Binary files a/docs/getting-started/images/tutorial-sample-edit2.png and /dev/null differ diff --git a/docs/getting-started/images/tutorial-sample-filter.png b/docs/getting-started/images/tutorial-sample-filter.png index 770e26e951b3a..56ebacadbef45 100644 Binary files a/docs/getting-started/images/tutorial-sample-filter.png and b/docs/getting-started/images/tutorial-sample-filter.png differ diff --git a/docs/getting-started/images/tutorial-sample-filter2.png b/docs/getting-started/images/tutorial-sample-filter2.png new file mode 100644 index 0000000000000..21402feacdecd Binary files /dev/null and b/docs/getting-started/images/tutorial-sample-filter2.png differ diff --git a/docs/getting-started/images/tutorial-sample-inspect1.png b/docs/getting-started/images/tutorial-sample-inspect1.png deleted file mode 100644 index 6a3d41ae03584..0000000000000 Binary files a/docs/getting-started/images/tutorial-sample-inspect1.png and /dev/null differ diff --git a/docs/getting-started/images/tutorial-sample-query.png b/docs/getting-started/images/tutorial-sample-query.png index 847542c0b17ff..4f1ca24924b28 100644 Binary files a/docs/getting-started/images/tutorial-sample-query.png and b/docs/getting-started/images/tutorial-sample-query.png differ diff --git a/docs/getting-started/images/tutorial-sample-query2.png b/docs/getting-started/images/tutorial-sample-query2.png new file mode 100644 index 0000000000000..0e91e1069a201 Binary files /dev/null and b/docs/getting-started/images/tutorial-sample-query2.png differ diff --git a/docs/getting-started/images/tutorial-treemap.png b/docs/getting-started/images/tutorial-treemap.png new file mode 100644 index 0000000000000..32e14fd2308e3 Binary files /dev/null and b/docs/getting-started/images/tutorial-treemap.png differ diff --git a/docs/getting-started/images/tutorial-visualization-dropdown.png b/docs/getting-started/images/tutorial-visualization-dropdown.png new file mode 100644 index 0000000000000..29d1b99700964 Binary files /dev/null and b/docs/getting-started/images/tutorial-visualization-dropdown.png differ diff --git a/docs/getting-started/images/tutorial_index_patterns.png b/docs/getting-started/images/tutorial_index_patterns.png deleted file mode 100644 index 430baf898b612..0000000000000 Binary files a/docs/getting-started/images/tutorial_index_patterns.png and /dev/null differ diff --git a/docs/getting-started/quick-start-guide.asciidoc b/docs/getting-started/quick-start-guide.asciidoc new file mode 100644 index 0000000000000..f239b7ae6ca88 --- /dev/null +++ b/docs/getting-started/quick-start-guide.asciidoc @@ -0,0 +1,143 @@ +[[get-started]] +== Quick start + +To quickly get up and running with {kib}, set up on Cloud, then add a sample data set that you can explore and analyze. + +When you've finished, you'll know how to: + +* <> + +* <> + +[float] +=== Required privileges +When security is enabled, you must have `read`, `write`, and `manage` privileges on the `kibana_sample_data_*` indices. +For more information, refer to {ref}/security-privileges.html[Security privileges]. + +[float] +[[set-up-on-cloud]] +== Set up on cloud + +include::{docs-root}/shared/cloud/ess-getting-started.asciidoc[] + +[float] +[[gs-get-data-into-kibana]] +== Add the sample data + +Sample data sets come with sample visualizations, dashboards, and more to help you explore {kib} without adding your own data. + +. From the home page, click *Try our sample data*. + +. On the *Sample eCommerce orders* card, click *Add data*. ++ +[role="screenshot"] +image::getting-started/images/add-sample-data.png[Add data UI] + +[float] +[[explore-the-data]] +== Explore the data + +*Discover* displays an interactive histogram that shows the distribution of of data, or documents, over time, and a table that lists the fields for each document that matches the index. By default, all fields are shown for each matching document. + +. Open the main menu, then click *Discover*. + +. Change the <> to *Last 7 days*. ++ +[role="screenshot"] +image::images/tutorial-discover-2.png[] + +. To focus in on the documents you want to view, use the <>. In the *KQL* search field, enter: ++ +[source,text] +products.taxless_price >= 60 AND category : Women's Clothing ++ +The query returns the women's clothing orders for $60 and more. ++ +[role="screenshot"] +image::images/tutorial-discover-4.png[] + +. Hover over the list of *Available fields*, then click *+* next to the fields you want to view in the table. ++ +For example, when you add the *category* field, the table displays the product categories for the orders. ++ +[role="screenshot"] +image::images/tutorial-discover-3.png[] ++ +For more information, refer to <>. + +[float] +[[view-and-analyze-the-data]] +== View and analyze the data + +A dashboard is a collection of panels that you can use to view and analyze the data. Panels contain visualizations, interactive controls, Markdown, and more. + +. Open the main menu, then click *Dashboard*. + +. Click *[eCommerce] Revenue Dashboard*. ++ +[role="screenshot"] +image::getting-started/images/tutorial-sample-dashboard.png[] + +[float] +[[filter-and-query-the-data]] +=== Filter the data + +To focus in on the data you want to view on the dashboard, use filters. + +. From the *[eCommerce] Controls* panel, make a selection from the *Manufacturer* and *Category* dropdowns, then click *Apply changes*. ++ +For example, the following dashboard shows the data for women's clothing from Gnomehouse. ++ +[role="screenshot"] +image::getting-started/images/tutorial-sample-filter.png[] + +. To manually add a filter, click *Add filter*, then specify the options. ++ +For example, to view the orders for Wednesday, select *day_of_week* from the *Field* dropdown, select *is* from the *Operator* dropdown, then select *Wednesday* from the *Value* dropdown. ++ +[role="screenshot"] +image::getting-started/images/tutorial-sample-filter2.png[] + +. When you are done, remove the filters. ++ +For more information, refer to <>. + +[float] +[[create-a-visualization]] +=== Create a visualization panel + +To create a treemap panel that shows the top regions and manufacturers, use *Lens*, then add the treemap panel to the dashboard. + +. From the toolbar, click *Edit*, then click *Create new*. + +. On the *New Visualization* window, click *Lens*. + +. From the *Available fields* list, drag and drop the following fields to the visualization builder: + +* *geoip.city_name* + +* *manufacturer.keyword* ++ +. From the visualization dropdown, select *Treemap*. ++ +[role="screenshot"] +image::getting-started/images/tutorial-visualization-dropdown.png[Visualization dropdown with Treemap selected] + +. Click *Save*. + +. On the *Save Lens visualization*, enter a title and make sure *Add to Dashboard after saving* is selected, then click *Save and return*. ++ +The treemap appears as the last visualization panel on the dashboard. ++ +[role="screenshot"] +image::getting-started/images/tutorial-final-dashboard.gif[Final dashboard with new treemap visualization] ++ +For more information, refer to <>. + +[float] +[[quick-start-whats-next]] +== What's next? + +If you are you ready to add your own data, refer to <>. + +If you want to ingest your data, refer to {ingest-guide}/ingest-management-getting-started.html[Quick start: Get logs and metrics into the Elastic Stack]. diff --git a/docs/getting-started/tutorial-discovering.asciidoc b/docs/getting-started/tutorial-discovering.asciidoc deleted file mode 100644 index 99a07acf98791..0000000000000 --- a/docs/getting-started/tutorial-discovering.asciidoc +++ /dev/null @@ -1,35 +0,0 @@ -[[explore-your-data]] -=== Explore your data - -With *Discover*, you use {ref}/query-dsl-query-string-query.html#query-string-syntax[Elasticsearch -queries] to explore your data and narrow the results with filters. - -. Open the menu, then go to *Discover*. -+ -The `shakes*` index pattern appears. - -. To make `ba*` the index, click the *Change Index Pattern* dropdown, then select `ba*`. -+ -By default, all fields are shown for each matching document. - -. In the *Search* field, enter the following, then click *Update*: -+ -[source,text] -account_number<100 AND balance>47500 -+ -The search returns all account numbers between zero and 99 with balances in -excess of 47,500. Results appear for account numbers 8, 32, 78, 85, and 97. -+ -[role="screenshot"] -image::images/tutorial-discover-2.png[Image showing the search results for account numbers between zero and 99, with balances in excess of 47,500] -+ -. Hover over the list of *Available fields*, then -click *Add* next to each field you want include in the table. -+ -For example, when you add the `account_number` field, the display changes to a list of five -account numbers. -+ -[role="screenshot"] -image::images/tutorial-discover-3.png[Image showing a dropdown with five account numbers, which match the previous query for account balance] - -Now that you know what your documents contain, it's time to gain insight into your data with visualizations. diff --git a/docs/getting-started/tutorial-sample-data.asciidoc b/docs/getting-started/tutorial-sample-data.asciidoc deleted file mode 100644 index 18ef862272f85..0000000000000 --- a/docs/getting-started/tutorial-sample-data.asciidoc +++ /dev/null @@ -1,159 +0,0 @@ -[[explore-kibana-using-sample-data]] -== Explore {kib} using sample data - -Ready to get some hands-on experience with {kib}? -In this tutorial, you’ll work with {kib} sample data and learn to: - -* <> - -* <> - -* <> - -NOTE: If security is enabled, you must have `read`, `write`, and `manage` privileges -on the `kibana_sample_data_*` indices. For more information, refer to -{ref}/security-privileges.html[Security privileges]. - -[float] -[[add-the-sample-data]] -=== Add the sample data - -Add the *Sample flight data*. - -. On the home page, click *Load a data set and a {kib} dashboard*. - -. On the *Sample flight data* card, click *Add data*. - -[float] -[[explore-the-data]] -=== Explore the data - -Explore the documents in the index that -match the selected index pattern. The index pattern tells {kib} which {es} index you want to -explore. - -. Open the menu, then go to *Discover*. - -. Make sure `kibana_sample_data_flights` is the current index pattern. -You might need to click *New* in the {kib} toolbar to refresh the data. -+ -You'll see a histogram that shows the distribution of -documents over time. A table lists the fields for -each document that matches the index. By default, all fields are shown. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-discover1.png[] - -. Hover over the list of *Available fields*, then click *Add* next -to each field you want explore in the table. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-discover2.png[] - -[float] -[[view-and-analyze-the-data]] -=== View and analyze the data - -A _dashboard_ is a collection of panels that provide you with an overview of your data that you can -use to analyze your data. Panels contain everything you need, including visualizations, -interactive controls, Markdown, and more. - -To open the *Global Flight* dashboard, open the menu, then go to *Dashboard*. - -[role="screenshot"] -image::getting-started/images/tutorial-sample-dashboard.png[] - -[float] -[[change-the-panel-data]] -==== Change the panel data - -To gain insights into your data, change the appearance and behavior of the panels. -For example, edit the metric panel to find the airline that has the lowest average fares. - -. In the {kib} toolbar, click *Edit*. - -. In the *Average Ticket Price* metric panel, open the panel menu, then select *Edit visualization*. - -. To change the data on the panel, use an {es} {ref}/search-aggregations.html[bucket aggregation], -which sorts the documents that match your search criteria into different categories or buckets. - -.. In the *Buckets* pane, select *Add > Split group*. - -.. From the *Aggregation* dropdown, select *Terms*. - -.. From the *Field* dropdown, select *Carrier*. - -.. Set *Descending* to *4*, then click *Update*. -+ -The average ticket price for all four airlines appear in the visualization builder. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-edit1.png[] - -. To save your changes, click *Save and return* in the {kib} toolbar. - -. To save the dashboard, click *Save* in the {kib} toolbar. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-edit2.png[] - -[float] -[[filter-and-query-the-data]] -==== Filter and query the data - -To focus in on the data you want to explore, use filters and queries. -For more information, refer to -{ref}/query-filter-context.html[Query and filter context]. - -To filter the data: - -. In the *Controls* visualization, select an *Origin City* and *Destination City*, then click *Apply changes*. -+ -The `OriginCityName` and the `DestCityName` fields filter the data in the panels. -+ -For example, the following dashboard shows the data for flights from London to Milan. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-filter.png[] - -. To manually add a filter, click *Add filter*, -then specify the data you want to view. - -. When you are finished experimenting, remove all filters. - -[[query-the-data]] -To query the data: - -. To view all flights out of Rome, enter the following in the *KQL* query bar, then click *Update*: -+ -[source,text] -OriginCityName: Rome - -. For a more complex query with AND and OR, enter: -+ -[source,text] -OriginCityName:Rome AND (Carrier:JetBeats OR Carrier:"Kibana Airlines") -+ -The dashboard panels update to display the flights out of Rome on JetBeats and -{kib} Airlines. -+ -[role="screenshot"] -image::getting-started/images/tutorial-sample-query.png[] - -. When you are finished exploring, remove the query by -clearing the contents in the *KQL* query bar, then click *Update*. - -[float] -=== Next steps - -Now that you know the {kib} basics, try out the <> tutorial, where you'll learn to: - -* Add a data set to {kib} - -* Define an index pattern - -* Discover and explore data - -* Create and add panels to a dashboard - - diff --git a/docs/getting-started/tutorial-visualizing.asciidoc b/docs/getting-started/tutorial-visualizing.asciidoc deleted file mode 100644 index a53c8cb6bc23d..0000000000000 --- a/docs/getting-started/tutorial-visualizing.asciidoc +++ /dev/null @@ -1,193 +0,0 @@ -[[tutorial-visualizing]] -=== Visualize your data - -Shape your data using a variety -of {kib} supported visualizations, tables, and more. In this tutorial, you'll create four -visualizations that you'll use to create a dashboard. - -To begin, open the menu, go to *Dashboard*, then click *Create new dashboard*. - -[float] -[[compare-the-number-of-speaking-parts-in-the-play]] -=== Compare the number of speaking parts in the plays - -To visualize the Shakespeare data and compare the number of speaking parts in the plays, create a bar chart using *Lens*. - -. Click *Create new*, then click *Lens* on the *New Visualization* window. -+ -[role="screenshot"] -image::images/tutorial-visualize-wizard-step-1.png[Image showing different options for your new visualization] - -. Make sure the index pattern is *shakes*. - -. Display the play data along the x-axis. - -.. From the *Available fields* list, drag and drop *play_name* to the *X-axis* field. - -.. Click *Top values of play_name*. - -.. From the *Order direction* dropdown, select *Ascending*. - -.. In the *Label* field, enter `Play Name`. - -. Display the number of speaking parts per play along the y-axis. - -.. From the *Available fields* list, drag and drop *speaker* to the *Y-axis* field. - -.. Click *Unique count of speaker*. - -.. In the *Label* field, enter `Speaking Parts`. -+ -[role="screenshot"] -image::images/tutorial-visualize-bar-1.5.png[Bar chart showing the speaking parts data] - -. *Save* the chart with the name `Bar Example`. -+ -To show a tooltip with the number of speaking parts for that play, hover over a bar. -+ -Notice how the individual play names show up as whole phrases, instead of -broken up into individual words. This is the result of the mapping -you did at the beginning of the tutorial, when you marked the `play_name` field -as `not analyzed`. - -[float] -[[view-the-average-account-balance-by-age]] -=== View the average account balance by age - -To gain insight into the account balances in the bank account data, create a pie chart. In this tutorial, you'll use the {es} -{ref}/search-aggregations.html[bucket aggregation] to specify the pie slices to display. The bucket aggregation sorts the documents that match your search criteria into different -categories and establishes multiple ranges of account balances so that you can find how many accounts fall into each range. - -. Click *Create new*, then click *Pie* on the *New Visualization* window. - -. On the *Choose a source* window, select `ba*`. -+ -Since the default search matches all documents, the pie contains a single slice. - -. In the *Buckets* pane, click *Add > Split slices.* - -.. From the *Aggregation* dropdown, select *Range*. - -.. From the *Field* dropdown, select *balance*. - -.. Click *Add range* until there are six rows of fields, then define the following ranges: -+ -[source,text] -0 999 -1000 2999 -3000 6999 -7000 14999 -15000 30999 -31000 50000 - -. Click *Update*. -+ -The pie chart displays the proportion of the 1,000 accounts that fall into each of the ranges. -+ -[role="screenshot"] -image::images/tutorial-visualize-pie-2.png[Pie chart displaying accounts that fall into each of the ranges, scaled to 1000 accounts] - -. Add another bucket aggregation that displays the ages of the account holders. - -.. In the *Buckets* pane, click *Add*, then click *Split slices*. - -.. From the *Sub aggregation* dropdown, select *Terms*. - -.. From the *Field* dropdown, select *age*, then click *Update*. -+ -The break down of the ages of the account holders are displayed -in a ring around the balance ranges. -+ -[role="screenshot"] -image::images/tutorial-visualize-pie-3.png[Final pie chart showing all of the changes] - -. Click *Save*, then enter `Pie Example` in the *Title* field. - -[float] -[role="xpack"] -[[visualize-geographic-information]] -=== Visualize geographic information - -To visualize geographic information in the log file data, use <>. - -. Click *Create new*, then click *Maps* on the *New Visualization* window. - -. To change the time, use the time filter. - -.. Set the *Start date* to `May 18, 2015 @ 12:00:00.000`. - -.. Set the *End date* to `May 20, 2015 @ 12:00:00.000`. -+ -[role="screenshot"] -image::images/gs_maps_time_filter.png[Image showing the time filter for Maps tutorial] - -.. Click *Update* - -. Map the geo coordinates from the log files. - -.. Click *Add layer > Clusters and grids*. - -.. From the *Index pattern* dropdown, select *logstash*. - -.. Click *Add layer*. - -. Specify the *Layer Style*. - -.. From the *Fill color* dropdown, select the yellow to red color ramp. - -.. In the *Border width* field, enter `3`. - -.. From the *Border color* dropdown, select *#FFF*, then click *Save & close*. -+ -[role="screenshot"] -image::images/tutorial-visualize-map-2.png[Example of a map visualization] - -. Click *Save*, then enter `Map Example` in the *Title* field. - -. Add the map to your dashboard. - -.. Open the menu, go to *Dashboard*, then click *Add*. - -.. On the *Add panels* flyout, click *Map Example*. - -[float] -[[tutorial-visualize-markdown]] -=== Add context to your visualizations with Markdown - -Add context to your new visualizations with Markdown text. - -. Click *Create new*, then click *Markdown* on the *New Visualization* window. - -. In the *Markdown* text field, enter: -+ -[source,markdown] -# This is a tutorial dashboard! -The Markdown widget uses **markdown** syntax. -> Blockquotes in Markdown use the > character. - -. Click *Update*. -+ -The Markdown renders in the preview pane. -+ -[role="screenshot"] -image::images/tutorial-visualize-md-2.png[Image showing example markdown editing field] - -. Click *Save*, then enter `Markdown Example` in the *Title* field. - -[role="screenshot"] -image::images/tutorial-dashboard.png[Final visualization with bar chart, pie chart, map, and markdown text field] - -[float] -=== Next steps - -Now that you have the basics, you're ready to start exploring your own system data with {kib}. - -* To add your own data to {kib}, refer to <>. - -* To search and filter your data, refer to {kibana-ref}/discover.html[Discover]. - -* To create a dashboard with your own data, refer to <>. - -* To create maps that you can add to your dashboards, refer to <>. - -* To create presentations of your live data, refer to <>. diff --git a/docs/images/Dashboard_add_new_visualization.png b/docs/images/Dashboard_add_new_visualization.png index 3685f9c5c9a74..5f73b2f1adde2 100644 Binary files a/docs/images/Dashboard_add_new_visualization.png and b/docs/images/Dashboard_add_new_visualization.png differ diff --git a/docs/images/Dashboard_add_visualization.png b/docs/images/Dashboard_add_visualization.png index b1b86d47e5982..4caa34ef3d082 100644 Binary files a/docs/images/Dashboard_add_visualization.png and b/docs/images/Dashboard_add_visualization.png differ diff --git a/docs/images/Dashboard_example.png b/docs/images/Dashboard_example.png index 1a80f4b3bdf07..c2e338d0fd31b 100644 Binary files a/docs/images/Dashboard_example.png and b/docs/images/Dashboard_example.png differ diff --git a/docs/images/Dashboard_inspect.png b/docs/images/Dashboard_inspect.png index d65b968e043a6..635eef4a017f6 100644 Binary files a/docs/images/Dashboard_inspect.png and b/docs/images/Dashboard_inspect.png differ diff --git a/docs/images/Discover-Start.png b/docs/images/Discover-Start.png index fb885c20c1cf7..12ec2f9889bbd 100644 Binary files a/docs/images/Discover-Start.png and b/docs/images/Discover-Start.png differ diff --git a/docs/images/canvas-autoplay-interval.png b/docs/images/canvas-autoplay-interval.png index 68a7ca248d9ee..a7b1251efc808 100644 Binary files a/docs/images/canvas-autoplay-interval.png and b/docs/images/canvas-autoplay-interval.png differ diff --git a/docs/images/canvas-gs-example.png b/docs/images/canvas-gs-example.png index 90beccd322aa4..a9b960342709f 100644 Binary files a/docs/images/canvas-gs-example.png and b/docs/images/canvas-gs-example.png differ diff --git a/docs/images/canvas-refresh-interval.png b/docs/images/canvas-refresh-interval.png index 62e88ad4bf7d0..c097d950a7ec7 100644 Binary files a/docs/images/canvas-refresh-interval.png and b/docs/images/canvas-refresh-interval.png differ diff --git a/docs/images/intro-dashboard.png b/docs/images/intro-dashboard.png index fe4e6f620d19c..bb4e98a516fb7 100644 Binary files a/docs/images/intro-dashboard.png and b/docs/images/intro-dashboard.png differ diff --git a/docs/images/intro-data-tutorial.png b/docs/images/intro-data-tutorial.png index 2882a092fbb0b..781e134605b87 100644 Binary files a/docs/images/intro-data-tutorial.png and b/docs/images/intro-data-tutorial.png differ diff --git a/docs/images/intro-discover.png b/docs/images/intro-discover.png index 54e5725596421..134804941a356 100644 Binary files a/docs/images/intro-discover.png and b/docs/images/intro-discover.png differ diff --git a/docs/images/intro-kibana.png b/docs/images/intro-kibana.png index 62c2c99826131..3d10a31d7e380 100644 Binary files a/docs/images/intro-kibana.png and b/docs/images/intro-kibana.png differ diff --git a/docs/images/management-index-templates-mappings.png b/docs/images/management-index-templates-mappings.png deleted file mode 100755 index 62321fc0e4666..0000000000000 Binary files a/docs/images/management-index-templates-mappings.png and /dev/null differ diff --git a/docs/images/management-index-templates.png b/docs/images/management-index-templates.png deleted file mode 100755 index 6f2564af72b5c..0000000000000 Binary files a/docs/images/management-index-templates.png and /dev/null differ diff --git a/docs/images/management_index_create_wizard.png b/docs/images/management_index_create_wizard.png deleted file mode 100755 index b18c36366be94..0000000000000 Binary files a/docs/images/management_index_create_wizard.png and /dev/null differ diff --git a/docs/images/management_index_details.png b/docs/images/management_index_details.png deleted file mode 100644 index 77aeaba472307..0000000000000 Binary files a/docs/images/management_index_details.png and /dev/null differ diff --git a/docs/images/management_index_labels.png b/docs/images/management_index_labels.png deleted file mode 100755 index 79e378e367e78..0000000000000 Binary files a/docs/images/management_index_labels.png and /dev/null differ diff --git a/docs/images/tutorial-discover-2.png b/docs/images/tutorial-discover-2.png index 681e4834de830..cf217562c37fd 100644 Binary files a/docs/images/tutorial-discover-2.png and b/docs/images/tutorial-discover-2.png differ diff --git a/docs/images/tutorial-discover-3.png b/docs/images/tutorial-discover-3.png index bbab47acaf9d4..b024ad6dc39fe 100644 Binary files a/docs/images/tutorial-discover-3.png and b/docs/images/tutorial-discover-3.png differ diff --git a/docs/infrastructure/images/infra-sysmon.png b/docs/infrastructure/images/infra-sysmon.png deleted file mode 100644 index dd653bb046f45..0000000000000 Binary files a/docs/infrastructure/images/infra-sysmon.png and /dev/null differ diff --git a/docs/infrastructure/index.asciidoc b/docs/infrastructure/index.asciidoc deleted file mode 100644 index 81a3022436a7e..0000000000000 --- a/docs/infrastructure/index.asciidoc +++ /dev/null @@ -1,32 +0,0 @@ -[chapter] -[role="xpack"] -[[xpack-infra]] -= Metrics - -The {metrics-app} in {kib} enables you to monitor your infrastructure metrics and identify problems in real time. -You start with a visual summary of your infrastructure where you can view basic metrics for common servers, containers, and services. -Then you can drill down to view more detailed metrics or other information for that component. - -You can: - -* View your infrastructure metrics by hosts, Kubernetes pods, or Docker containers. -You can group and filter the data in various ways to help you identify the items that interest you. - -* View current and historic values for metrics such as CPU usage, memory usage, and network traffic for each component. -The available metrics depend on the kind of component being inspected. - -* Use *Metrics Explorer* to group and visualize multiple customizable metrics for one or more components in a graphical format. -You can optionally save these views and add them to {kibana-ref}/dashboard.html[dashboards]. - -* Seamlessly switch to view the corresponding logs, application traces or uptime information for a component. - -* Create alerts based on metric thresholds for one or more components. - -[role="screenshot"] -image::infrastructure/images/infra-sysmon.png[Infrastructure Overview in Kibana] - -[float] -=== Get started - -To get started with Metrics, refer to {metrics-guide}/install-metrics-monitoring.html[Install Metrics]. - diff --git a/docs/ingest_manager/images/ingest-manager-start.png b/docs/ingest_manager/images/ingest-manager-start.png deleted file mode 100644 index 89174686a9768..0000000000000 Binary files a/docs/ingest_manager/images/ingest-manager-start.png and /dev/null differ diff --git a/docs/logs/images/logs-console.png b/docs/logs/images/logs-console.png deleted file mode 100644 index ddd3346475da6..0000000000000 Binary files a/docs/logs/images/logs-console.png and /dev/null differ diff --git a/docs/logs/index.asciidoc b/docs/logs/index.asciidoc deleted file mode 100644 index 45d4321f40556..0000000000000 --- a/docs/logs/index.asciidoc +++ /dev/null @@ -1,21 +0,0 @@ -[chapter] -[role="xpack"] -[[xpack-logs]] -= Logs - -The Logs app in Kibana enables you to explore logs for common servers, containers, and services. - -The Logs app has a compact, console-like display that you can customize. -You can filter the logs by various fields, start and stop live streaming, and highlight text of interest. - -You can open the Logs app from the *Logs* tab in Kibana. -You can also open the Logs app directly from a component in the Metrics app. -In this case, you will only see the logs for the selected component. - -[role="screenshot"] -image::logs/images/logs-console.png[Logs Console in Kibana] - -[float] -=== Get started - -To get started with Elastic Logs, refer to {logs-guide}/install-logs-monitoring.html[Install Logs]. diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index a29909701d64a..8673189e6dec8 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -6,11 +6,18 @@ behavior of Kibana. For example, you can change the format used to display dates specify the default index pattern, and set the precision for displayed decimal values. -. Open the menu, then go to *Stack Management > {kib} > Advanced Settings*. +. Open the main menu, then click *Stack Management > Advanced Settings*. . Scroll or search for the setting you want to modify. . Enter a new value for the setting. . Click *Save changes*. +[float] +=== Required permissions + +The `Advanced Settings` {kib} privilege is required to access *Advanced Settings*. + +To add the privilege, open the menu, then click *Stack Management > Roles*. + [float] [[settings-read-only-access]] @@ -455,9 +462,6 @@ The opacity of the chart items that are dimmed when highlighting another element of the chart. The lower this number, the more the highlighted element stands out. This must be a number between 0 and 1. -[[visualization-loadingdelay]]`visualization:loadingDelay`:: -The time to wait before dimming visualizations during a query. - [[visualization-regionmap-showwarnings]]`visualization:regionmap:showWarnings`:: Shows a warning in a region map when terms cannot be joined to a shape. diff --git a/docs/management/alerting/alert-management.asciidoc b/docs/management/alerting/alert-management.asciidoc index 73cf40c4d7c40..f348812550978 100644 --- a/docs/management/alerting/alert-management.asciidoc +++ b/docs/management/alerting/alert-management.asciidoc @@ -4,7 +4,7 @@ beta[] -The *Alerts* tab provides a cross-app view of alerting. Different {kib} apps like <>, <>, <>, and <> can offer their own alerts, and the *Alerts* tab provides a central place to: +The *Alerts* tab provides a cross-app view of alerting. Different {kib} apps like <>, <>, <>, and <> can offer their own alerts, and the *Alerts* tab provides a central place to: * <> alerts * <> including enabling/disabling, muting/unmuting, and deleting @@ -39,7 +39,7 @@ image::images/alerts-filter-by-action-type.png[Filtering the alert list by type [[create-edit-alerts]] ==== Creating and editing alerts -Many alerts must be created within the context of a {kib} app like <>, <>, or <>, but others are generic. Generic alert types can be created in the *Alerts* management UI by clicking the *Create* button. This will launch a flyout that guides you through selecting an alert type and configuring it's properties. Refer to <> for details on what types of alerts are available and how to configure them. +Many alerts must be created within the context of a {kib} app like <>, <>, or <>, but others are generic. Generic alert types can be created in the *Alerts* management UI by clicking the *Create* button. This will launch a flyout that guides you through selecting an alert type and configuring it's properties. Refer to <> for details on what types of alerts are available and how to configure them. After an alert is created, you can re-open the flyout and change an alerts properties by clicking the *Edit* button shown on each row of the alert listing. diff --git a/docs/management/alerting/alerts-and-actions-intro.asciidoc b/docs/management/alerting/alerts-and-actions-intro.asciidoc index 429d7915cc1c3..35a2452e99951 100644 --- a/docs/management/alerting/alerts-and-actions-intro.asciidoc +++ b/docs/management/alerting/alerts-and-actions-intro.asciidoc @@ -6,8 +6,8 @@ beta[] The *Alerts and Actions* UI lets you <> in a space, and provides tools to <> so that alerts can trigger actions like notification, indexing, and ticketing. -To manage alerting and connectors, open the menu, -then go to *Stack Management > Alerts and Insights > Alerts and Actions*. +To manage alerting and connectors, open the main menu, +then click *Stack Management > Alerts and Insights > Alerts and Actions*. [role="screenshot"] image:management/alerting/images/alerts-and-actions-ui.png[Example alert listing in the Alerts and Actions UI] @@ -24,3 +24,8 @@ The *Alerts and Actions* UI only shows alerts and connectors for the current spa can be managed through the <>. See <> for more information. ============================================================================ + +[float] +=== Required permissions + +Access to alerts and actions is granted based on your privileges to alerting-enabled features. See <> for more information. diff --git a/docs/management/index-lifecycle-policies/add-policy-to-index.asciidoc b/docs/management/index-lifecycle-policies/add-policy-to-index.asciidoc deleted file mode 100644 index 0fec62d895754..0000000000000 --- a/docs/management/index-lifecycle-policies/add-policy-to-index.asciidoc +++ /dev/null @@ -1,17 +0,0 @@ -[role="xpack"] -[[adding-policy-to-index]] -=== Adding a policy to an index - -To add a lifecycle policy to an index and view the status for indices -managed by a policy, open the menu, then go to *Stack Management > Data > Index Management*. -This page lists your -{es} indices, which you can filter by lifecycle status and lifecycle phase. - -To add a policy, select the index name and then select *Manage Index > Add lifecycle policy*. -You’ll see the policy name, the phase the index is in, the current -action, and if any errors occurred performing that action. - -To remove a policy from an index, select *Manage Index > Remove lifecycle policy*. - -[role="screenshot"] -image::images/index_management_add_policy.png[][UI for adding a policy to an index] diff --git a/docs/management/index-lifecycle-policies/create-policy.asciidoc b/docs/management/index-lifecycle-policies/create-policy.asciidoc deleted file mode 100644 index 7849ef6b92054..0000000000000 --- a/docs/management/index-lifecycle-policies/create-policy.asciidoc +++ /dev/null @@ -1,93 +0,0 @@ -[role="xpack"] -[[creating-index-lifecycle-policies]] -=== Creating an index lifecycle policy - -An index lifecycle policy enables you to define rules over when to perform -certain actions, such as a rollover or force merge, on an index. Index lifecycle -management automates execution of those actions at the right time. - -When you create an index lifecycle policy, consider the tradeoffs between -performance and availability. As you move your index through the lifecycle, -you’re likely moving your data to less performant hardware and reducing the -number of shards and replicas. It’s important to ensure that the index -continues to have enough replicas to prevent data loss in the event of failures. - -*Index Lifecycle Policies* is automatically enabled in {kib}. Open the menu, then go to -*Stack Management > {es} > Index Lifecycle Policies*. - -NOTE: If you don’t want to use this feature, you can disable it by setting -`xpack.ilm.enabled` to false in your `kibana.yml` configuration file. If you -disable *Index Management*, then *Index Lifecycle Policies* is also disabled. - -[role="screenshot"] -image::images/index-lifecycle-policies-create.png[][UI for creating an index lifecycle policy] - -==== Defining the phases of the index lifecycle - -You can define up to four phases in the index lifecycle. For each phase, you -can enable actions to optimize performance for that phase. - -The four phases in the index lifecycle are: - -* *Hot.* The index is actively being queried and written to. You can -roll over to a new index when the -original index reaches a specified size, document count, or age. When a rollover occurs, a new -index is created, added to the index alias, and designated as the new “hot” -index. You can still query the previous indices, but you only ever write to -the “hot” index. See <>. - -* *Warm.* The index is typically searched at a lower rate than when the data is -hot. The index is not used for storing new data, but might occasionally add -late-arriving data, for example, from a Beat with a network problem that's now fixed. -You can optionally shrink the number replicas and move the shards to a -different set of nodes with smaller or less performant hardware. You can also -reduce the number of primary shards and force merge the index into -smaller {ref}/indices-segments.html[segments]. - -* *Cold.* The index is no longer being updated and is seldom queried, but is -still searchable. If you have a big deployment, you can move it to even -less performant hardware. You might also reduce the number of replicas because -you expect the data to be queried less frequently. To keep the index searchable -for a longer period, and reduce the hardware requirements, you can use the -{ref}/frozen-indices.html[freeze action]. Queries are slower on a frozen index because the index is -reloaded from the disk to RAM on demand. - -* *Delete.* The index is no longer relevant. You can define when it is safe to -delete it. - -The index lifecycle always includes an active hot phase. The warm, cold, and -delete phases are optional. For example, you might define all four phases for -one policy and only a hot and delete phase for another. See {ref}/_actions.html[Actions] -for more information on the actions available in each phase. - -[[setting-a-rollover-action]] -==== Setting a rollover action - -The {ref}/indices-rollover-index.html[rollover] action enables you to automatically roll over to a new index based -on the index size, document count, or age. Rolling over to a new index based on -these criteria is preferable to time-based rollovers. Rolling over at an arbitrary -time often results in many small indices, which can have a negative impact on performance and resource usage. - -When you create an index lifecycle policy, the rollover action is enabled -by default. The default size for triggering the rollover is 50 gigabytes, and -the default age is 30 days. The rollover occurs when any of the criteria are met. - -With the rollover action enabled, you can move to the warm phase on rollover or you can -time the move for a specified number of hours or days after the rollover. The -move to the cold and delete phases is based on the time from the rollover. - -If you are using daily indices (created by Logstash or another client) and you -want to use the index lifecycle policy to manage aging data, you can -disable the rollover action in the hot phase. You can then -transition to the warm, cold, and delete phases based on the time of index creation. - -==== Setting the index priority - -For the hot, warm, and cold phases, you can set a priority for recovering -indices after a node restart. Indices with higher priorities are recovered -before indices with lower priorities. By default, the index priority is set to -100 in the hot phase, 50 in the warm phase, and 0 in the cold phase. -If the cold phase of one index has data that -is more important than the data in the hot phase of another, you might increase -the index priority in the cold phase. See -{ref}/recovery-prioritization.html[Index recovery prioritization]. diff --git a/docs/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc b/docs/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc deleted file mode 100644 index ba1d79710de05..0000000000000 --- a/docs/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc +++ /dev/null @@ -1,30 +0,0 @@ -[role="xpack"] -[[index-lifecycle-policies]] -== Index Lifecycle Policies - -If you're working with time series data, you don't want to continually dump -everything into a single index. Instead, you might periodically roll over the -data to a new index to keep it from growing so big it's slow and expensive. -As the index ages and you query it less frequently, you’ll likely move it to -less expensive hardware and reduce the number of shards and replicas. - -To automatically move an index through its lifecycle, you can create a policy -to define actions to perform on the index as it ages. Index lifecycle policies -are especially useful when working with {beats-ref}/beats-reference.html[Beats] -data shippers, which continually -send operational data, such as metrics and logs, to Elasticsearch. You can -automate a rollover to a new index when the existing index reaches a specified -size or age. This ensures that all indices have a similar size instead of having -daily indices where size can vary based on the number of Beats and the number -of events sent. - -{kib}’s *Index Lifecycle Policies* walks you through the process for creating -and configuring a policy. Before using this feature, you should be familiar -with index lifecycle management: - -* For an introduction, refer to -{ref}/getting-started-index-lifecycle-management.html[Getting started with index -lifecycle management]. -* To dig into the concepts and technical details, see -{ref}/index-lifecycle-management.html[Managing the index lifecycle]. -* To check out the APIs, see {ref}/index-lifecycle-management-api.html[Index lifecycle management API]. diff --git a/docs/management/index-lifecycle-policies/manage-policy.asciidoc b/docs/management/index-lifecycle-policies/manage-policy.asciidoc deleted file mode 100644 index 8e2dc96de4b99..0000000000000 --- a/docs/management/index-lifecycle-policies/manage-policy.asciidoc +++ /dev/null @@ -1,34 +0,0 @@ -[role="xpack"] -[[managing-index-lifecycle-policies]] -=== Managing index lifecycle policies - -Your configured policies appear on the *Index lifecycle policies* page. -You can update an existing index lifecycle policy to fix errors or change -strategies for newly created indices. To edit a policy, select its name. - -[role="screenshot"] -image::images/index_lifecycle_policies_options.png[][UI for viewing and editing an index lifecycle policy] - -In addition, you can: - -* *View indices linked to the policy.* This is important when editing a policy. -Any changes you make affect all indices attached to the policy. The settings -for the current phase are cached, so the update doesn’t affect that phase. This -prevents conflicts when you’re modifying a phase that is currently executing on -an index. The changes takes effect when the next phase in the index lifecycle begins. - -* *Add the policy to an index template.* When an index is automatically -created using the index template, the policy is applied. If the index is rolled -over, the policies for any matching index templates are applied to the newly -created index. For more information, see {ref}/indices-templates.html[Index templates]. - -* *Delete a policy.* You can’t delete a policy that is currently in use or -recover a deleted index. - -[float] -=== Required permissions - -The `manage_ilm` cluster privilege is required to access *Index lifecycle policies*. - -You can add these privileges in *Stack Management > Security > Roles*. - diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 7de2a042160e9..e83e6d262f26c 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -25,8 +25,8 @@ image::images/management-index-read-only-badge.png[Example of Index Pattern Mana [[settings-create-pattern]] === Create an index pattern -When you don't have an index pattern, {kib} prompts you to create one. Or, you can open the menu, -then go to *Stack Management > {kib} > Index Patterns* to go directly to the *Index Patterns* UI. +When you don't have an index pattern, {kib} prompts you to create one. Or, you can open the main menu, +then click *Stack Management > Index Patterns*. [role="screenshot"] image:management/index-patterns/images/rollup-index-pattern.png["Menu with rollup index pattern"] diff --git a/docs/management/index-patterns/images/index-pattern-ui.png b/docs/management/index-patterns/images/index-pattern-ui.png new file mode 100644 index 0000000000000..7d16540aa03a2 Binary files /dev/null and b/docs/management/index-patterns/images/index-pattern-ui.png differ diff --git a/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png b/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png old mode 100755 new mode 100644 index 8d8b8aa4b42e3..2de7449affd0c Binary files a/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png and b/docs/management/ingest-pipelines/images/ingest-pipeline-processor.png differ diff --git a/docs/management/ingest-pipelines/ingest-pipelines.asciidoc b/docs/management/ingest-pipelines/ingest-pipelines.asciidoc index da2d3b8accac2..d9745bfef524a 100644 --- a/docs/management/ingest-pipelines/ingest-pipelines.asciidoc +++ b/docs/management/ingest-pipelines/ingest-pipelines.asciidoc @@ -7,7 +7,7 @@ pipelines that perform common transformations and enrichments on your data. For example, you might remove a field, rename an existing field, or set a new field. -You’ll find *Ingest Node Pipelines* in *Stack Management > Ingest*. With this feature, you can: +To begin, open the main menu, then click *Stack Management > Ingest Node Pipelines*. With *Ingest Node Pipelines*, you can: * View a list of your pipelines and drill down into details. * Create a pipeline that defines a series of tasks, known as processors. @@ -23,7 +23,7 @@ image:management/ingest-pipelines/images/ingest-pipeline-list.png["Ingest node p The minimum required permissions to access *Ingest Node Pipelines* are the `manage_pipeline` and `cluster:monitor/nodes/info` cluster privileges. -You can add these privileges in *Stack Management > Security > Roles*. +To add privileges, open the main menu, then click *Stack Management > Roles*. [role="screenshot"] image:management/ingest-pipelines/images/ingest-pipeline-privileges.png["Privileges required for Ingest Node Pipelines"] @@ -62,11 +62,40 @@ You also want to know where the request is coming from. . In *Ingest Node Pipelines*, click *Create a pipeline*. . Provide a name and description for the pipeline. -. Define the processors: +. Add a grok processor to parse the log message: + +.. Click *Add a processor* and select the *Grok* processor type. +.. Set the field input to `message` and enter the following grok pattern: + [source,js] ---------------------------------- -[ +%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "%{WORD:verb} %{DATA:request} HTTP/%{NUMBER:httpversion}" %{NUMBER:response:int} (?:-|%{NUMBER:bytes:int}) %{QS:referrer} %{QS:agent} +---------------------------------- ++ +.. Click *Update* to save the processor. + +. Add processors to map the date, IP, and user agent fields. + +.. Map the appropriate field to each processor type: ++ +-- +* **Date**: `timestamp` +* **GeoIP**: `clientip` +* **User agent**: `agent` + +For the **Date** processor, you also need to specify the date format you want to use: `dd/MMM/YYYY:HH:mm:ss Z`. +-- +Your form should look similar to this: ++ +[role="screenshot"] +image:management/ingest-pipelines/images/ingest-pipeline-processor.png["Processors for Ingest Node Pipelines"] ++ +Alternatively, you can click the **Import processors** link and define the processors as JSON: ++ +[source,js] +---------------------------------- +{ + "processors": [ { "grok": { "field": "message", @@ -90,19 +119,16 @@ You also want to know where the request is coming from. } } ] +} ---------------------------------- + -This code defines four {ref}/ingest-processors.html[processors] that run sequentially: +The four {ref}/ingest-processors.html[processors] will run sequentially: {ref}/grok-processor.html[grok], {ref}/date-processor.html[date], -{ref}/geoip-processor.html[geoip], and {ref}/user-agent-processor.html[user_agent]. -Your form should look similar to this: -+ -[role="screenshot"] -image:management/ingest-pipelines/images/ingest-pipeline-processor.png["Processors for Ingest Node Pipelines"] +{ref}/geoip-processor.html[geoip], and {ref}/user-agent-processor.html[user_agent]. You can reorder processors using the arrow icon next to each processor. -. To verify that the pipeline gives the expected outcome, click *Test pipeline*. +. To test the pipeline to verify that it produces the expected results, click *Add documents*. -. In the *Document* tab, provide the following sample document for testing: +. In the *Documents* tab, provide a sample document for testing: + [source,js] ---------------------------------- diff --git a/docs/management/managing-beats.asciidoc b/docs/management/managing-beats.asciidoc index 678e160b99af0..232efb60cadd3 100644 --- a/docs/management/managing-beats.asciidoc +++ b/docs/management/managing-beats.asciidoc @@ -4,7 +4,7 @@ include::{asciidoc-dir}/../../shared/discontinued.asciidoc[tag=cm-discontinued] -To use {beats} Central Management UI, open the menu, go to *Stack Management > Ingest > +To use {beats} Central Management, open the main menu, click *Stack Management > {beats} Central Management*, then define and manage configurations in a central location in {kib} and quickly deploy configuration changes to all {beats} running across your enterprise. For more @@ -18,8 +18,8 @@ about central management, see the related {beats} documentation: This feature requires an Elastic license that includes {beats} central management. -Don't have a license? You can start a 30-day trial. Open the menu, -go to *Stack Management > Stack > License Management*. At the end of the trial +Don't have a license? You can start a 30-day trial. Open the main menu, then +click *Stack Management > License Management*. At the end of the trial period, you can purchase a subscription to keep using central management. For more information, see https://www.elastic.co/subscriptions and <>. @@ -29,6 +29,13 @@ more information, see https://www.elastic.co/subscriptions and enrollment and configuration process step by step the first time you use the Central Management UI. +[float] +=== Required permissions + +You must have the `beats_admin` role assigned to use **{beats} Central Management** + +To assign the role, open the menu, then click *Stack Management > Users*. + [float] === Enroll {beats} diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index ad3a0ef0fcdd1..3734655edd91b 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -7,6 +7,13 @@ the index patterns that retrieve your data from {es}. [role="screenshot"] image::images/management-index-patterns.png[] +[float] +=== Required permissions + +The `Index Pattern Management` {kib} privilege is required to access the *Index patterns* UI. + +To add the privilege, open the menu, then click *Stack Management > Roles*. + [float] === Create an index pattern @@ -134,7 +141,7 @@ https://www.elastic.co/blog/using-painless-kibana-scripted-fields[Using Painless [[create-scripted-field]] === Create a scripted field -. Open the menu, then go to *Stack Management > {kib} > Index Patterns* +. Open the main menu, then click *Stack Management > Index Patterns*. . Select the index pattern you want to add a scripted field to. . Go to the *Scripted fields* tab for the index pattern, then click *Add scripted field*. . Enter a name for the scripted field. diff --git a/docs/management/managing-indices.asciidoc b/docs/management/managing-indices.asciidoc deleted file mode 100644 index 24cd094c877c6..0000000000000 --- a/docs/management/managing-indices.asciidoc +++ /dev/null @@ -1,198 +0,0 @@ -[role="xpack"] -[[managing-indices]] -== Index Management - -*Index Management* enables you to view index settings, -mappings, and statistics and perform index-level operations. -These include refreshing, flushing, clearing the cache, force merging segments, -freezing indices, and more. Practicing good index management helps ensure -that your data is stored in the most cost-effective way possible. - -*Index Management* also helps you create index templates. A template reduces -the amount of bookkeeping when working with indices. Instead of manually -setting up your indices, you can create them automatically from a template, -ensuring that your settings, mappings, and aliases are consistently defined. - -To manage your indices, open the menu, then go to *Stack Management > Data > Index Management*. - -[role="screenshot"] -image::images/management_index_labels.png[Index Management UI] - -If security is enabled, -you must have the `monitor` cluster privilege and the `view_index_metadata` -and `manage` index privileges to view the data. -For index templates, you must have the `manage_index_templates` cluster privilege. -See {ref}/security-privileges.html[Security privileges] for more -information. - -Before using this feature, you should be familiar with index management -operations. Refer to the {ref}/indices.html[index management APIs] -and the {ref}/indices-templates.html[index template APIs]. - -[float] -=== View and edit indices - -When you open *Index Management*, you’re presented an overview of your configured indices. -Badges indicate if an index is {ref}/frozen-indices.html[frozen], -a {ref}/ccr-put-follow.html[follower index], -or a {ref}/rollup-get-rollup-index-caps.html[rollup index]. - -Clicking a badge narrows the list to only indices of that type. -You can also filter your indices using the search bar. - -You can drill down into each index to investigate the index -{ref}/index-modules.html#index-modules-settings[settings], {ref}/mapping.html[mapping], and statistics. -From this view, you can also edit the index settings. - -[role="screenshot"] -image::images/management_index_details.png[Index Management UI] - -[float] -=== Perform index-level operations - -Use the *Manage* menu to perform index-level operations. This menu -is available in the index details view, or when you select the checkbox of one or more -indices on the overview page. The menu includes the following actions: - -* *Close index*. Blocks the index from read/write operations. -A closed index exists in the cluster, but doesn't consume resources -other than disk space. If you reopen a closed index, it goes through the -normal recovery process. - -* *Force merge index*. Reduces the number of segments in your shard by -merging smaller files and clearing deleted ones. Only force merge a read-only index. - -* *Refresh index*. Writes the operations in the indexing buffer to the -filesystem cache. This action is automatically performed once per second. Forcing a manual -refresh is useful during testing, but should not be routinely done in -production because it has a performance impact. - -* *Clear index cache*. Clears all caches associated with the index. - -* *Flush index*. Frees memory by syncing the filesystem cache to disk and -clearing the cache. Once the sync is complete, the internal transaction log is reset. - -* *Freeze index*. Makes the index read-only and reduces its memory footprint -by moving shards to disk. Frozen indices remain -searchable, but queries take longer. - -* *Delete index*. Permanently removes the index and all of its documents. - -* *Add lifecycle policy*. Specifies a policy for managing the lifecycle of the -index. - -[float] -[[manage-index-templates]] -=== Manage index templates - -An index template defines {ref}/index-modules.html#index-modules-settings[settings], -{ref}/mapping.html[mappings], and {ref}/indices-add-alias.html[aliases] -that you can automatically apply when creating a new index. {es} applies a -template to a new index based on an index pattern that matches the index name. - -The *Index Templates* view lists your templates and enables you to examine, edit, clone, and -delete them. Changes you make to an index template -do not affect existing indices. - -[role="screenshot"] -image::images/management-index-templates.png[Index templates] - -If you don't have any templates, you can create one using the *Create template* wizard. -Index templates are applied during index creation, -so you must create the -template before you create the indices. - -[float] -==== Example: Create an index template - -In this example, you’ll create an index template for randomly generated log files. - -Open the *Create template* wizard, and enter `logs_template` in the *Name* -field. Set *Index pattern* to `logstash*` so the template matches any index -with that index pattern. The merge order and version are both optional, -and you'll leave them blank in this example. - -[role="screenshot"] -image::images/management_index_create_wizard.png[Create wizard] - -The second step in the *Create template* wizard allows you to define index settings. -These settings are optional, and this example skips this step. - -The logs data set requires a -mapping to label the latitude and longitude pairs as geographic locations -by applying the geo_point type. In the third step of the wizard, define this mapping -under the *Mapped fields* tab as follows: - -[role="screenshot"] -image::images/management-index-templates-mappings.png[Mapped fields page] - -Alternatively, you can click the *Load JSON* link and define the mapping as JSON: - -[source,js] ----------------------------------- -{ - "properties": { - "geo": { - "properties": { - "coordinates": { - "type": "geo_point" - } - } - } - } -} ----------------------------------- - -You can create additional mapping configurations in the *Dynamic templates* and -*Advanced options* tabs. No additional mappings are required for this example. - -In the fourth step, define an alias named `logstash`. - -[source,js] ----------------------------------- -{ - "logstash": {} -} ----------------------------------- - -A summary of the template is in step 5. If everything looks right, click *Create template*. - -At this point, you’re ready to use the {es} index API to load the logs data. -In the {kib} *Console*, index two documents: - -[source,js] ----------------------------------- -POST /logstash-2019.05.18/_doc -{ - "@timestamp": "2019-05-18T15:57:27.541Z", - "ip": "225.44.217.191", - "extension": "jpg", - "response": "200", - "geo": { - "coordinates": { - "lat": 38.53146222, - "lon": -121.7864906 - } - }, - "url": "https://media-for-the-masses.theacademyofperformingartsandscience.org/uploads/charles-fullerton.jpg" -} - -POST /logstash-2019.05.20/_doc -{ - "@timestamp": "2019-05-20T03:44:20.844Z", - "ip": "198.247.165.49", - "extension": "php", - "response": "200", - "geo": { - "coordinates": { - "lat": 37.13189556, - "lon": -76.4929875 - } - }, - "memory": 241720, - "url": "https://theacademyofperformingartsandscience.org/people/type:astronauts/name:laurel-b-clark/profile" -} ----------------------------------- - -The mappings and alias are configured automatically based on the template. To verify, you -can view one of the newly created indices using the {ref}/indices-get-index.html#indices-get-index[index API]. diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index b53bda95466dc..8944414f6bfbc 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -7,7 +7,7 @@ with no expiration date. For the full list of features, refer to If you want to try out the full set of features, you can activate a free 30-day trial. To view the status of your license, start a trial, or install a new -license, open the menu, then go to *Stack Management > Stack > License Management*. +license, open the main menu, then click *Stack Management > License Management*. NOTE: You can start a trial only if your cluster has not already activated a trial license for the current major product version. For example, if you have @@ -34,7 +34,7 @@ the features that will no longer be supported if you revert to a basic license. The `manage` cluster privilege is required to access *License Management*. -You can add this privilege in *Stack Management > Security > Roles*. +To add the privilege, open the main menu, then click *Stack Management > Roles*. [discrete] [[update-license]] diff --git a/docs/management/managing-saved-objects.asciidoc b/docs/management/managing-saved-objects.asciidoc index 8c885ddca52e5..2e081c09e0e70 100644 --- a/docs/management/managing-saved-objects.asciidoc +++ b/docs/management/managing-saved-objects.asciidoc @@ -5,17 +5,21 @@ The *Saved Objects* UI helps you keep track of and manage your saved objects. Th store data for later use, including dashboards, visualizations, maps, index patterns, Canvas workpads, and more. -To get started, open the menu, then go to *Stack Management > {kib} > Saved Objects*. With this UI, you can: - -* <> -* <> -* <> -* <> - +To get started, open the main menu, then click *Stack Management > Saved Objects*. [role="screenshot"] image::images/management-saved-objects.png[Saved Objects] +[float] +=== Required permissions + +The `Saved Objects Management` {kib} privilege is required to access the *Saved Objects* UI. + +To add the privilege, open the menu, then click *Stack Management > Roles*. + +NOTE: +Granting access to Saved Objects Management will authorize users to manage all saved objects in {kib}, including objects that are managed by applications they may not otherwise be authorized to access. + [float] [[managing-saved-objects-view]] diff --git a/docs/management/rollups/create_and_manage_rollups.asciidoc b/docs/management/rollups/create_and_manage_rollups.asciidoc index e20f384b5ed18..bc876ab67bc62 100644 --- a/docs/management/rollups/create_and_manage_rollups.asciidoc +++ b/docs/management/rollups/create_and_manage_rollups.asciidoc @@ -8,11 +8,7 @@ by an index pattern, and then rolls it into a new index. Rollup indices are a go compactly store months or years of historical data for use in visualizations and reports. -To get started, open the menu, then go to *Stack Management > Data > Rollup Jobs*. With this UI, -you can: - -* <> -* <> +To get started, open the main menu, then click *Stack Management > Rollup Jobs*. [role="screenshot"] image::images/management_rollup_list.png[][List of currently active rollup jobs] @@ -25,7 +21,7 @@ Before using this feature, you should be familiar with how rollups work. The `manage_rollup` cluster privilege is required to access *Rollup jobs*. -You can add this privilege in *Stack Management > Security > Roles*. +To add the privilege, open the main menu, then click *Stack Management > Roles*. [float] [[create-and-manage-rollup-job]] @@ -67,7 +63,7 @@ You can read more at {ref}/rollup-job-config.html[rollup job configuration]. === Try it: Create and visualize rolled up data This example creates a rollup job to capture log data from sample web logs. -To follow along, add the <>. +To follow along, add the sample web logs data set. In this example, you want data that is older than 7 days in the target index pattern `kibana_sample_data_logs` to roll up once a day into the index `rollup_logstash`. You’ll bucket the @@ -137,7 +133,7 @@ Your next step is to visualize your rolled up data in a vertical bar chart. Most visualizations support rolled up data, with the exception of Timelion and Vega visualizations. -. Go to *Stack Management > {kib} > Index Patterns*. +. Open the main menu, then click *Stack Management > Index Patterns*. . Click *Create index pattern*, and select *Rollup index pattern* from the dropdown. + @@ -152,7 +148,7 @@ is `rollup_logstash,kibana_sample_data_logs`. In this index pattern, `rollup_log matches the rolled up index pattern and `kibana_sample_data_logs` matches the index pattern for raw data. -. Go to *Dashboard* and create a vertical bar chart. +. Open the main menu, click *Dashboard*, then create and add a vertical bar chart. . Choose `rollup_logstash,kibana_sample_data_logs` as your source to see both the raw and rolled up data. diff --git a/docs/management/snapshot-restore/index.asciidoc b/docs/management/snapshot-restore/index.asciidoc index 1bf62522e245c..62633441ef161 100644 --- a/docs/management/snapshot-restore/index.asciidoc +++ b/docs/management/snapshot-restore/index.asciidoc @@ -8,7 +8,7 @@ Snapshots are important because they provide a copy of your data in case something goes wrong. If you need to roll back to an older version of your data, you can restore a snapshot from the repository. -To get started, open the menu, then go to *Stack Management > Data > Snapshot and Restore*. +To get started, open the main menu, then click *Stack Management > Snapshot and Restore*. With this UI, you can: * Register a repository for storing your snapshots @@ -32,7 +32,7 @@ The minimum required permissions to access *Snapshot and Restore* include: * Cluster privileges: `monitor`, `manage_slm`, `cluster:admin/snapshot`, and `cluster:admin/repository` * Index privileges: `all` on the `monitor` index if you want to access content in the *Restore Status* tab -To add privileges, open the menu, then go to *Stack Management > Security > Roles*. +To add privileges, open the main menu, then click *Stack Management > Roles*. [role="screenshot"] image:management/snapshot-restore/images/snapshot_permissions.png["Edit Role"] @@ -191,7 +191,7 @@ your master and data nodes. You can do this in one of two ways: Use *Snapshot and Restore* to register the repository where your snapshots will live. -. Open the menu, then go to *Stack Management > Data > Snapshot and Restore*. +. Open the main menu, then click *Stack Management > Snapshot and Restore*. . Click *Register a repository* in either the introductory message or *Repository view*. . Enter a name for your repository, for example, `my_backup`. . Select *Shared file system*. @@ -212,7 +212,7 @@ The repository currently doesn’t have any snapshots. ==== Add a snapshot to the repository Use the {ref}/snapshots-take-snapshot.html[snapshot API] to create a snapshot. -. Open the menu, go to *Dev Tools*, then select *Console*. +. Open the main menu, click *Dev Tools*, then select *Console*. . Create the snapshot: + [source,js] diff --git a/docs/management/upgrade-assistant/index.asciidoc b/docs/management/upgrade-assistant/index.asciidoc index 994780c559334..5f68eac16343a 100644 --- a/docs/management/upgrade-assistant/index.asciidoc +++ b/docs/management/upgrade-assistant/index.asciidoc @@ -4,7 +4,7 @@ The Upgrade Assistant helps you prepare for your upgrade to the next major {es} version. For example, if you are using 6.8, the Upgrade Assistant helps you to upgrade to 7.0. -To access the assistant, open the menu, then go to *Stack Management > Stack > Upgrade Assistant*. +To access the assistant, open the main menu, then click *Stack Management > Upgrade Assistant*. The assistant identifies the deprecated settings in your cluster and indices and guides you through the process of resolving issues, including reindexing. @@ -19,7 +19,7 @@ For example, if you want to upgrade to to 7.0, make sure that you are using 6.8. The `manage` cluster privilege is required to access the *Upgrade assistant*. Additional privileges may be needed to perform certain actions. -You can add this privilege in *Stack Management > Security > Roles*. +To add the privilege, open the main menu, then click *Stack Management > Roles*. [float] === Reindexing diff --git a/docs/management/watcher-ui/index.asciidoc b/docs/management/watcher-ui/index.asciidoc index 23a0acbff5718..aded7a45022db 100644 --- a/docs/management/watcher-ui/index.asciidoc +++ b/docs/management/watcher-ui/index.asciidoc @@ -8,8 +8,8 @@ Watches are helpful for analyzing mission-critical and business-critical streaming data. For example, you might watch application logs for performance outages or audit access logs for security threats. -To get started with the Watcher UI, open then menu, -then go to *Stack Management > Alerts and Insights > Watcher*. +To get started, open then main menu, +then click *Stack Management > Watcher*. With this UI, you can: * <> @@ -41,12 +41,12 @@ and either of these watcher roles: * `watcher_admin`. You can perform all Watcher actions, including create and edit watches. * `watcher_user`. You can view watches, but not create or edit them. -To manage roles, open then menu, then go to *Stack Management > Security > Roles*, or use the +To manage roles, open then main menu, then click *Stack Management > Roles*, or use the <>. Watches are shared between all users with the same role. NOTE: If you are creating a threshold watch, you must also have the `view_index_metadata` index privilege. See -<> for detailed information. +{ref}/index-mgmt.html[Index management] for detailed information. [float] [[watcher-create-threshold-alert]] diff --git a/docs/maps/geojson-upload.asciidoc b/docs/maps/geojson-upload.asciidoc index 6c28840087252..3c9bea11176cc 100644 --- a/docs/maps/geojson-upload.asciidoc +++ b/docs/maps/geojson-upload.asciidoc @@ -19,7 +19,7 @@ GeoJSON is the most commonly used and flexible option. Follow these instructions to upload a GeoJSON data file, or try the <>. -. Open the menu, go to *Maps*, and then click *Add layer*. +. Open the main menu, click *Maps*, and then click *Add layer*. . Click *Uploaded GeoJSON*. + [role="screenshot"] diff --git a/docs/maps/images/fu_gs_select_source_file_upload.png b/docs/maps/images/fu_gs_select_source_file_upload.png deleted file mode 100644 index 4fe1162acb29c..0000000000000 Binary files a/docs/maps/images/fu_gs_select_source_file_upload.png and /dev/null differ diff --git a/docs/maps/import-geospatial-data.asciidoc b/docs/maps/import-geospatial-data.asciidoc new file mode 100644 index 0000000000000..ff0c9bf1f72ba --- /dev/null +++ b/docs/maps/import-geospatial-data.asciidoc @@ -0,0 +1,46 @@ +[role="xpack"] +[[import-geospatial-data]] +== Import geospatial data + +To import geospatical data into the Elastic Stack, the data must be indexed as {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape]. +Geospatial data comes in many formats. +Choose an import tool based on the format of your geospatial data. + +[discrete] +=== Upload CSV with latitude and longitude columns + +*File Data Visualizer* indexes CSV files with latitude and longitude columns as a geo_point. + +. Open the main menu, then click *Machine Learning*. +. Select the *Data Visualizer* tab, then click *Upload file*. +. Use the file chooser to select a CSV file. +. Click *Import*. +. Select the *Advanced* tab. +. Set *Index name*. +. Click *Add combined field*, then click *Add geo point field*. +. Fill out the form and click *Add*. +. Click *Import*. + +[discrete] +=== Upload a GeoJSON file + +*Upload GeoJSON* indexes GeoJSON features as a geo_point or geo_shape. + +. <>. +. Click *Add layer*. +. Select *Upload GeoJSON*. +. Use the file chooser to select a GeoJSON file. +. Click *Import file*. + +[discrete] +=== Upload data with IP addresses + +The GeoIP processor adds information about the geographical location of IP addresses. +See {ref}/geoip-processor.html[GeoIP processor] for details. +For private IP addresses, see https://www.elastic.co/blog/enriching-elasticsearch-data-geo-ips-internal-private-ip-addresses[Enriching data with GeoIPs from internal, private IP addresses]. + +[discrete] +=== Upload data with GDAL + +https://www.gdal.org/[GDAL] (Geospatial Data Abstraction Library) contains command line tools that can convert geospatial data between 75 different geospatial file formats and index that geospatial data into {es}. +See https://www.elastic.co/blog/how-to-ingest-geospatial-data-into-elasticsearch-with-gdal[Ingest geospatial data into Elasticsearch with GDAL] for details. diff --git a/docs/maps/index.asciidoc b/docs/maps/index.asciidoc index 8999b9fe20b11..3c3537826a6a8 100644 --- a/docs/maps/index.asciidoc +++ b/docs/maps/index.asciidoc @@ -56,6 +56,6 @@ include::maps-aggregations.asciidoc[] include::search.asciidoc[] include::map-settings.asciidoc[] include::connect-to-ems.asciidoc[] -include::geojson-upload.asciidoc[] +include::import-geospatial-data.asciidoc[] include::indexing-geojson-data-tutorial.asciidoc[] include::trouble-shooting.asciidoc[] diff --git a/docs/maps/indexing-geojson-data-tutorial.asciidoc b/docs/maps/indexing-geojson-data-tutorial.asciidoc index d1a6593f61fe1..434c9ab369a5b 100644 --- a/docs/maps/indexing-geojson-data-tutorial.asciidoc +++ b/docs/maps/indexing-geojson-data-tutorial.asciidoc @@ -47,7 +47,7 @@ image::maps/images/fu_gs_new_england_map.png[] For each GeoJSON file you downloaded, complete the following steps: . Click *Add layer*. -. From the list of layer types, click *Uploaded GeoJSON*. +. From the list of layer types, click *Upload GeoJSON*. . Using the File Picker, upload the GeoJSON file. + Depending on the geometry type of your features, this will diff --git a/docs/maps/map-settings.asciidoc b/docs/maps/map-settings.asciidoc index e11be438a2237..f606511a6f391 100644 --- a/docs/maps/map-settings.asciidoc +++ b/docs/maps/map-settings.asciidoc @@ -9,12 +9,16 @@ To access these settings, click *Map settings* in the application toolbar. [[maps-settings-navigation]] === Navigation +*Auto fit map to bounds*:: +When enabled, the map will automatically pan and zoom to show the filtered data bounds. + *Zoom range*:: Constrain the map to the defined zoom range. *Initial map location*:: Configure the initial map center and zoom. * *Map location at save*: Use the map center and zoom from the map position at the time of the latest save. +* *Auto fit map to bounds*: Set the initial map location to show the filtered data bounds. * *Fixed location*: Lock the map center and zoom to fixed values. * *Browser location*: Set the initial map center to the browser location. diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index e0d43a571a331..5c6cd87b235e1 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -50,7 +50,7 @@ In this tutorial, you'll learn to: The first thing to do is to create a new map. -. If you haven't already, open the menu, then click *{kib} > Maps*. +. If you haven't already, open the main menu, then click *Maps*. . On the maps list page, click *Create map*. . Set the time range to *Last 7 days*. + @@ -188,7 +188,7 @@ You have completed the steps for re-creating the sample data map. === Add the map to a dashboard You can add your saved map to a {kibana-ref}/dashboard.html[dashboard] and view your geospatial data alongside bar charts, pie charts, and other visualizations. -. Open the menu, then go to *Dashboard*. +. Open the main menu, then click *Dashboard*. . Click *Create dashboard*. . Set the time range to *Last 7 days*. . Click *Add*. diff --git a/docs/maps/search.asciidoc b/docs/maps/search.asciidoc index 0c4042a37f700..09d9788cd37e0 100644 --- a/docs/maps/search.asciidoc +++ b/docs/maps/search.asciidoc @@ -49,6 +49,7 @@ Spatial filters have the following properties: * *Geometry label* enables you to provide a meaningful name for your spatial filter. * *Spatial field* specifies the geo_point or geo_shape field used to determine if a document matches the spatial relation with the specified geometry. * *Spatial relation* determines the {ref}/query-dsl-geo-shape-query.html#_spatial_relations[spatial relation operator] to use at search time. Only available when *Spatial field* is set to geo_shape. +* *Action* specifies whether to apply the filter to the current view or to a drilldown action. Only available when the map is a panel in a {kibana-ref}/dashboard.html[dashboard] with {kibana-ref}/drilldowns.html[drilldowns]. [float] [[maps-phrase-filter]] @@ -56,6 +57,7 @@ Spatial filters have the following properties: A phrase filter narrows search results to documents that contain the specified text. You can create a phrase filter by clicking the plus icon image:maps/images/gs_plus_icon.png[] in a <>. +If the map is a dashboard panel with drilldowns, you can apply a phrase filter to a drilldown by selecting the drilldown action. [role="screenshot"] image::maps/images/create_phrase_filter.png[] diff --git a/docs/maps/vector-layer.asciidoc b/docs/maps/vector-layer.asciidoc index 494bd915b7f56..f7128b0f99f88 100644 --- a/docs/maps/vector-layer.asciidoc +++ b/docs/maps/vector-layer.asciidoc @@ -16,9 +16,20 @@ The index must contain at least one field mapped as {ref}/geo-point.html[geo_poi *Documents*:: Points, lines, and polyons from Elasticsearch. The index must contain at least one field mapped as {ref}/geo-point.html[geo_point] or {ref}/geo-shape.html[geo_shape]. ++ +Results are limited to the `index.max_result_window` index setting, which defaults to 10000. +Select the appropriate *Scaling* option for your use case. ++ +* *Limit results to 10000.* The layer displays features from the first `index.max_result_window` documents. +Results exceeding `index.max_result_window` are not displayed. -NOTE: Document results are limited to the `index.max_result_window` index setting, which defaults to 10000. -Use <> to plot large data sets. +* *Show top hits per entity.* The layer displays the <>. + +* *Show clusters when results exceed 10000.* When results exceed `index.max_result_window`, the layer uses {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into clusters and displays metrics for each cluster. When results are less then `index.max_result_window`, the layer displays features from individual documents. + +* *Use vector tiles.* Vector tiles partition your map into 6 to 8 tiles. +Each tile request is limited to the `index.max_result_window` index setting. +Tiles exceeding `index.max_result_window` have a visual indicator when there are too many features to display. *EMS Boundaries*:: Administrative boundaries from https://www.elastic.co/elastic-maps-service[Elastic Maps Service]. diff --git a/docs/observability/images/apm-app.png b/docs/observability/images/apm-app.png new file mode 100644 index 0000000000000..acbaa70c7f2f1 Binary files /dev/null and b/docs/observability/images/apm-app.png differ diff --git a/docs/observability/images/logs-app.png b/docs/observability/images/logs-app.png new file mode 100644 index 0000000000000..1138ec175e5bf Binary files /dev/null and b/docs/observability/images/logs-app.png differ diff --git a/docs/observability/images/metrics-app.png b/docs/observability/images/metrics-app.png new file mode 100644 index 0000000000000..8c00a31974a70 Binary files /dev/null and b/docs/observability/images/metrics-app.png differ diff --git a/docs/observability/images/uptime-app.png b/docs/observability/images/uptime-app.png new file mode 100644 index 0000000000000..522a696adf506 Binary files /dev/null and b/docs/observability/images/uptime-app.png differ diff --git a/docs/observability/index.asciidoc b/docs/observability/index.asciidoc index d63402e8df2fb..c924cea3712dd 100644 --- a/docs/observability/index.asciidoc +++ b/docs/observability/index.asciidoc @@ -13,12 +13,69 @@ With *Observability*, you have: * *View in app* options to drill down and analyze data in the Logs, Metrics, Uptime, and APM apps. * An alerts chart to keep you informed of any issues that you may need to resolve quickly. +{kib} provides step-by-step instructions to help you add and configure your data +sources. The {observability-guide}/index.html[Observability Guide] is a good source for more detailed information +and instructions. + [role="screenshot"] image::observability/images/observability-overview.png[Observability Overview in {kib}] [float] -== Get started +[[logs-app]] +== Logs -{kib} provides step-by-step instructions to help you add and configure your data -sources. The {observability-guide}/index.html[Observability Guide] is a good source for more detailed information -and instructions. +The {logs-app} in {kib} enables you to search, filter, and tail all your logs +ingested into {es}. Instead of having to log into different servers, change +directories, and tail individual files, all your logs are available in the {logs-app}. + +There is live streaming of logs, filtering using auto-complete, and a logs histogram +for quick navigation. You can also use machine learning to detect specific log +anomalies automatically and categorize log messages to quickly identify patterns in your +log events. + +To get started with the {logs-app}, see {observability-guide}/ingest-logs.html[Ingest logs]. + +[role="screenshot"] +image::observability/images/logs-app.png[Logs app in {kib}] + +[float] +[[metrics-app]] +== Metrics + +The {metrics-app} in {kib} enables you to visualize infrastructure metrics +to help diagnose problematic spikes, identify high resource utilization, +automatically discover and track pods, and unify your metrics +with logs and APM data in {es}. + +To get started with the {metrics-app}, see {observability-guide}/ingest-metrics.html[Ingest metrics]. + +[role="screenshot"] +image::observability/images/metrics-app.png[Metrics app in {kib}] + +[float] +[[uptime-app]] +== Uptime + +The {uptime-app} in {kib} enables you to monitor the availability and response times +of applications and services in real time, and detect problems before they affect users. +You can monitor the status of network endpoints via HTTP/S, TCP, and ICMP, explore +endpoint status over time, drill down into specific monitors, and view a high-level +snapshot of your environment at any point in time. + +To get started with the {uptime-app}, see {observability-guide}/ingest-uptime.html[Ingest uptime data]. + +[role="screenshot"] +image::observability/images/uptime-app.png[Uptime app in {kib}] + +[float] +[[apm-app]] +== APM + +The APM app in {kib} enables you to monitors software services and applications in real time, +collect unhandled errors and exceptions, and automatically pick up basic host-level metrics +and agent specific metrics. + +To get started with the APM app, see <>. + +[role="screenshot"] +image::observability/images/apm-app.png[APM app in {kib}] diff --git a/docs/plugins/known-plugins.asciidoc b/docs/plugins/known-plugins.asciidoc index 37e1113f42d68..7b24de42d8e1c 100644 --- a/docs/plugins/known-plugins.asciidoc +++ b/docs/plugins/known-plugins.asciidoc @@ -14,7 +14,7 @@ This list of plugins is not guaranteed to work on your version of Kibana. Instea * https://github.com/sivasamyk/logtrail[LogTrail] - View, analyze, search and tail log events in realtime with a developer/sysadmin friendly interface * https://github.com/wtakase/kibana-own-home[Own Home] (wtakase) - enables multi-tenancy * https://github.com/asileon/kibana_shard_allocation[Shard Allocation] (asileon) - visualize elasticsearch shard allocation -* https://github.com/samtecspg/conveyor[Conveyor] - Simple (GUI) interface for importing data into Elasticsearch. +* https://github.com/wazuh/wazuh-kibana-app[Wazuh] - Wazuh provides host-based security visibility using lightweight multi-platform agents. * https://github.com/TrumanDu/indices_view[Indices View] - View indices related information. * https://github.com/johtani/analyze-api-ui-plugin[Analyze UI] (johtani) - UI for elasticsearch _analyze API * https://github.com/TrumanDu/cleaner[Cleaner] (TrumanDu)- Setting index ttl. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 209dfa6675eb9..121639f865e15 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -28,12 +28,12 @@ This page has moved. Please see the new section in the {heartbeat-ref}/securing- [role="exclude",id="infra-read-only-access"] == Configure source read-only access -This page has moved. Please see the new section in the {metrics-guide}/configure-metrics-source.html[Metrics Monitoring Guide]. +This page has moved. Please see {observability-guide}/configure-settings.html[configure settings]. [role="exclude",id="logs-read-only-access"] == Configure source read-only access -This page has moved. Please see {logs-guide}/configure-logs-source.html[logs configuration]. +This page has moved. Please see {observability-guide}/configure-data-sources.html[configure data sources]. [role="exclude",id="creating-df-kib"] == Creating {transforms} @@ -75,7 +75,7 @@ This page has moved. Please see <>. [role="exclude",id="add-sample-data"] == Add sample data -This page has moved. Please see <>. +This page has moved. Please see <>. [role="exclude",id="tilemap"] == Coordinate map @@ -128,3 +128,39 @@ This content has moved. See This content has moved. See {ref}/ccr-getting-started.html#ccr-getting-started-remote-cluster[Connect to a remote cluster]. + +[role="exclude",id="adding-policy-to-index"] +== Adding a policy to an index + +This content has moved. See +{ref}/set-up-lifecycle-policy.html[Configure a lifecycle policy]. + +[role="exclude",id="creating-index-lifecycle-policies"] +== Creating an index lifecycle policy + +This content has moved. See +{ref}/set-up-lifecycle-policy.html[Configure a lifecycle policy]. + +[role="exclude",id="index-lifecycle-policies"] +== Index Lifecycle Policies + +This content has moved. See +{ref}/index-lifecycle-management.html[ILM: Manage the index lifecycle]. + +[role="exclude",id="managing-index-lifecycle-policies"] +== Managing index lifecycle policies + +This content has moved. See +{ref}/index-lifecycle-management.html[ILM: Manage the index lifecycle]. + +[role="exclude",id="tutorial-define-index"] +== Define your index patterns + +This content has moved. See +<>. + +[role="exclude",id="managing-indices"] +== Index management + +This content has moved. See {ref}/index-mgmt.html[Index management]. + diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index 140db00e2c881..eb5260f13fbfc 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -23,7 +23,7 @@ It is enabled by default. // Any changes made in this file will be seen there as well. // tag::apm-indices-settings[] -Index defaults can be changed in Kibana. Navigate to *APM* > *Settings* > *Indices*. +Index defaults can be changed in Kibana. Open the main menu, then click *APM > Settings > Indices*. Index settings in the APM app take precedence over those set in `kibana.yml`. [role="screenshot"] @@ -48,8 +48,14 @@ Changing these settings may disable features of the APM App. | `xpack.apm.enabled` | Set to `false` to disable the APM app. Defaults to `true`. +| `xpack.apm.serviceMapFingerprintBucketSize` + | Maximum number of unique transaction combinations sampled for generating service map focused on a specific service. Defaults to `100`. + +| `xpack.apm.serviceMapFingerprintGlobalBucketSize` + | Maximum number of unique transaction combinations sampled for generating the global service map. Defaults to `100`. + | `xpack.apm.ui.enabled` {ess-icon} - | Set to `false` to hide the APM app from the menu. Defaults to `true`. + | Set to `false` to hide the APM app from the main menu. Defaults to `true`. | `xpack.apm.ui.transactionGroupBucketSize` | Number of top transaction groups displayed in the APM app. Defaults to `1000`. diff --git a/docs/settings/ingest-manager-settings.asciidoc b/docs/settings/fleet-settings.asciidoc similarity index 68% rename from docs/settings/ingest-manager-settings.asciidoc rename to docs/settings/fleet-settings.asciidoc index 9fa83fc242b4a..9c28d28003175 100644 --- a/docs/settings/ingest-manager-settings.asciidoc +++ b/docs/settings/fleet-settings.asciidoc @@ -1,29 +1,29 @@ [role="xpack"] -[[ingest-manager-settings-kb]] -=== {ingest-manager} settings in {kib} +[[fleet-settings-kb]] +=== {fleet} settings in {kib} ++++ -{ingest-manager} settings +{fleet} settings ++++ experimental[] You can configure `xpack.fleet` settings in your `kibana.yml`. -By default, {ingest-manager} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts. +By default, {fleet} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts. See the {ingest-guide}/index.html[Ingest Management] docs for more information. -[[general-ingest-manager-settings-kb]] -==== General {ingest-manager} settings +[[general-fleet-settings-kb]] +==== General {fleet} settings [cols="2*<"] |=== | `xpack.fleet.enabled` {ess-icon} - | Set to `true` (default) to enable {ingest-manager}. + | Set to `true` (default) to enable {fleet}. | `xpack.fleet.agents.enabled` {ess-icon} | Set to `true` (default) to enable {fleet}. |=== -[[ingest-manager-data-visualizer-settings]] +[[fleet-data-visualizer-settings]] ==== {package-manager} settings diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 917821ad09e2f..f48dbeab9d61a 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -76,6 +76,9 @@ a|`monitoring.cluster_alerts.` health checks. By default, it matches the <> setting, which has a default value of `30000`. +| `monitoring.ui.elasticsearch.ssl` + | Shares the same configuration as <>. These settings configure encrypted communication between {kib} and the monitoring cluster. + |=== [float] diff --git a/docs/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index adfc3964d4204..d44c42db92f41 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -10,6 +10,7 @@ You can configure `xpack.reporting` settings in your `kibana.yml` to: * <> * <> * <> +* <> [float] [[general-reporting-settings]] @@ -65,7 +66,7 @@ proxy host requires that the {kib} server has network access to the proxy. [NOTE] ============ -Reporting authenticates requests on the Kibana page only when the hostname matches the +Reporting authenticates requests on the {kib} page only when the hostname matches the <> setting. Therefore Reporting would fail if the set value redirects to another server. For that reason, `"0"` is an invalid setting because, in the Reporting browser, it becomes an automatic redirect to `"0.0.0.0"`. @@ -214,6 +215,23 @@ a| `xpack.reporting.capture.browser` | The maximum {ref}/common-options.html#byte-units[byte size] of a CSV file before being truncated. This setting exists to prevent large exports from causing performance and storage issues. Can be specified as number of bytes. Defaults to `10mb`. +|=== + +[NOTE] +============ +Setting `xpack.reporting.csv.maxSizeBytes` much larger than the default 10 MB limit has the potential to negatively affect the +performance of {kib} and your {es} cluster. There is no enforced maximum for this setting, but a reasonable maximum value depends +on multiple factors: + +* The `http.max_content_length` setting in {es}. +* Network proxies, which are often configured by default to block large requests with a 413 error. +* The amount of memory available to the {kib} server, which limits the size of CSV data that must be held temporarily. + +For information about {kib} memory limits, see <>. +============ + +[cols="2*<"] +|=== | `xpack.reporting.csv.scroll.size` | Number of documents retrieved from {es} for each scroll iteration during a CSV @@ -248,6 +266,11 @@ a| `xpack.reporting.capture.browser` exist. Configure this to a unique value, beginning with `.reporting-`, for every {kib} instance that has a unique <> setting. Defaults to `.reporting`. +| `xpack.reporting.capture.networkPolicy` + | Capturing a screenshot from a {kib} page involves sending out requests for all the linked web assets. For example, a Markdown + visualization can show an image from a remote server. You can configure what type of requests to allow or filter by setting a + <> for Reporting. + | `xpack.reporting.roles.allow` | Specifies the roles in addition to superusers that can use reporting. Defaults to `[ "reporting_user" ]`. + diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 3f21c8a80d3e5..51ff24256746e 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -239,3 +239,26 @@ The format is a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', ============ |=== + +[float] +[[security-encrypted-saved-objects-settings]] +==== Encrypted saved objects settings + +These settings control the encryption of saved objects with sensitive data. For more details, refer to <>. + +[IMPORTANT] +============ +In high-availability deployments, make sure you use the same encryption and decryption keys for all instances of {kib}. Although the keys can be specified in clear text in `kibana.yml`, it's recommended to store them securely in the <>. +============ + +[cols="2*<"] +|=== +| [[xpack-encryptedSavedObjects-encryptionKey]] `xpack.encryptedSavedObjects.` +`encryptionKey` +| An arbitrary string of at least 32 characters that is used to encrypt sensitive properties of saved objects before they're stored in {es}. If not set, {kib} will generate a random key on startup, but certain features won't be available until you set the encryption key explicitly. + +| [[xpack-encryptedSavedObjects-keyRotation-decryptionOnlyKeys]] `xpack.encryptedSavedObjects.` +`keyRotation.decryptionOnlyKeys` +| An optional list of previously used encryption keys. Like <>, these must be at least 32 characters in length. {kib} doesn't use these keys for encryption, but may still require them to decrypt some existing saved objects. Use this setting if you wish to change your encryption key, but don't want to lose access to saved objects that were previously encrypted with a different key. + +|=== \ No newline at end of file diff --git a/docs/settings/settings-xkb.asciidoc b/docs/settings/settings-xkb.asciidoc index 04fed0d6204b7..9d9cc92401896 100644 --- a/docs/settings/settings-xkb.asciidoc +++ b/docs/settings/settings-xkb.asciidoc @@ -20,4 +20,4 @@ include::ml-settings.asciidoc[] include::reporting-settings.asciidoc[] include::spaces-settings.asciidoc[] include::i18n-settings.asciidoc[] -include::ingest-manager-settings.asciidoc[] +include::fleet-settings.asciidoc[] diff --git a/docs/setup/access.asciidoc b/docs/setup/access.asciidoc index 49aa411e91512..edf936fe54267 100644 --- a/docs/setup/access.asciidoc +++ b/docs/setup/access.asciidoc @@ -1,24 +1,37 @@ [[access]] == Access {kib} -Kibana is a web application that you access through port 5601. All you need to do is point your web browser at the -machine where Kibana is running and specify the port number. For example, `localhost:5601` or `http://YOURDOMAIN.com:5601`. -If you want to allow remote users to connect, set the parameter `server.host` in `kibana.yml` to a non-loopback address. +The fastest way to access {kib} is to use our hosted {es} Service. If you <>, access {kib} through the web application. -When you access Kibana, the <> page loads by default with the default index pattern selected. The -time filter is set to the last 15 minutes and the search query is set to match-all (\*). +[float] +=== Set up on cloud -If you don't see any documents, try setting the time filter to a wider time range. -If you still don't see any results, it's possible that you don't *have* any documents. +include::{docs-root}/shared/cloud/ess-getting-started.asciidoc[] + +[float] +[[log-on-to-the-web-application]] +=== Log on to the web application + +If you are using a self-managed deployment, you access {kib} through the web application on port 5601. + +. Point your web browser to the machine where you are running {kib} and specify the port number. For example, `localhost:5601` or `http://YOURDOMAIN.com:5601`. + +. To allow remote users to connect to {kib}, set the parameter `server.host` in kibana.yml to a non-loopback address. + +. On the home page, click *{kib}*. ++ +To make the {kib} page your landing page, click *Make this my landing page*. [float] [[status]] -=== Check {kib} status +=== Check the {kib} status -You can reach the Kibana server's status page by navigating to the status endpoint, for example, `localhost:5601/status`. The status page displays -information about the server's resource usage and lists the installed plugins. +To view the {kib} status page, use the status endpoint. For example, `localhost:5601/status`. The status page displays +information about the server resource usage and installed plugins. [role="screenshot"] image::images/kibana-status-page-7_5_0.png[] -NOTE: For JSON-formatted server status details, use the API endpoint at `localhost:5601/api/status` +For JSON-formatted server status details, use the `localhost:5601/api/status` API endpoint. + + diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index ea02afb8a9fda..9f04bb9416868 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -11,7 +11,7 @@ To start working with your data in {kib}, you can: * Connect {kib} with existing {es} indices. -If you're not ready to use your own data, you can add a <> +If you're not ready to use your own data, you can add a <> to see all that you can do in {kib}. [float] @@ -21,13 +21,13 @@ to see all that you can do in {kib}. experimental[] To visualize data in a CSV, JSON, or log file, you can upload it using the File -Data Visualizer. On the home page, click *Import a CSV, NDSON, or log file*, and -then drag your file into the File Data Visualizer. Alternatively, you can open +Data Visualizer. On the home page, click *Upload a file*, and +then drag your file onto the *File Data Visualizer*. Alternatively, you can open it by navigating to *Machine Learning* from the side navigation and selecting *Data Visualizer*. [role="screenshot"] -image::images/data-viz-homepage.jpg[File Data Visualizer on the home page] +image::images/ingest-data.png[File Data Visualizer on the home page] You can upload a file up to 100 MB. This value is configurable up to 1 GB in <>. @@ -45,16 +45,12 @@ repeated production process, but rather for the initial exploration of your data [[upload-geoipdata-kibana]] === Upload geospatial data -To visualize geospatial data in a point or shape file, you can upload it using the <> +To visualize geospatial data in a point or shape file, you can upload it using the <> feature in Maps, and then use that data as a layer in a map. The data is also available for use in the broader Kibana ecosystem, for example, in visualizations and Canvas workpads. With GeoJSON Upload, you can upload a file up to 50 MB. -[role="screenshot"] -image::images/fu_gs_select_source_file_upload.png[] - - [float] [[add-data-tutorial-kibana]] === Index metrics, log, security, and application data @@ -82,7 +78,7 @@ create an index pattern that matches the names of the indices that you want to e When you add data with the File Data Visualizer, GeoJSON Upload feature, or built-in tutorial, an index pattern is created for you. -. Go to *Stack Management*, and then click *Index Patterns*. +. Open the main menu, then click *Stack Management > Index Patterns*. . Click *Create index pattern*. diff --git a/docs/setup/images/ingest-data.png b/docs/setup/images/ingest-data.png new file mode 100644 index 0000000000000..b1943d6de27d2 Binary files /dev/null and b/docs/setup/images/ingest-data.png differ diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 903bb59cef380..2f2c87ca9c7d4 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -638,7 +638,7 @@ include::{kib-repo-dir}/settings/alert-action-settings.asciidoc[] include::{kib-repo-dir}/settings/apm-settings.asciidoc[] include::{kib-repo-dir}/settings/dev-settings.asciidoc[] include::{kib-repo-dir}/settings/graph-settings.asciidoc[] -include::{kib-repo-dir}/settings/ingest-manager-settings.asciidoc[] +include::{kib-repo-dir}/settings/fleet-settings.asciidoc[] include::{kib-repo-dir}/settings/i18n-settings.asciidoc[] include::{kib-repo-dir}/settings/logs-ui-settings.asciidoc[] include::{kib-repo-dir}/settings/infrastructure-ui-settings.asciidoc[] diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index e5222c950b7e3..3d10b2cdf8bd3 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -4,29 +4,24 @@ Depending on the {kib} version you're upgrading from, the upgrade process to 7.0 varies. -NOTE: {kib} upgrades automatically when starting a new version, as described in -<>. -Although you do not need to manually back up {kib} before upgrading, we recommend -that you have a backup on hand. You can use -<> to back up {kib} -data by targeting `.kibana*` indices. If you are using the Reporting plugin, -you can also target `.reporting*` indices. - [float] [[upgrade-before-you-begin]] === Before you begin +WARNING: {kib} automatically runs upgrade migrations when required. To roll back to an earlier version in case of an upgrade failure, you **must** have a backup snapshot available. Use <> to back up {kib} data by targeting the `.kibana*` indices. For more information see <>. + Before you upgrade {kib}: * Consult the <>. +* Back up your data with <>. To roll back to an earlier version, you **must** have a snapshot of the `.kibana*` indices. +* Although not a requirement for rollbacks, we recommend taking a snapshot of all {kib} indices created by the plugins you use such as the `.reporting*` indices created by the reporting plugin. * Before you upgrade production servers, test the upgrades in a dev environment. -* Back up your data with {es} {ref}/modules-snapshots.html[snapshots]. - To roll back to an earlier version, you **must** have a backup of your data. +* See <> for common reasons upgrades fail and how to prevent these. * If you are using custom plugins, check that a compatible version is available. -* Shut down all {kib} nodes. Running more than one {kib} version against the - same Elasticseach index is unsupported. If you upgrade while older {kib} nodes are - running, the upgrade can fail. +* Shut down all {kib} instances. Running more than one {kib} version against + the same Elasticseach index is unsupported. Upgrading while older {kib} + instances are running can cause data loss or upgrade failures. To identify the changes you need to make to upgrade, and to enable you to perform an Elasticsearch rolling upgrade with no downtime, you must upgrade to diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index cbe56b9e65894..74d097164c4a7 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -1,54 +1,127 @@ [[upgrade-migrations]] -=== Migrate saved objects +=== Upgrade migrations -Every time {kib} is upgraded it checks to see if all saved objects, such as dashboards, visualizations, and index patterns, are compatible with the new version. If any objects need to be updated, then the automatic saved object migration process is kicked off. +Every time {kib} is upgraded it checks to see if all saved objects, such as dashboards, visualizations, and index patterns, are compatible with the new version. If any saved objects need to be updated, then the automatic saved object migration process is kicked off. NOTE: 6.7 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[Upgrade Assistant] to help you prepare for your upgrade to 7.0. To access the assistant, go to *Management > 7.0 Upgrade Assistant*. +WARNING: The following instructions assumes {kib} is using the default index names. If the `kibana.index` or `xpack.tasks.index` configuration settings were changed these instructions will have to be adapted accordingly. + [float] [[upgrade-migrations-process]] -==== How the process works +==== Background -Saved objects are stored in an index named `.kibana_N`, where `N` is a number that increments over time as {kib} is upgraded. The index alias `.kibana` points to the latest up-to-date index for a given install. +Saved objects are stored in two indices: -NOTE: Prior to 6.5.0, saved objects were stored directly in an index named `.kibana`, so the first time you upgrade to {kib} version 6.5+, {kib} will migrate into `.kibana_1` and set `.kibana` up as an index alias. +* `.kibana_N`, or if set, the `kibana.index` configuration setting +* `.kibana_task_manager_N`, or if set, the `xpack.tasks.index` configuration setting + +For each of these indices, `N` is a number that increments every time {kib} runs an upgrade migration on that index. The index aliases `.kibana` and `.kibana_task_manager` point to the most up-to-date index. While {kib} is starting up and before serving any HTTP traffic, it checks to see if any internal mapping changes or data transformations for existing saved objects are required. -When changes are necessary, a new incremental `.kibana_N` index is created with updated mappings, then the saved objects are loaded in batches from the existing index, transformed to whatever extent necessary, and added to this new index. +When changes are necessary, a new migration is started. To ensure that only one {kib} instance performs the migration, each instance will attempt to obtain a migration lock by creating a new `.kibana_N+1` index. The instance that succeeds in creating the index will then read batches of documents from the existing index, migrate them, and write them to the new index. Once the objects are migrated, the lock is released by pointing the `.kibana` index alias the new upgraded `.kibana_N+1` index. + +Instances that failed to acquire a lock will log `Another Kibana instance appears to be migrating the index. Waiting for that migration to complete`. The instance will then wait until `.kibana` points to an upgraded index before starting up and serving HTTP traffic. -Once the objects are migrated, the `.kibana` index alias is updated to point to the new index, and {kib} finishes starting up and serving HTTP traffic. +NOTE: Prior to 6.5.0, saved objects were stored directly in an index named `.kibana`. After upgrading to version 6.5+, {kib} will migrate this index into `.kibana_N` and set `.kibana` up as an index alias. + +Prior to 7.4.0, task manager tasks were stored directly in an index name `.kibana_task_manager`. After upgrading to version 7.4+, {kib} will migrate this index into `.kibana_task_manager_N` and set `.kibana_task_manager` up as an index alias. [float] -[[upgrade-migrations-old-indices]] -==== Handling old `.kibana` indices +[[preventing-migration-failures]] +==== Preventing migration failures +This section highlights common causes of {kib} upgrade failures and how to prevent them. + +[float] +===== Corrupt saved objects +We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. + +[float] +===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings +Matching index templates which specify `settings.refresh_interval` or `mappings` are known to interfere with {kib} upgrades. + +Prevention: narrow down the index patterns of any user-defined index templates to ensure that these won't apply to new `.kibana*` indices. -After migrations have run, there will be multiple {kib} indices in {es}: (`.kibana_1`, `.kibana_2`, etc). {kib} only uses the index that the `.kibana` alias points to. The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. +Note: {kib} < 6.5 creates it's own index template called `kibana_index_template:.kibana` and index pattern `.kibana`. This index template will not interfere and does not need to be changed or removed. + +[float] +===== An unhealthy {es} cluster +Problems with your {es} cluster can prevent {kib} upgrades from succeeding. Ensure that your cluster has: + + * enough free disk space, at least twice the amount of storage taken up by the `.kibana` and `.kibana_task_manager` indices + * sufficient heap size + * a "green" cluster status + +[float] +===== Running different versions of {kib} connected to the same {es} index +Kibana does not support rolling upgrades. Stop all {kib} instances before starting a newer version to prevent upgrade failures and data loss. + +[float] +===== Incompatible `xpack.tasks.index` configuration setting +For {kib} < 7.5.1, if the task manager index is set to `.tasks` with the configuration setting `xpack.tasks.index: ".tasks"`, upgrade migrations will fail. {kib} 7.5.1 and later prevents this by refusing to start with an incompatible configuration setting. [float] -[[upgrade-migrations-errors]] -==== Handling errors during saved object migrations +[[resolve-migrations-failures]] +==== Resolving migration failures -If {kib} terminates unexpectedly while migrating a saved object index, some additional work may be required in order to get {kib} to re-attempt the migration. +If {kib} terminates unexpectedly while migrating a saved object index, manual intervention is required before {kib} will attempt to perform the migration again. Follow the advice in (preventing migration failures)[preventing-migration-failures] before retrying a migration upgrade. -For example, if the `.kibana` alias is pointing to `.kibana_4`, and there is a `.kibana_5` index in {es}, the `.kibana_5` index will need to be deleted. {kib} will never attempt to overwrite an existing index. +As mentioned above, {kib} will create a migration lock for each index that requires a migration by creating a new `.kibana_N+1` index. For example: if the `.kibana_task_manager` alias is pointing to `.kibana_task_manager_5` then the first {kib} that succeeds in creating `.kibana_task_manager_6` will obtain the lock to start migrations. + +However, if the instance that obtained the lock fails to migrate the index, all other {kib} instances will be blocked from performing this migration. This includes the instance that originally obtained the lock, it will be blocked from retrying the migration even when restarted. [float] -[[upgrade-migrations-multiple-instances]] -==== Support for multiple {kib} instances +===== Retry a migration by restoring a backup snapshot: + +1. Before proceeding ensure that you have a recent and successful backup snapshot of all `.kibana*` indices. +2. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. +3. Delete all saved object indices with `DELETE /.kibana*` +4. Restore the `.kibana* indices and their aliases from the backup snapshot. See {es} {ref}/modules-snapshots.html[snapshots] +5. Start up all {kib} instances to retry the upgrade migration. -If you're running multiple {kib} instances for a single index behind a load balancer, it's important that you stop all instances before upgrading, so you do not have multiple different versions of {kib} trying to perform saved object migrations. +[float] +===== (Not recommended) Retry a migration without a backup snapshot: -The first instance that triggers saved object migrations will run the entire process. Any other instances started up while a migration is running will log a message and then wait until saved object migration has completed before they start serving HTTP traffic. +1. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. +2. Identify any migration locks by comparing the output of `GET /_cat/aliases` and `GET /_cat/indices`. If e.g. `.kibana` is pointing to `.kibana_4` and there is a `.kibana_5` index, the `.kibana_5` index will act like a migration lock blocking further attempts. Be sure to check both the `.kibana` and `.kibana_task_manager` aliases and their indices. +3. Remove any migration locks e.g. `DELETE /.kibana_5`. +4. Start up all {kib} instances. [float] [[upgrade-migrations-rolling-back]] ==== Rolling back to a previous version of {kib} -When rolling {kib} back to a previous version, point the `.kibana` alias to -the appropriate {kib} index. When you have the previous version running again, -delete the more recent `.kibana_N` index or indices so that future upgrades are -based on the current {kib} index. You must restart {kib} to re-trigger the migration. +If you've followed the advice in (preventing migration failures)[preventing-migration-failures] and (resolving migration failures)[resolve-migrations-failures] and {kib} is still not able to upgrade successfully, you might choose to rollback {kib} until you're able to identify the root cause. + +WARNING: Before rolling back {kib}, ensure that the version you wish to rollback to is compatible with your {es} cluster. If the version you're rolling back to is not compatible, you will have to also rollback {es}. + +Any changes made after an upgrade will be lost when rolling back to a previous version. + +In order to rollback after a failed upgrade migration, the saved object indices might also have to be rolled back to be compatible with the previous {kibana} version. + +[float] +===== Rollback by restoring a backup snapshot: + +1. Before proceeding ensure that you have a recent and successful backup snapshot of all `.kibana*` indices. +2. Shutdown all {kib} instances to be 100% sure that there are no instances currently performing a migration. +3. Delete all saved object indices with `DELETE /.kibana*` +4. Restore the `.kibana* indices and their aliases from the backup snapshot. See {es} {ref}/modules-snapshots.html[snapshots] +5. Start up all {kib} instances on the older version you wish to rollback to. + +[float] +===== (Not recommended) Rollback without a backup snapshot: + +WARNING: {kib} does not run a migration for every saved object index on every upgrade. A {kib} version upgrade can cause no migrations, migrate only the `.kibana` or the `.kibana_task_manager` index or both. Carefully read the logs to ensure that you're only deleting indices created by a later version of {kib} to avoid data loss. + +1. Shutdown all {kib} instances to be 100% sure that there are no {kib} instances currently performing a migration. +2. Create a backup snapshot of the `.kibana*` indices. +3. Use the logs from the upgraded instances to identify which indices {kib} attempted to upgrade. The server logs will contain an entry like `[savedobjects-service] Creating index .kibana_4.` and/or `[savedobjects-service] Creating index .kibana_task_manager_2.` If no indices were created after upgrading {kib} then no further action is required to perform a rollback, skip ahead to step (5). If you're running multiple {kib} instances, be sure to inspect all instances' logs. +4. Delete each of the indices identified in step (2). e.g. `DELETE /.kibana_task_manager_2` +5. Inspect the output of `GET /_cat/aliases`. If either the `.kibana` and/or `.kibana_task_manager` alias is missing, these will have to be created manually. Find the latest index from the output of `GET /_cat/indices` and create the missing alias to point to the latest index. E.g. if the `.kibana` alias was missing and the latest index is `.kibana_3` create a new alias with `POST /.kibana_3/_aliases/.kibana`. +6. Start up {kib} on the older version you wish to rollback to. + +[float] +[[upgrade-migrations-old-indices]] +==== Handling old `.kibana_N` indices -WARNING: Rolling back to a previous {kib} version can result in saved object data loss if you had successfully upgraded and made changes to saved objects before rolling back. +After migrations have completed, there will be multiple {kib} indices in {es}: (`.kibana_1`, `.kibana_2`, etc). {kib} only uses the index that the `.kibana` alias points to. The other {kib} indices can be safely deleted, but are left around as a matter of historical record, and to facilitate rolling {kib} back to a previous version. \ No newline at end of file diff --git a/docs/setup/upgrade/upgrade-standard.asciidoc b/docs/setup/upgrade/upgrade-standard.asciidoc index df38427881d65..b27bb8867e624 100644 --- a/docs/setup/upgrade/upgrade-standard.asciidoc +++ b/docs/setup/upgrade/upgrade-standard.asciidoc @@ -12,11 +12,20 @@ If you've saved and/or exported objects in {kib} that rely on the necessary remediation steps as per those instructions. =========================================== +[float] +==== Upgrading multiple {kib} instances + +WARNING: Kibana does not support rolling upgrades. If you're running multiple {kib} instances, all instances should be stopped before upgrading. + +Different versions of {kib} running against the same {es} index, such as during a rolling upgrade, can cause upgrade migration failures and data loss. This is because acknowledged writes from the older instances could be written into the _old_ index while the migration is in progress. To prevent this from happening ensure that all old {kib} instances are shutdown before starting up instances on a newer version. + +The first instance that triggers saved object migrations will run the entire process. Any other instances started up while a migration is running will log a message and then wait until saved object migrations has completed before they start serving HTTP traffic. + [float] ==== Upgrade using a `deb` or `rpm` package . Stop the existing {kib} process using the appropriate command for your - system. + system. If you have multiple {kib} instances connecting to the same {es} cluster ensure that all instances are stopped before proceeding to the next step to avoid data loss. . Use `rpm` or `dpkg` to install the new package. All files should be placed in their proper locations and config files should not be overwritten. + @@ -43,8 +52,7 @@ otherwise {kib} will fail to start. don't overwrite the `config` or `data` directories. + + -- -IMPORTANT: If you use {monitor-features}, you must re-use the data directory when you -upgrade {kib}. Otherwise, the {kib} instance is assigned a new persistent UUID +IMPORTANT: If you use {monitor-features}, you must re-use the data directory when you upgrade {kib}. Otherwise, the {kib} instance is assigned a new persistent UUID and becomes a new instance in the monitoring data. -- @@ -57,5 +65,5 @@ and becomes a new instance in the monitoring data. . Install the appropriate versions of all your plugins for your new installation using the `kibana-plugin` script. Check out the <> documentation for more information. -. Stop the old {kib} process. +. Stop the old {kib} process. If you have multiple {kib} instances connecting to the same {es} cluster ensure that all instances are stopped before proceeding to the next step to avoid data loss. . Start the new {kib} process. diff --git a/docs/spaces/images/edit-space-feature-visibility.png b/docs/spaces/images/edit-space-feature-visibility.png index bc983bde2679c..3a74c21fe2635 100644 Binary files a/docs/spaces/images/edit-space-feature-visibility.png and b/docs/spaces/images/edit-space-feature-visibility.png differ diff --git a/docs/spaces/images/edit-space.png b/docs/spaces/images/edit-space.png index 68ffea23c4ac4..c4565a3ba0f4a 100644 Binary files a/docs/spaces/images/edit-space.png and b/docs/spaces/images/edit-space.png differ diff --git a/docs/spaces/images/space-selector.png b/docs/spaces/images/space-selector.png index 908c5360acd39..b2576cbc9acc4 100644 Binary files a/docs/spaces/images/space-selector.png and b/docs/spaces/images/space-selector.png differ diff --git a/docs/spaces/images/spaces-roles.png b/docs/spaces/images/spaces-roles.png old mode 100755 new mode 100644 index 2ecbfed6017c4..407926895daa8 Binary files a/docs/spaces/images/spaces-roles.png and b/docs/spaces/images/spaces-roles.png differ diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index 9e505b8bfe045..81f3945779503 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -25,11 +25,16 @@ Kibana supports spaces in several ways. You can: * <> * <> +[float] +==== Required permissions + +The `kibana_admin` role or equivilent is required to manage **Spaces**. + [float] [[spaces-managing]] === View, create, and delete spaces -Open the menu, then go to *Stack Management > {kib} > Spaces* for an overview of your spaces. This view provides actions +Open the main menu, then click *Stack Management > Spaces* for an overview of your spaces. This view provides actions for you to create, edit, and delete spaces. [role="screenshot"] @@ -94,8 +99,8 @@ image::spaces/images/spaces-roles.png["Controlling features visiblity"] [[spaces-moving-objects]] === Move saved objects between spaces -To <> from one space to another, open the menu, -then go to *Stack Management > {kib} > Saved objects*. +To <> from one space to another, open the main menu, +then click *Stack Management > Saved Objects*. Alternately, you can move objects using {kib}'s <> interface. diff --git a/docs/uptime/images/uptime-overview.png b/docs/uptime/images/uptime-overview.png deleted file mode 100644 index 25c88b2d14287..0000000000000 Binary files a/docs/uptime/images/uptime-overview.png and /dev/null differ diff --git a/docs/uptime/index.asciidoc b/docs/uptime/index.asciidoc deleted file mode 100644 index 66c9e9357420f..0000000000000 --- a/docs/uptime/index.asciidoc +++ /dev/null @@ -1,19 +0,0 @@ -[chapter] -[role="xpack"] -[[xpack-uptime]] -= Uptime - -The Uptime app in {kib} enables you to monitor the status of network endpoints via HTTP/S, TCP, and ICMP. -You can explore endpoint status over time, drill down into specific monitors, -and view a high-level snapshot of your environment at any point in time. - -[role="screenshot"] -image::images/uptime-overview.png[Uptime app overview] - -[float] -=== Get started - -To get started with Elastic Uptime, refer to {uptime-guide}/install-uptime.html[Install Uptime]. - - - diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index 9301224e6df48..aad192dbddb30 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -89,8 +89,8 @@ image::user/alerting/images/pagerduty-integration.png[PagerDuty Integrations tab + * Create a connector as part of creating an alert by selecting PagerDuty in the *Actions* section of the alert configuration and selecting *Add new*. -* Alternatively, create a connector by navigating to *Management* from the {kib} navbar and selecting -*Alerts and Actions*. Then, select the *Connectors* tab, click the *Create connector* button, and select the PagerDuty option. +* Alternatively, create a connector. To create a connector, open the main menu, click *Stack Management* > +Alerts and Actions*, select *Connectors*, click *Create connector*, then select the PagerDuty option. . Configure the connector by giving it a name and entering the Integration Key, optionally entering a custom API URL. + @@ -99,7 +99,7 @@ See <> for how to obtain the endpoint and . Save the Connector. -. Create an alert using *Management > Alerts and Actions* or the application of your choice. +. To create an alert, open the main menu, then click *Stack Management > Alerts and Actions* or the application of your choice. . Set up an action using your PagerDuty connector, by determining: + @@ -120,7 +120,7 @@ To remove a PagerDuty connector from an alert, simply remove it from the *Actions* section of that alert, using the remove (x) icon. This will disable the integration for the particular alert. -To delete the connector entirely, go to *Management > Alerts and Actions*. +To delete the connector entirely, open the main menu, then click *Stack Management > Alerts and Actions*. Select the *Connectors* tab, and then click on the delete icon. This is an irreversible action and impacts all alerts that use this connector. diff --git a/docs/user/alerting/action-types/pre-configured-connectors.asciidoc b/docs/user/alerting/action-types/pre-configured-connectors.asciidoc index e3f1703f08e88..722607ac05f87 100644 --- a/docs/user/alerting/action-types/pre-configured-connectors.asciidoc +++ b/docs/user/alerting/action-types/pre-configured-connectors.asciidoc @@ -61,7 +61,7 @@ Sensitive properties, such as passwords, can also be stored in the < {kib} > Alerts and Actions*, preconfigured connectors +When you open the main menu, click *Stack Management > Alerts and Actions*. Preconfigured connectors appear on the <>, regardless of which space you are in. They are tagged as “preconfigured”, and you cannot delete them. @@ -101,7 +101,7 @@ This example shows a preconfigured action type with one out-of-the box connector [[managing-pre-configured-action-types]] To attach a preconfigured action to an alert: -. Open the menu, then go to *Stack Management > {kib} > Alerts and Actions*, open the *Connectors* tab. +. Open the main menu, click *Stack Management > Alerts and Actions*, then open the *Connectors* tab. . Click *Create connector.* diff --git a/docs/user/alerting/alert-types.asciidoc b/docs/user/alerting/alert-types.asciidoc index 4a99c70f9d961..f71e43c5defc7 100644 --- a/docs/user/alerting/alert-types.asciidoc +++ b/docs/user/alerting/alert-types.asciidoc @@ -2,7 +2,7 @@ [[alert-types]] == Alert types -{kib} supplies alerts types in two ways: some are built into {kib}, while domain-specific alert types are registered by {kib} apps such as <>, <>, and <>. +{kib} supplies alerts types in two ways: some are built into {kib}, while domain-specific alert types are registered by {kib} apps such as <>, <>, and <>. This section covers built-in alert types. For domain-specific alert types, refer to the documentation for that app. diff --git a/docs/user/alerting/alerting-getting-started.asciidoc b/docs/user/alerting/alerting-getting-started.asciidoc index bdb72b1658cd2..f8656b87cbe04 100644 --- a/docs/user/alerting/alerting-getting-started.asciidoc +++ b/docs/user/alerting/alerting-getting-started.asciidoc @@ -6,7 +6,7 @@ beta[] -- -Alerting allows you to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with <>, <>, <>, <>, can be centrally managed from the <> UI, and provides a set of built-in <> and <> for you to use. +Alerting allows you to detect complex conditions within different {kib} apps and trigger actions when those conditions are met. Alerting is integrated with <>, <>, <>, <>, can be centrally managed from the <> UI, and provides a set of built-in <> and <> for you to use. image::images/alerting-overview.png[Alerts and actions UI] @@ -148,7 +148,7 @@ Functionally, {kib} alerting differs in that: * {kib} alerts tracks and persists the state of each detected condition through *alert instances*. This makes it possible to mute and throttle individual instances, and detect changes in state such as resolution. * Actions are linked to *alert instances* in {kib} alerting. Actions are fired for each occurrence of a detected condition, rather than for the entire alert. -At a higher level, {kib} alerts allow rich integrations across use cases like <>, <>, <>, and <>. +At a higher level, {kib} alerts allow rich integrations across use cases like <>, <>, <>, and <>. Pre-packaged *alert types* simplify setup, hide the details complex domain-specific detections, while providing a consistent interface across {kib}. [float] @@ -170,9 +170,9 @@ If you are using an *on-premises* Elastic Stack deployment with <> -* <> +* <> * <> -* <> +* <> See <> for more information on configuring roles that provide access to these features. diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index 7f201d2c39e89..89a487ca8fb32 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -2,7 +2,7 @@ [[defining-alerts]] == Defining alerts -{kib} alerts can be created in a variety of apps including <>, <>, <>, <> and from <> UI. While alerting details may differ from app to app, they share a common interface for defining and configuring alerts that this section describes in more detail. +{kib} alerts can be created in a variety of apps including <>, <>, <>, <> and from <> UI. While alerting details may differ from app to app, they share a common interface for defining and configuring alerts that this section describes in more detail. [float] === Alert flyout diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 0b0eb7a318495..c10641bb3a6b9 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -17,6 +17,8 @@ With Canvas, you can: * Focus the data you want to display with filters. +To begin, open the main menu, then click *Canvas*. + [role="screenshot"] image::images/canvas-gs-example.png[Getting started example] @@ -26,7 +28,8 @@ For a quick overview of Canvas, watch link:https://www.youtube.com/watch?v=ZqvF_ [[create-workpads]] == Create workpads -A _workpad_ provides you with a space where you can build presentations of your live data. +A _workpad_ provides you with a space where you can build presentations of your live data. With Canvas, +you can create a workpad from scratch, start with a preconfigured workpad, import an existing workpad, or use a sample data workpad. [float] [[start-with-a-blank-workpad]] @@ -34,19 +37,15 @@ A _workpad_ provides you with a space where you can build presentations of your To use the background colors, images, and data of your choice, start with a blank workpad. -. Open the menu, then go to *Canvas*. - -. On the *Canvas workpads* view, click *Create workpad*. +. On the *Canvas workpads* page, click *Create workpad*. -. Add a *Name* to your workpad. +. Specify the *Workpad settings*. -. In the *Width* and *Height* fields, specify the size. +.. Add a *Name* to your workpad. -. Select the layout. -+ -For example, click *720p* for a traditional presentation layout. +.. In the *Width* and *Height* fields, specify the size, or select one of default layouts. -. Click the *Background color* picker, then select the background color for your workpad. +.. Click the *Background* color picker, then select the color for your workpad. + [role="screenshot"] image::images/canvas-background-color-picker.png[Canvas color picker] @@ -57,9 +56,7 @@ image::images/canvas-background-color-picker.png[Canvas color picker] If you're unsure about where to start, you can use one of the preconfigured templates that come with Canvas. -. Open the menu, then go to *Canvas*. - -. On the *Canvas workpads* view, select *Templates*. +. On the *Canvas workpads* page, select *Templates*. . Click the preconfigured template that you want to use. @@ -69,17 +66,15 @@ If you're unsure about where to start, you can use one of the preconfigured temp [[import-existing-workpads]] === Import existing workpads -When you want to use a workpad that someone else has already started, import the JSON file into Canvas. - -. Open the menu, then go to *Canvas*. +When you want to use a workpad that someone else has already started, import the JSON file. -. On the *Canvas workpads* view, click and drag the file to the *Import workpad JSON file* field. +To begin, drag the file to the *Import workpad JSON file* field on the *Canvas workpads* page. [float] [[use-sample-data-workpads]] === Use sample data workpads -Each of the sample data sets comes with a Canvas workpad that you can use for your own workpad inspiration. +Each of the {kib} sample data sets comes with a workpad that you can use for your own workpad inspiration. . Add a {kibana-ref}/add-sample-data.html[sample data set]. @@ -123,12 +118,12 @@ To save a group of elements, press and hold Shift, select the elements you want Elements are saved in *Add element > My elements*. [float] -[[add-existing-visuualizations]] -=== Add existing visualizations +[[add-saved-objects]] +=== Add saved objects Add <> to your workpad, such as maps and visualizations. -. Click *Add element > Add from Visualize Library*. +. Click *Add element > Add from {kib}*. . Select the saved object you want to add. + diff --git a/docs/user/dashboard/dashboard-drilldown.asciidoc b/docs/user/dashboard/dashboard-drilldown.asciidoc index e50c1281beede..bdff7355d7467 100644 --- a/docs/user/dashboard/dashboard-drilldown.asciidoc +++ b/docs/user/dashboard/dashboard-drilldown.asciidoc @@ -39,7 +39,7 @@ Create the *Host Overview* drilldown shown above. *Set up the dashboards* -. Add the <> data set. +. Add the sample web logs data set. . Create a new dashboard, called `Host Overview`, and include these visualizations from the sample data set: @@ -57,8 +57,8 @@ TIP: If you don’t see data for a panel, try changing the time range. . Set a search and filter. + [%hardbreaks] -Search: `extension.keyword:( “gz” or “css” or “deb”)` -Filter: `geo.src : CN` +Search: `extension.keyword: ("gz" or "css" or "deb")` +Filter: `geo.src: CN` *Create the drilldown* @@ -94,4 +94,3 @@ image::images/drilldown_on_panel.png[Drilldown on pie chart that navigates to an + You are navigated to your destination dashboard. Verify that the search query, filters, and time range are carried over. - diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index c8bff91be91a6..a28034075e1c6 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -8,7 +8,7 @@ A _dashboard_ is a collection of panels that you use to analyze your data. On a you can rearrange and tell a story about your data. Panels contain everything you need, including visualizations, interactive controls, markdown, and more. -With *Dashboard*s, you can: +With *Dashboard*, you can: * Add multiple panels to see many aspects and views of your data in one place. @@ -22,7 +22,7 @@ With *Dashboard*s, you can: * Generate reports based on your findings. -To begin, open the menu, go to *Dashboard*, then click *Create dashboard*. +To begin, open the main menu, click *Dashboard*, then click *Create dashboard*. [role="screenshot"] image:images/Dashboard_example.png[Example dashboard] @@ -426,33 +426,37 @@ Ready to try out Timelion? For step-by-step tutorials, refer to: [[timelion-deprecation]] ==== Timelion app deprecation -Deprecated since 7.0, the Timelion app will be removed in 8.0. If you have any Timelion worksheets, you must migrate them to a dashboard. +In 7.0 and later, *Timelion* app is deprecated. In 8.0 and later, *Timelion* app is removed from {kib}. To prepare for the removal of *Timelion* app, you must migrate *Timelion* app worksheets to a dashboard. -NOTE: Only the Timelion app is deprecated. {kib} continues to support Timelion -visualizations on dashboards and in Visualize and Canvas. +NOTE: Only *Timelion* app is deprecated. {kib} continues to support *Timelion* +visualizations in *Dashboard*, *Visualize*, and *Canvas*. -To migrate a Timelion worksheet to a dashboard: +To migrate a *Timelion* worksheet to a dashboard: -. Open the menu, click **Dashboard**, then click **Create dashboard**. +. Open the main menu, click *Dashboard*, then click *Create dashboard*. -. On the dashboard, click **Create New**, then select the Timelion visualization. +. For each *Timelion* app worksheet, complete the following steps. -. On a new tab, open the Timelion app, select the chart you want to copy, and copy its expression. +.. On the dashboard, click *Create New*, then click *Timelion* on the *New Visualization* window. + +.. Open a new tab, open the *Timelion* app, select the chart you want to copy, then copy the chart expression. + [role="screenshot"] -image::images/timelion-copy-expression.png[] +image::images/timelion-copy-expression.png[Timelion app chart] -. Return to the other tab and paste the copied expression to the *Timelion Expression* field and click **Update**. +.. Go to *Timelion*, paste the chart expression in the *Timelion expression* field, then click *Update*. + [role="screenshot"] -image::images/timelion-vis-paste-expression.png[] +image::images/timelion-vis-paste-expression.png[Timelion advanced editor UI] + +.. In the toolbar, click *Save*. -. Save the new visualization, give it a name, and click **Save and Return**. +.. On the *Save visualization* window, enter the visualization *Title*, then click *Save and return*. + -Your Timelion visualization will appear on the dashboard. Repeat this for all your charts on each worksheet. +The Timelion visualization panel appears on the dashboard. + [role="screenshot"] -image::images/timelion-dashboard.png[] +image::images/timelion-dashboard.png[Final dashboard with saved Timelion app worksheet] [float] [[save-panels]] @@ -460,7 +464,7 @@ image::images/timelion-dashboard.png[] When you’ve finished making changes, save the panels. -. Click *Save*. +. In the toolbar, click *Save*. . Add the *Title* and optional *Description*. . Click *Save and return*. diff --git a/docs/user/dashboard/edit-dashboards.asciidoc b/docs/user/dashboard/edit-dashboards.asciidoc index 7534ea1e9e9fb..7b712b355b315 100644 --- a/docs/user/dashboard/edit-dashboards.asciidoc +++ b/docs/user/dashboard/edit-dashboards.asciidoc @@ -78,7 +78,7 @@ Put the dashboard in *Edit* mode, then use the following options: * To resize, click the resize control, then drag to the new dimensions. -* To delete, open the panel menu, then select Delete from dashboard. When you delete a panel from the dashboard, the +* To delete, open the panel menu, then select *Delete from dashboard*. When you delete a panel from the dashboard, the visualization or saved search from the panel is still available in Kibana. [float] diff --git a/docs/user/dashboard/images/drilldown_on_piechart.gif b/docs/user/dashboard/images/drilldown_on_piechart.gif index c9b3311df0325..c438e14371887 100644 Binary files a/docs/user/dashboard/images/drilldown_on_piechart.gif and b/docs/user/dashboard/images/drilldown_on_piechart.gif differ diff --git a/docs/user/dashboard/images/timelion-copy-expression.png b/docs/user/dashboard/images/timelion-copy-expression.png new file mode 100644 index 0000000000000..a9c3afe9b060f Binary files /dev/null and b/docs/user/dashboard/images/timelion-copy-expression.png differ diff --git a/docs/visualize/images/timelion-vis-paste-expression.png b/docs/user/dashboard/images/timelion-vis-paste-expression.png similarity index 100% rename from docs/visualize/images/timelion-vis-paste-expression.png rename to docs/user/dashboard/images/timelion-vis-paste-expression.png diff --git a/docs/user/dashboard/images/url_drilldown_go_to_github.gif b/docs/user/dashboard/images/url_drilldown_go_to_github.gif index 7cca3f72d5a68..3a3b00dc0e2ce 100644 Binary files a/docs/user/dashboard/images/url_drilldown_go_to_github.gif and b/docs/user/dashboard/images/url_drilldown_go_to_github.gif differ diff --git a/docs/user/dashboard/share-dashboards.asciidoc b/docs/user/dashboard/share-dashboards.asciidoc index cfa146d60fdac..6c05240c934e8 100644 --- a/docs/user/dashboard/share-dashboards.asciidoc +++ b/docs/user/dashboard/share-dashboards.asciidoc @@ -23,5 +23,5 @@ tools. To create a short URL, you must have write access to {kib}. [[import-dashboards]] === Export the dashboard -To export the dashboard, open the menu, then click *Stack Management > Saved Objects*. For more information, +To export the dashboard, open the main menu, then click *Stack Management > Saved Objects*. For more information, refer to <>. \ No newline at end of file diff --git a/docs/user/dashboard/url-drilldown.asciidoc b/docs/user/dashboard/url-drilldown.asciidoc index 620a2d2056bf1..b71dfb016c765 100644 --- a/docs/user/dashboard/url-drilldown.asciidoc +++ b/docs/user/dashboard/url-drilldown.asciidoc @@ -36,7 +36,7 @@ The following panels support URL drilldowns: This example shows how to create the "Show on Github" drilldown shown above. -. Add the <> data set. +. Add the sample web logs data set. . Open the *[Logs] Web traffic* dashboard. This isn’t data from Github, but it should work for demonstration purposes. . In the dashboard menu bar, click *Edit*. . In *[Logs] Visitors by OS*, open the panel menu, and then select *Create drilldown*. diff --git a/docs/user/dashboard/vega-reference.asciidoc b/docs/user/dashboard/vega-reference.asciidoc index 6fd30690b988e..378f7a53a6650 100644 --- a/docs/user/dashboard/vega-reference.asciidoc +++ b/docs/user/dashboard/vega-reference.asciidoc @@ -64,16 +64,17 @@ Override it by providing a different `stroke`, `fill`, or `color` (Vega-Lite) va [[vega-queries]] ==== Writing {es} queries in Vega -experimental[] {kib} extends the Vega https://vega.github.io/vega/docs/data/[data] elements -with support for direct {es} queries specified as a `url`. +{kib} extends the Vega https://vega.github.io/vega/docs/data/[data] elements +with support for direct {es} queries specified as `url`. -Because of this, {kib} is **unable to support dynamically loaded data**, +{kib} is **unable to support dynamically loaded data**, which would otherwise work in Vega. All data is fetched before it's passed to the Vega renderer. -To define an {es} query in Vega, set the `url` to an object. {kib} will parse +To define an {es} query in Vega, set the `url` to an object. {kib} parses the object looking for special tokens that allow your query to integrate with {kib}. -These tokens are: + +Tokens include the following: * `%context%: true`: Set at the top level, and replaces the `query` section with filters from dashboard * `%timefield%: `: Set at the top level, integrates the query with the dashboard time filter @@ -87,8 +88,7 @@ These tokens are: * `"%dashboard_context-filter_clause%"`: String replaced by an object containing filters * `"%dashboard_context-must_not_clause%"`: String replaced by an object containing filters -Putting this together, an example query that counts the number of documents in -a specific index: +For example, the following query counts the number of documents in a specific index: [source,yaml] ---- diff --git a/docs/user/getting-started.asciidoc b/docs/user/getting-started.asciidoc deleted file mode 100644 index a877f6a66a79a..0000000000000 --- a/docs/user/getting-started.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[[get-started]] -= Get started - -[partintro] --- - -Ready to try out {kib} and see what it can do? The quickest way to get started with {kib} is to set up on Cloud, then add a sample data set to explore the full range of {kib} features. - -[float] -[[set-up-on-cloud]] -== Set up on cloud - -include::{docs-root}/shared/cloud/ess-getting-started.asciidoc[] - -[float] -[[gs-get-data-into-kibana]] -== Get data into {kib} - -The easiest way to get data into {kib} is to add a sample data set. - -{kib} has several sample data sets that you can use before loading your own data: - -* *Sample eCommerce orders* includes visualizations for tracking product-related information, -such as cost, revenue, and price. - -* *Sample flight data* includes visualizations for monitoring flight routes. - -* *Sample web logs* includes visualizations for monitoring website traffic. - -To use the sample data sets: - -. Go to the home page. - -. Click *Load a data set and a {kib} dashboard*. - -. Click *View data* and view the prepackaged dashboards, maps, and more. - -[role="screenshot"] -image::getting-started/images/add-sample-data.png[] - -NOTE: The timestamps in the sample data sets are relative to when they are installed. -If you uninstall and reinstall a data set, the timestamps change to reflect the most recent installation. - -[float] -== Next steps - -* To get a hands-on experience creating visualizations, follow the <> tutorial. - -* If you're ready to load an actual data set and build a dashboard, follow the <> tutorial. - --- - -include::{kib-repo-dir}/getting-started/tutorial-sample-data.asciidoc[] - -include::{kib-repo-dir}/getting-started/tutorial-full-experience.asciidoc[] - -include::{kib-repo-dir}/getting-started/tutorial-define-index.asciidoc[] - -include::{kib-repo-dir}/getting-started/tutorial-discovering.asciidoc[] - -include::{kib-repo-dir}/getting-started/tutorial-visualizing.asciidoc[] diff --git a/docs/user/graph/getting-started.asciidoc b/docs/user/graph/getting-started.asciidoc index aca6d40a3532e..086c0707b3c2c 100644 --- a/docs/user/graph/getting-started.asciidoc +++ b/docs/user/graph/getting-started.asciidoc @@ -9,7 +9,7 @@ You must index data into {es} before you can create a graph. [[exploring-connections]] === Graph a data connection -. Open the menu, then go to *Graph*. +. Open the main menu, then click *Graph*. + If this is your first graph, follow the prompts to create it. For subsequent graphs, click *New*. diff --git a/docs/user/graph/images/graph-add-query.png b/docs/user/graph/images/graph-add-query.png index 1b233e3ef8b69..93ddf6a6132f4 100644 Binary files a/docs/user/graph/images/graph-add-query.png and b/docs/user/graph/images/graph-add-query.png differ diff --git a/docs/user/graph/images/graph-link-summary.png b/docs/user/graph/images/graph-link-summary.png index 4c75be00de0f5..a3dfdc0f79d96 100644 Binary files a/docs/user/graph/images/graph-link-summary.png and b/docs/user/graph/images/graph-link-summary.png differ diff --git a/docs/user/graph/images/graph-url-connections.png b/docs/user/graph/images/graph-url-connections.png index 4f8c163ab764b..34b57d489b048 100644 Binary files a/docs/user/graph/images/graph-url-connections.png and b/docs/user/graph/images/graph-url-connections.png differ diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index 608cf1431c934..afbd8cb5a166d 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -2,6 +2,8 @@ include::introduction.asciidoc[] include::whats-new.asciidoc[] +include::{kib-repo-dir}/getting-started/quick-start-guide.asciidoc[] + include::setup.asciidoc[] include::monitoring/configuring-monitoring.asciidoc[leveloffset=+1] @@ -11,8 +13,6 @@ include::monitoring/monitoring-kibana.asciidoc[leveloffset=+2] include::security/securing-kibana.asciidoc[] -include::getting-started.asciidoc[] - include::discover.asciidoc[] include::dashboard/dashboard.asciidoc[] @@ -27,14 +27,8 @@ include::graph/index.asciidoc[] include::{kib-repo-dir}/observability/index.asciidoc[] -include::{kib-repo-dir}/logs/index.asciidoc[] - -include::{kib-repo-dir}/infrastructure/index.asciidoc[] - include::{kib-repo-dir}/apm/index.asciidoc[] -include::{kib-repo-dir}/uptime/index.asciidoc[] - include::{kib-repo-dir}/siem/index.asciidoc[] include::dev-tools.asciidoc[] @@ -43,7 +37,7 @@ include::monitoring/index.asciidoc[] include::management.asciidoc[] -include::{kib-repo-dir}/ingest_manager/ingest-manager.asciidoc[] +include::{kib-repo-dir}/fleet/fleet.asciidoc[] include::reporting/index.asciidoc[] diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 079d183dd959d..91f149d5cdb3c 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -20,16 +20,16 @@ and more — all from the convenience of a {kib} UI. document discovery to SIEM, {kib} is the portal for accessing these and other capabilities. [role="screenshot"] -image::images/intro-kibana.png[] +image::images/intro-kibana.png[Kibana home page] [float] [[get-data-into-kibana]] -=== Add data +=== Ingest data -{kib} is designed to use {es} as a data source. Think of Elasticsearch as the engine that stores +{kib} is designed to use {es} as a data source. Think of {es} as the engine that stores and processes the data, with {kib} sitting on top. -From the home page, {kib} provides these options for adding data: +From the home page, {kib} provides these options for ingesting data: * Import data using the https://www.elastic.co/blog/importing-csv-and-log-data-into-elasticsearch-with-file-data-visualizer[File Data visualizer]. @@ -60,7 +60,7 @@ search for hidden insights and relationships. Ask your questions, and then narrow the results to just the data you want. [role="screenshot"] -image::images/intro-discover.png[] +image::images/intro-discover.png[Discover UI] [float] [[visualize-and-analyze]] @@ -79,7 +79,7 @@ use <> to collect them in one place. A dashboard provides insights into your data from multiple perspectives. [role="screenshot"] -image::images/intro-dashboard.png[] +image::images/intro-dashboard.png[Sample eCommerce data set dashboard] {kib} also offers these visualization features: @@ -112,7 +112,7 @@ You can even choose which features to enable within each space. Don’t need Machine learning in your “Executive” space? Simply turn it off. [role="screenshot"] -image::images/intro-spaces.jpg[] +image::images/intro-spaces.png[Space selector screen] You can take this all one step further with Kibana’s security features, and control which users have access to each space. {kib} allows for fine-grained @@ -155,6 +155,5 @@ and start exploring data in minutes. You can also <> — no code, no additional infrastructure required. -Our <> and in-product guidance can -help you get up and running, faster. Click the help icon image:images/intro-help-icon.png[] -in the top navigation bar for help with questions or to provide feedback. +Our <> and in-product guidance can +help you get up and running, faster. Click the help icon image:images/intro-help-icon.png[Help icon in navigation bar] for help with questions or to provide feedback. diff --git a/docs/user/introduction/images/intro-spaces.png b/docs/user/introduction/images/intro-spaces.png new file mode 100644 index 0000000000000..b2576cbc9acc4 Binary files /dev/null and b/docs/user/introduction/images/intro-spaces.png differ diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 537639d5a370d..3a60449d8fa24 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -6,6 +6,10 @@ *Stack Management* is home to UIs for managing all things Elastic Stack— indices, clusters, licenses, UI settings, index patterns, spaces, and more. + +Access to individual features is governed by {es} and {kib} privileges. +Consult your administrator if you do not have the appropriate access. + [float] [[manage-ingest]] == Ingest @@ -35,12 +39,12 @@ quickly deploy configuration changes to all Beats running across your enterprise [cols="50, 50"] |=== -a| <> +a| {ref}/index-mgmt.html[Index Management] | View index settings, mappings, and statistics and perform operations, such as refreshing, flushing, and clearing the cache. Practicing good index management ensures that your data is stored cost effectively. -| <> +| {ref}/index-lifecycle-management.html[Index Lifecycle Policies] |Create a policy for defining the lifecycle of an index as it ages through the hot, warm, cold, and delete phases. Such policies help you control operation costs @@ -180,16 +184,6 @@ include::{kib-repo-dir}/management/alerting/connector-management.asciidoc[] include::{kib-repo-dir}/management/managing-beats.asciidoc[] -include::{kib-repo-dir}/management/index-lifecycle-policies/intro-to-lifecycle-policies.asciidoc[] - -include::{kib-repo-dir}/management/index-lifecycle-policies/create-policy.asciidoc[] - -include::{kib-repo-dir}/management/index-lifecycle-policies/manage-policy.asciidoc[] - -include::{kib-repo-dir}/management/index-lifecycle-policies/add-policy-to-index.asciidoc[] - -include::{kib-repo-dir}/management/managing-indices.asciidoc[] - include::{kib-repo-dir}/management/ingest-pipelines/ingest-pipelines.asciidoc[] include::{kib-repo-dir}/management/managing-fields.asciidoc[] diff --git a/docs/user/monitoring/beats-details.asciidoc b/docs/user/monitoring/beats-details.asciidoc index 3d7a726d2f8a2..dbd9aecd04768 100644 --- a/docs/user/monitoring/beats-details.asciidoc +++ b/docs/user/monitoring/beats-details.asciidoc @@ -9,7 +9,7 @@ If you are monitoring Beats, the *Stack Monitoring* page in {kib} contains a panel for Beats in the cluster overview. [role="screenshot"] -image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="images/monitoring-beats.jpg"] +image::user/monitoring/images/monitoring-beats.jpg["Monitoring Beats",link="user/monitoring/images/monitoring-beats.jpg"] To view an overview of the Beats data in the cluster, click *Overview*. The overview page has a section for activity in the last day, which is a real-time @@ -24,7 +24,7 @@ cluster. All columns are sortable. Clicking a Beat name takes you to the detail page. For example: [role="screenshot"] -image::user/monitoring/images/monitoring-beats-detail.jpg["Monitoring details for Filebeat",link="images/monitoring-beats-detail.jpg"] +image::user/monitoring/images/monitoring-beats-detail.jpg["Monitoring details for Filebeat",link="user/monitoring/images/monitoring-beats-detail.jpg"] The detail page contains a summary bar and charts. There are more charts on this page than the overview page and they are specific to a single Beat instance. diff --git a/docs/user/monitoring/kibana-alerts.asciidoc b/docs/user/monitoring/kibana-alerts.asciidoc index 1ac5c385f8ed5..300497126c3e5 100644 --- a/docs/user/monitoring/kibana-alerts.asciidoc +++ b/docs/user/monitoring/kibana-alerts.asciidoc @@ -31,6 +31,33 @@ default, the trigger condition is set at 85% or more averaged over the last 5 minutes. The alert is grouped across all the nodes of the cluster by running checks on a schedule time of 1 minute with a re-notify internal of 1 day. +[discrete] +[[kibana-alerts-disk-usage-threshold]] +== Disk usage threshold + +This alert is triggered when a node is nearly at disk capacity. By +default, the trigger condition is set at 80% or more averaged over the last 5 +minutes. The alert is grouped across all the nodes of the cluster by running +checks on a schedule time of 1 minute with a re-notify internal of 1 day. + +[discrete] +[[kibana-alerts-jvm-memory-threshold]] +== JVM memory threshold + +This alert is triggered when a node runs a consistently high JVM memory usage. By +default, the trigger condition is set at 85% or more averaged over the last 5 +minutes. The alert is grouped across all the nodes of the cluster by running +checks on a schedule time of 1 minute with a re-notify internal of 1 day. + +[discrete] +[[kibana-alerts-missing-monitoring-data]] +== Missing monitoring data + +This alert is triggered when any stack products nodes or instances stop sending +monitoring data. By default, the trigger condition is set to missing for 15 minutes +looking back 1 day. The alert is grouped across all the nodes of the cluster by running +checks on a schedule time of 1 minute with a re-notify internal of 6 hours. + NOTE: Some action types are subscription features, while others are free. For a comparison of the Elastic subscription levels, see the alerting section of the {subscriptions}[Subscriptions page]. diff --git a/docs/user/monitoring/monitoring-kibana.asciidoc b/docs/user/monitoring/monitoring-kibana.asciidoc index 9d735ea1fe3db..047fcc08775e6 100644 --- a/docs/user/monitoring/monitoring-kibana.asciidoc +++ b/docs/user/monitoring/monitoring-kibana.asciidoc @@ -48,7 +48,7 @@ By default, if you are running {kib} locally, go to `http://localhost:5601/`. If {security-features} are enabled, log in. -- -... Open the menu, then go to *Stack Monitoring*. If data collection is +... Open the main menu, then click *Stack Monitoring*. If data collection is disabled, you are prompted to turn it on. ** From the Console or command line, set `xpack.monitoring.collection.enabled` diff --git a/docs/user/monitoring/viewing-metrics.asciidoc b/docs/user/monitoring/viewing-metrics.asciidoc index 0c48e3b7d011d..9507b70c4f72e 100644 --- a/docs/user/monitoring/viewing-metrics.asciidoc +++ b/docs/user/monitoring/viewing-metrics.asciidoc @@ -80,7 +80,7 @@ By default, if you are running {kib} locally, go to `http://localhost:5601/`. If the Elastic {security-features} are enabled, log in. -- -. Open *Stack Monitoring*. +. Open the main menu, then click *Stack Monitoring*. + -- If data collection is disabled, you are prompted to turn on data collection. diff --git a/docs/user/reporting/automating-report-generation.asciidoc b/docs/user/reporting/automating-report-generation.asciidoc index 371855deb2f3c..413573e7ec182 100644 --- a/docs/user/reporting/automating-report-generation.asciidoc +++ b/docs/user/reporting/automating-report-generation.asciidoc @@ -13,7 +13,7 @@ URL that triggers a report to generate. To create the POST URL for PDF reports: -. Go to *Dashboard*, then open the visualization or dashboard. +. Open then main menu, click *Dashboard*, then open a dashboard. + To specify a relative or absolute time period, use the time filter. diff --git a/docs/user/reporting/configuring-reporting.asciidoc b/docs/user/reporting/configuring-reporting.asciidoc index 6a0c44cf4c2a4..a8b76f36b9a84 100644 --- a/docs/user/reporting/configuring-reporting.asciidoc +++ b/docs/user/reporting/configuring-reporting.asciidoc @@ -75,3 +75,4 @@ to point to a proxy host requires that the Kibana server has network access to the proxy. include::{kib-repo-dir}/user/security/reporting.asciidoc[] +include::network-policy.asciidoc[] diff --git a/docs/user/reporting/images/preserve-layout-switch.png b/docs/user/reporting/images/preserve-layout-switch.png index 9cfbdaafc3ac5..0aaefb14d7ee5 100644 Binary files a/docs/user/reporting/images/preserve-layout-switch.png and b/docs/user/reporting/images/preserve-layout-switch.png differ diff --git a/docs/user/reporting/images/share-button.png b/docs/user/reporting/images/share-button.png deleted file mode 100644 index 0b307d947935e..0000000000000 Binary files a/docs/user/reporting/images/share-button.png and /dev/null differ diff --git a/docs/user/reporting/images/share-menu.png b/docs/user/reporting/images/share-menu.png new file mode 100644 index 0000000000000..7f1d9eda0b5bc Binary files /dev/null and b/docs/user/reporting/images/share-menu.png differ diff --git a/docs/user/reporting/images/shareable-container.png b/docs/user/reporting/images/shareable-container.png index e114f63e2fe12..829fe15706a52 100644 Binary files a/docs/user/reporting/images/shareable-container.png and b/docs/user/reporting/images/shareable-container.png differ diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 50ae92382fb24..cd93389bb5fde 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -14,7 +14,7 @@ Reporting is available from the *Share* menu in *Discover*, *Dashboard*, and *Canvas*. [role="screenshot"] -image::user/reporting/images/share-button.png["Share"] +image::user/reporting/images/share-menu.png["Share"] [float] == Setup @@ -94,7 +94,7 @@ image::user/reporting/images/preserve-layout-switch.png["Share"] [[manage-report-history]] == View and manage report history -For a list of your reports, open the menu, then go to *Stack Management > Alerts and Insights > Reporting*. +For a list of your reports, open the main menu, then click *Stack Management > Reporting*. From this view, you can monitor the generation of a report and download reports that you previously generated. diff --git a/docs/user/reporting/network-policy.asciidoc b/docs/user/reporting/network-policy.asciidoc new file mode 100644 index 0000000000000..782473a3b0f18 --- /dev/null +++ b/docs/user/reporting/network-policy.asciidoc @@ -0,0 +1,71 @@ +[role="xpack"] +[[reporting-network-policy]] +=== Restrict requests with a Reporting network policy + +When Reporting generates PDF reports, it uses the Chromium browser to fully load the {kib} page on the server. This +potentially involves sending requests to external hosts. For example, a request might go to an external image server to show a +field formatted as an image, or to show an image in a Markdown visualization. + +If the Chromium browser is asked to send a request that violates the network policy, Reporting stops processing the page +before the request goes out, and the report is marked as a failure. Additional information about the event is in +the Kibana server logs. + +[NOTE] +============ +{kib} installations are not designed to be publicly accessible over the Internet. The Reporting network policy and other capabilities +of the Elastic Stack security features do not change this condition. +============ + +==== Configure a Reporting network policy + +You configure the network policy by specifying the `xpack.reporting.capture.networkPolicy.rules` setting in `kibana.yml`. A policy is specified as +an array of objects that describe what to allow or deny based on a host or protocol. If a host or protocol +is not specified, the rule matches any host or protocol. + +The rule objects are evaluated sequentially from the beginning to the end of the array, and continue until there is a matching rule. +If no rules allow a request, the request is denied. + +[source,yaml] +------------------------------------------------------- +# Only allow requests to placeholder.com +xpack.reporting.capture.networkPolicy: + rules: [ { allow: true, host: "placeholder.com" } ] +------------------------------------------------------- + +[source,yaml] +------------------------------------------------------- +# Only allow requests to https://placeholder.com +xpack.reporting.capture.networkPolicy: + rules: [ { allow: true, host: "placeholder.com", protocol: "https:" } ] +------------------------------------------------------- + +A final `allow` rule with no host or protocol will allow all requests that are not explicitly denied. + +[source,yaml] +------------------------------------------------------- +# Denies requests from http://placeholder.com, but anything else is allowed. +xpack.reporting.capture.networkPolicy: + rules: [{ allow: false, host: "placeholder.com", protocol: "http:" }, { allow: true }]; +------------------------------------------------------- + +A network policy can be composed of multiple rules. + +[source,yaml] +------------------------------------------------------- +# Allow any request to http://placeholder.com but for any other host, https is required +xpack.reporting.capture.networkPolicy + rules: [ + { allow: true, host: "placeholder.com", protocol: "http:" }, + { allow: true, protocol: "https:" }, + ] +------------------------------------------------------- + +[NOTE] +============ +The `file:` protocol is always denied, even if no network policy is configured. +============ + +==== Disable a Reporting network policy + +You can use the `xpack.reporting.capture.networkPolicy.enabled: false` setting to disable the network policy feature. The default for +this configuration property is `true`, so it is not necessary to explicitly enable it. diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc index 7cf1b964082d9..8b59115859622 100644 --- a/docs/user/security/api-keys/index.asciidoc +++ b/docs/user/security/api-keys/index.asciidoc @@ -15,7 +15,7 @@ Or, you might create API keys to automate ingestion of new data from remote sources, without a live user interaction. You can create API keys from the {kib} Console. To view and invalidate -API keys, open the menu, then go to *Stack Management > Security > API Keys*. +API keys, open the main menu, then click *Stack Management > API Keys*. [role="screenshot"] image:user/security/api-keys/images/api-keys.png["API Keys UI"] @@ -39,8 +39,8 @@ or contact your system administrator. === Security privileges You must have the `manage_security`, `manage_api_key`, or the `manage_own_api_key` -cluster privileges to use API keys in {kib}. To manage roles, open the menu, then go to -*Stack Management > Security > Roles*, or use the <>. +cluster privileges to use API keys in {kib}. To manage roles, open the main menu, then click +*Stack Management > Roles*, or use the <>. [float] diff --git a/docs/user/security/authorization/index.asciidoc b/docs/user/security/authorization/index.asciidoc index 930153484a0b2..780927b2d5df7 100644 --- a/docs/user/security/authorization/index.asciidoc +++ b/docs/user/security/authorization/index.asciidoc @@ -13,7 +13,12 @@ you cannot use `kibana_admin` to grant access. You must create custom roles that [[kibana-role-management]] === {kib} role management -To create a role that grants {kib} privileges, open the menu, go to *Stack Management > Security > Roles* and click **Create role**. +To create a role that grants {kib} privileges, open the menu, then click *Stack Management > Roles* and click **Create role**. + +[float] +==== Required permissions + +The `manage_security` cluster privilege is required to access role management. [[adding_kibana_privileges]] ==== Adding {kib} privileges diff --git a/docs/user/security/images/add-space-privileges.png b/docs/user/security/images/add-space-privileges.png old mode 100755 new mode 100644 index 7739332c33b60..d2fcbe76c1a06 Binary files a/docs/user/security/images/add-space-privileges.png and b/docs/user/security/images/add-space-privileges.png differ diff --git a/docs/user/security/images/assign_base_privilege.png b/docs/user/security/images/assign_base_privilege.png index 34e2bcf81d618..93bed0de05555 100644 Binary files a/docs/user/security/images/assign_base_privilege.png and b/docs/user/security/images/assign_base_privilege.png differ diff --git a/docs/user/security/images/assign_feature_privilege.png b/docs/user/security/images/assign_feature_privilege.png index c9449f6390253..d42ec208325a3 100644 Binary files a/docs/user/security/images/assign_feature_privilege.png and b/docs/user/security/images/assign_feature_privilege.png differ diff --git a/docs/user/security/images/privilege-example-1.png b/docs/user/security/images/privilege-example-1.png old mode 100755 new mode 100644 index 68ba716437240..b8fb4d15b8f77 Binary files a/docs/user/security/images/privilege-example-1.png and b/docs/user/security/images/privilege-example-1.png differ diff --git a/docs/user/security/images/role-space-visualization.png b/docs/user/security/images/role-space-visualization.png index 746af89c66e85..0de94d81065fe 100644 Binary files a/docs/user/security/images/role-space-visualization.png and b/docs/user/security/images/role-space-visualization.png differ diff --git a/docs/user/security/images/view-privilege-summary.png b/docs/user/security/images/view-privilege-summary.png old mode 100755 new mode 100644 index d93d55c93fd12..7d2f3018d7de9 Binary files a/docs/user/security/images/view-privilege-summary.png and b/docs/user/security/images/view-privilege-summary.png differ diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index e1a46a415fe68..18ace452ce00c 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -10,10 +10,16 @@ auditing. For more information, see {ref}/secure-cluster.html[Secure a cluster] and <>. +[float] +=== Required permissions + +The `manage_security` cluster privilege is required to access all Security features. + + [float] === Users -To create and manage users, open the menu, then go to *Stack Management > Security > Users*. +To create and manage users, open the main menu, then click *Stack Management > Users*. You can also change their passwords and roles. For more information about authentication and built-in users, see {ref}/setting-up-authentication.html[Setting up user authentication]. @@ -21,7 +27,7 @@ authentication and built-in users, see [float] === Roles -To manage roles, open the menu, then go to *Stack Management > Security > Roles*, or use +To manage roles, open the main menu, then click *Stack Management > Roles*, or use the <>. For more information on configuring roles for {kib}, see <>. For a more holistic overview of configuring roles for the entire stack, diff --git a/docs/user/security/rbac_tutorial.asciidoc b/docs/user/security/rbac_tutorial.asciidoc index cc4af9041bcd9..2088110f6de21 100644 --- a/docs/user/security/rbac_tutorial.asciidoc +++ b/docs/user/security/rbac_tutorial.asciidoc @@ -28,7 +28,7 @@ To complete this tutorial, you'll need the following: * **A space**: In this tutorial, use `Dev Mortgage` as the space name. See <> for details on creating a space. -* **Data**: You can use <> or +* **Data**: You can use <> or live data. In the following steps, Filebeat and Metricbeat data are used. [float] @@ -45,7 +45,7 @@ through in this tutorial: [float] ==== Create a role -Open the menu, then go to *Stack Management > Security > Roles* +Open the main menu, then click *Stack Management > Roles* for an overview of your roles. This view provides actions for you to create, edit, and delete roles. @@ -90,7 +90,7 @@ image::security/images/role-space-visualization.png["Associate space"] [float] ==== Create the developer user account with the proper roles -. Open the menu, then go to *Stack Management > Security > Users*. +. Open the main menu, then click *Stack Management > Users*. . Click **Create user**, then give the user the `dev-mortgage` and `monitoring-user` roles, which are required for *Stack Monitoring* users. diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index daf9720a0f1d8..6e7fc0c212f07 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -24,11 +24,11 @@ to report on and the {es} indices. [[reporting-roles-management-ui]] === If you are using the `native` realm -To assign roles, open the menu, then go to *Stack Management > Security > Roles*, use the <>. +To assign roles, use the *Roles* UI or <>. This example shows how to use *Roles* page to create a user who has a custom role and the `reporting_user` role. -. Open the menu, then go to *Stack Management > Security > Roles*. +. Open the main menu, then click *Stack Management > Roles*. . Click *Create role*, then give the role a name, for example, `custom_reporting_user`. @@ -51,7 +51,7 @@ that provides read and write privileges in . Save your new role. -. Open the menu, then go to *Stack Management > Security > Users*, add a new user, and assign the user the built-in +. Open the main menu, then click *Stack Management > Users*, add a new user, and assign the user the built-in `reporting_user` role and your new custom role, `custom_reporting_user`. [float] @@ -69,10 +69,10 @@ If you use a different pattern for the `xpack.reporting.index` setting, you must create a custom role with appropriate access to the index, similar to the following: -. Open the menu, then go to *Stack Management >Security > Roles*. +. Open the main menu, then click *Stack Management > Roles*. . Click *Create role*, then name the role `custom-reporting-user`. . Specify the custom index and assign it the `all` index privilege. -. Open the menu, then go to *Stack Management > Security > Users* and create a new user with +. Open the main menu, then click *Stack Management > Users* and create a new user with the `kibana_system` role and the `custom-reporting-user` role. . Configure {kib} to use the new account: [source,js] diff --git a/docs/user/security/role-mappings/index.asciidoc b/docs/user/security/role-mappings/index.asciidoc index 661c319af827f..ca3ca9a686892 100644 --- a/docs/user/security/role-mappings/index.asciidoc +++ b/docs/user/security/role-mappings/index.asciidoc @@ -9,7 +9,7 @@ or SAML. Role mappings have no effect for users inside the `native` or `file` realms. -To manage your role mappings, open the menu, then go to *Stack Management > Security > Role Mappings*. +To manage your role mappings, open the main menu, then click *Stack Management > Role Mappings*. With *Role mappings*, you can: @@ -19,11 +19,16 @@ With *Role mappings*, you can: [role="screenshot"] image:user/security/role-mappings/images/role-mappings-grid.png["Role mappings"] +[float] +==== Required permissions + +The `manage_security` cluster privilege is required to manage Role Mappings. + [float] === Create a role mapping -. Open the menu, then go to *Stack Management > Security > Role Mappings*. +. Open the main menu, then click *Stack Management > Role Mappings*. . Click *Create role mapping*. . Give your role mapping a unique name, and choose which roles you wish to assign to your users. + diff --git a/docs/user/security/secure-saved-objects.asciidoc b/docs/user/security/secure-saved-objects.asciidoc new file mode 100644 index 0000000000000..3b15a576500f1 --- /dev/null +++ b/docs/user/security/secure-saved-objects.asciidoc @@ -0,0 +1,47 @@ +[role="xpack"] +[[xpack-security-secure-saved-objects]] +=== Secure saved objects + +{kib} stores entities such as dashboards, visualizations, alerts, actions, and advanced settings as saved objects, which are kept in a dedicated, internal {es} index. If such an object includes sensitive information, for example a PagerDuty integration key or email server credentials used by the alert action, {kib} encrypts it and makes sure it cannot be accidentally leaked or tampered with. + +Encrypting sensitive information means that a malicious party with access to the {kib} internal indices won't be able to extract that information without also knowing the encryption key. + +Example `kibana.yml`: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.encryptedSavedObjects: + encryptionKey: "min-32-byte-long-strong-encryption-key" +-------------------------------------------------------------------------------- + +[IMPORTANT] +============================================================================ +If you don't specify an encryption key, {kib} automatically generates a random key at startup. Every time you restart {kib}, it uses a new ephemeral encryption key and is unable to decrypt saved objects encrypted with another key. To prevent data loss, {kib} might disable features that rely on this encryption until you explicitly set an encryption key. +============================================================================ + +[[encryption-key-rotation]] +==== Encryption key rotation + +Many policies and best practices stipulate that encryption keys should be periodically rotated to decrease the amount of content encrypted with one key and therefore limit the potential damage if the key is compromised. {kib} allows you to rotate encryption keys whenever there is a need. + +When you change an encryption key, be sure to keep the old one for some time. Although {kib} only uses a new encryption key to encrypt all new and updated data, it still may need the old one to decrypt data that was encrypted using the old key. It's possible to have multiple old keys used only for decryption. {kib} doesn't automatically re-encrypt existing saved objects with the new encryption key. Re-encryption only happens when you update existing object or use the <>. + +Here is how your `kibana.yml` might look if you use key rotation functionality: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.encryptedSavedObjects: + encryptionKey: "min-32-byte-long-NEW-encryption-key" <1> + keyRotation: + decryptionOnlyKeys: ["min-32-byte-long-OLD#1-encryption-key", "min-32-byte-long-OLD#2-encryption-key"] <2> +-------------------------------------------------------------------------------- + +<1> The encryption key {kib} will use to encrypt all new or updated saved objects. This is known as the primary encryption key. +<2> A list of encryption keys {kib} will try to use to decrypt existing saved objects if decryption with the primary encryption key isn't possible. These keys are known as the decryption-only or secondary encryption keys. + +[NOTE] +============================================================================ +You might also leverage this functionality if multiple {kib} instances connected to the same {es} cluster use different encryption keys. In this case, you might have a mix of saved objects encrypted with different keys, and every {kib} instance can only deal with a specific subset of objects. To fix this, you must choose a single primary encryption key for `xpack.encryptedSavedObjects.encryptionKey`, move all other encryption keys to `xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys`, and sync this configuration across all {kib} instances. +============================================================================ + +At some point, you might want to dispose of old encryption keys completely. Make sure there are no saved objects that {kib} encrypted with these encryption keys. You can use the <> to determine which existing saved objects require decryption-only keys and re-encrypt them with the primary key. diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index 0f02279eaf1f3..613ec88ed0edc 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -81,10 +81,10 @@ use {kib}. For more information on Basic Authentication and additional methods of authenticating {kib} users, see <>. -To manage privileges, open the menu, then go to *Stack Management > Security > Roles*. +To manage privileges, open the main menu, then click *Stack Management > Roles*. -If you're using the native realm with Basic Authentication, open then menu, -then go to *Stack Management > Security > Users* to assign roles, or use the +If you're using the native realm with Basic Authentication, open then main menu, +then click *Stack Management > Users* to assign roles, or use the {ref}/security-api.html#security-user-apis[user management APIs]. For example, the following creates a user named `jacknich` and assigns it the `kibana_admin` role: @@ -129,3 +129,4 @@ include::securing-communications/elasticsearch-mutual-tls.asciidoc[] include::audit-logging.asciidoc[] include::access-agreement.asciidoc[] include::session-management.asciidoc[] +include::secure-saved-objects.asciidoc[] diff --git a/docs/user/setup.asciidoc b/docs/user/setup.asciidoc index 31e7d157d1bc7..54bdfff8e0bbb 100644 --- a/docs/user/setup.asciidoc +++ b/docs/user/setup.asciidoc @@ -1,5 +1,5 @@ [[setup]] -= Set up Kibana += Set up [partintro] -- diff --git a/docs/visualize/images/timelion-copy-expression.png b/docs/visualize/images/timelion-copy-expression.png deleted file mode 100644 index 376bf7919166e..0000000000000 Binary files a/docs/visualize/images/timelion-copy-expression.png and /dev/null differ diff --git a/package.json b/package.json index 3b4c886fa2ca0..adddb8ea30a81 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ ], "private": true, "version": "7.10.0", - "branch": "7.x", + "branch": "7.10", "types": "./kibana.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", "build": { @@ -113,8 +113,8 @@ }, "dependencies": { "@elastic/datemath": "5.0.3", - "@elastic/elasticsearch": "7.9.1", - "@elastic/eui": "29.3.0", + "@elastic/elasticsearch": "7.10.0-rc.1", + "@elastic/eui": "29.3.1", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "^2.5.0", "@elastic/request-crypto": "1.1.4", @@ -128,10 +128,7 @@ "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/logging": "1.0.0", - "@kbn/pm": "1.0.0", "@kbn/std": "1.0.0", - "@kbn/telemetry-tools": "1.0.0", - "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", "@kbn/ace": "1.0.0", "@kbn/monaco": "1.0.0", @@ -243,8 +240,11 @@ "@kbn/expect": "1.0.0", "@kbn/optimizer": "1.0.0", "@kbn/plugin-generator": "1.0.0", + "@kbn/pm": "1.0.0", "@kbn/release-notes": "1.0.0", + "@kbn/telemetry-tools": "1.0.0", "@kbn/test": "1.0.0", + "@kbn/test-subj-selector": "0.2.1", "@kbn/utility-types": "1.0.0", "@microsoft/api-documenter": "7.7.2", "@microsoft/api-extractor": "7.7.0", @@ -360,7 +360,7 @@ "chai": "3.5.0", "chance": "1.0.18", "cheerio": "0.22.0", - "chromedriver": "^84.0.0", + "chromedriver": "^86.0.0", "classnames": "2.2.6", "compare-versions": "3.5.1", "d3": "3.5.17", diff --git a/packages/elastic-eslint-config-kibana/package.json b/packages/elastic-eslint-config-kibana/package.json index 3f2c6e9edb261..9d0d579086543 100644 --- a/packages/elastic-eslint-config-kibana/package.json +++ b/packages/elastic-eslint-config-kibana/package.json @@ -7,6 +7,9 @@ "type": "git", "url": "git+https://github.com/elastic/kibana.git" }, + "kibana": { + "devOnly": true + }, "keywords": [], "author": "Spencer Alger ", "license": "Apache-2.0", diff --git a/packages/kbn-babel-preset/package.json b/packages/kbn-babel-preset/package.json index 79d2fd8687dae..2fab970c5c71f 100644 --- a/packages/kbn-babel-preset/package.json +++ b/packages/kbn-babel-preset/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "private": true, "license": "Apache-2.0", + "kibana": { + "devOnly": true + }, "dependencies": { "@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-export-namespace-from": "^7.10.4", diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json index 6d2d56b929ead..f994836af8847 100644 --- a/packages/kbn-config/package.json +++ b/packages/kbn-config/package.json @@ -12,10 +12,8 @@ "dependencies": { "@elastic/safer-lodash-set": "0.0.0", "@kbn/config-schema": "1.0.0", - "@kbn/dev-utils": "1.0.0", "@kbn/logging": "1.0.0", "@kbn/std": "1.0.0", - "@kbn/utility-types": "1.0.0", "js-yaml": "^3.14.0", "load-json-file": "^6.2.0", "lodash": "^4.17.20", @@ -24,6 +22,8 @@ "type-detect": "^4.0.8" }, "devDependencies": { + "@kbn/dev-utils": "1.0.0", + "@kbn/utility-types": "1.0.0", "typescript": "4.0.2", "tsd": "^0.13.1" } diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index a51734168cf76..7fd9a9e7d67e1 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -9,6 +9,9 @@ "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" }, + "kibana": { + "devOnly": true + }, "dependencies": { "@babel/core": "^7.11.6", "@kbn/utils": "1.0.0", diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/README.md b/packages/kbn-dev-utils/src/ci_stats_reporter/README.md index c7b98224c4e57..12fc33dfaffb0 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/README.md +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/README.md @@ -8,10 +8,23 @@ This class integrates with the `ciStats.trackBuild {}` Jenkins Pipeline function To create an instance of the reporter, import the class and call `CiStatsReporter.fromEnv(log)` (passing it a tooling log). -#### `CiStatsReporter#metrics(metrics: Array<{ group: string, id: string, value: number }>)` +#### `CiStatsReporter#metrics(metrics: Metric[])` Use this method to record metrics in the Kibana CI Stats service. +```ts +interface Metric { + group: string, + id: string, + value: number, + // optional limit, values which exceed the limit will fail PRs + limit?: number + // optional path, relative to the root of the repo, where config values + // are defined. Will be linked to in PRs which have overages. + limitConfigPath?: string +} +``` + Example: ```ts diff --git a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts index b0378ab6c5cd5..a2f3b63daec50 100644 --- a/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts +++ b/packages/kbn-dev-utils/src/ci_stats_reporter/ci_stats_reporter.ts @@ -29,7 +29,13 @@ interface Config { buildId: string; } -export type CiStatsMetrics = Array<{ group: string; id: string; value: number }>; +export type CiStatsMetrics = Array<{ + group: string; + id: string; + value: number; + limit?: number; + limitConfigPath?: string; +}>; function parseConfig(log: ToolingLog) { const configJson = process.env.KIBANA_CI_STATS_CONFIG; diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json index 81c1747bb2727..645abd6195909 100644 --- a/packages/kbn-es-archiver/package.json +++ b/packages/kbn-es-archiver/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "license": "Apache-2.0", "main": "target/index.js", + "kibana": { + "devOnly": true + }, "scripts": { "kbn:bootstrap": "rm -rf target && tsc", "kbn:watch": "rm -rf target && tsc --watch" diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index c3733094350be..0f9b917e7f05a 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -4,12 +4,15 @@ "version": "1.0.0", "license": "Apache-2.0", "private": true, + "kibana": { + "devOnly": true + }, "scripts": { "kbn:bootstrap": "node scripts/build", "kbn:watch": "node scripts/build --watch" }, "dependencies": { - "@elastic/elasticsearch": "7.9.1", + "@elastic/elasticsearch": "7.10.0-rc.1", "@kbn/dev-utils": "1.0.0", "abort-controller": "^3.0.0", "chalk": "^4.1.0", diff --git a/packages/kbn-eslint-import-resolver-kibana/package.json b/packages/kbn-eslint-import-resolver-kibana/package.json index 223c73e97908e..ffbd94810a405 100755 --- a/packages/kbn-eslint-import-resolver-kibana/package.json +++ b/packages/kbn-eslint-import-resolver-kibana/package.json @@ -5,6 +5,9 @@ "version": "2.0.0", "main": "import_resolver_kibana.js", "license": "Apache-2.0", + "kibana": { + "devOnly": true + }, "repository": { "type": "git", "url": "https://github.com/elastic/kibana/tree/master/packages/kbn-eslint-import-resolver-kibana" diff --git a/packages/kbn-eslint-plugin-eslint/package.json b/packages/kbn-eslint-plugin-eslint/package.json index 026938213ac83..72b8577cb0945 100644 --- a/packages/kbn-eslint-plugin-eslint/package.json +++ b/packages/kbn-eslint-plugin-eslint/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "private": true, "license": "Apache-2.0", + "kibana": { + "devOnly": true + }, "peerDependencies": { "eslint": "6.8.0", "babel-eslint": "^10.0.3" diff --git a/packages/kbn-expect/package.json b/packages/kbn-expect/package.json index 0975f5762fa1c..8ca37c7c88673 100644 --- a/packages/kbn-expect/package.json +++ b/packages/kbn-expect/package.json @@ -3,5 +3,8 @@ "main": "./expect.js", "version": "1.0.0", "license": "MIT", - "private": true + "private": true, + "kibana": { + "devOnly": true + } } diff --git a/packages/kbn-optimizer/README.md b/packages/kbn-optimizer/README.md index a666907f02678..3fdf915e84c21 100644 --- a/packages/kbn-optimizer/README.md +++ b/packages/kbn-optimizer/README.md @@ -84,9 +84,9 @@ const config = OptimizerConfig.create({ dist: true }); -await runOptimizer(config) - .pipe(logOptimizerState(log, config)) - .toPromise(); +await lastValueFrom( + runOptimizer(config).pipe(logOptimizerState(log, config)) +); ``` This is essentially what we're doing in [`script/build_kibana_platform_plugins`][Cli] and the new [build system task][BuildTask]. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml new file mode 100644 index 0000000000000..4808eaf53f3d1 --- /dev/null +++ b/packages/kbn-optimizer/limits.yml @@ -0,0 +1,100 @@ +pageLoadAssetSize: + advancedSettings: 27596 + alerts: 106936 + apm: 64385 + apmOss: 18996 + beatsManagement: 188135 + bfetch: 41874 + canvas: 1065624 + charts: 159211 + cloud: 21076 + console: 46235 + core: 692646 + crossClusterReplication: 65408 + dashboard: 374267 + dashboardEnhanced: 65646 + dashboardMode: 22716 + data: 1174083 + dataEnhanced: 50420 + devTools: 38781 + discover: 105147 + discoverEnhanced: 42730 + embeddable: 242671 + embeddableEnhanced: 41145 + enterpriseSearch: 35741 + esUiShared: 326798 + expressions: 224120 + features: 31211 + fileUpload: 24717 + globalSearch: 43548 + globalSearchBar: 62888 + globalSearchProviders: 25554 + graph: 31504 + grokdebugger: 26779 + home: 41661 + indexLifecycleManagement: 107090 + indexManagement: 662506 + indexPatternManagement: 154366 + infra: 197873 + ingestManager: 415829 + ingestPipelines: 58003 + inputControlVis: 172819 + inspector: 148999 + kibanaLegacy: 107855 + kibanaOverview: 56426 + kibanaReact: 162353 + kibanaUtils: 198829 + lens: 96624 + licenseManagement: 41961 + licensing: 39008 + lists: 183665 + logstash: 53548 + management: 46112 + maps: 183754 + mapsLegacy: 116961 + mapsLegacyLicensing: 20214 + ml: 82187 + monitoring: 268758 + navigation: 37413 + newsfeed: 42228 + observability: 89709 + painlessLab: 179892 + regionMap: 66098 + remoteClusters: 51327 + reporting: 183418 + rollup: 97204 + savedObjects: 108662 + savedObjectsManagement: 100503 + searchprofiler: 67224 + security: 189428 + securityOss: 30806 + securitySolution: 622387 + share: 99205 + snapshotRestore: 79176 + spaces: 389643 + telemetry: 91832 + telemetryManagementSection: 52443 + tileMap: 65337 + timelion: 29920 + transform: 41151 + triggersActionsUi: 170145 + uiActions: 95074 + uiActionsEnhanced: 349799 + upgradeAssistant: 80967 + uptime: 40825 + urlDrilldown: 34174 + urlForwarding: 32579 + usageCollection: 39762 + visDefaultEditor: 50178 + visTypeMarkdown: 30896 + visTypeMetric: 42790 + visTypeTable: 95078 + visTypeTagcloud: 37575 + visTypeTimelion: 51933 + visTypeTimeseries: 155347 + visTypeVega: 153861 + visTypeVislib: 242982 + visTypeXy: 20255 + visualizations: 295169 + visualize: 57433 + watcher: 43742 diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 52f9349aec696..c9e414dbc5177 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -14,6 +14,7 @@ "@babel/core": "^7.11.6", "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", + "@kbn/std": "1.0.0", "@kbn/ui-shared-deps": "1.0.0", "autoprefixer": "^9.7.4", "babel-loader": "^8.0.6", @@ -22,6 +23,7 @@ "cpy": "^8.0.0", "core-js": "^3.6.5", "css-loader": "^3.4.2", + "dedent": "^0.7.0", "del": "^5.1.0", "execa": "^4.0.2", "file-loader": "^4.2.0", @@ -38,6 +40,7 @@ "postcss-loader": "^3.0.0", "raw-loader": "^3.1.0", "rxjs": "^6.5.5", + "js-yaml": "^3.14.0", "sass-loader": "^8.0.2", "source-map-support": "^0.5.19", "style-loader": "^1.1.3", diff --git a/packages/kbn-optimizer/src/cli.ts b/packages/kbn-optimizer/src/cli.ts index dcfb56be66efd..a822773052cae 100644 --- a/packages/kbn-optimizer/src/cli.ts +++ b/packages/kbn-optimizer/src/cli.ts @@ -22,12 +22,14 @@ import 'source-map-support/register'; import Path from 'path'; import { REPO_ROOT } from '@kbn/utils'; +import { lastValueFrom } from '@kbn/std'; import { run, createFlagError, CiStatsReporter } from '@kbn/dev-utils'; import { logOptimizerState } from './log_optimizer_state'; import { OptimizerConfig } from './optimizer'; import { reportOptimizerStats } from './report_optimizer_stats'; import { runOptimizer } from './run_optimizer'; +import { validateLimitsForAllBundles, updateBundleLimits } from './limits'; run( async ({ log, flags }) => { @@ -93,14 +95,24 @@ run( throw createFlagError('expected --filter to be one or more strings'); } + const validateLimits = flags['validate-limits'] ?? false; + if (typeof validateLimits !== 'boolean') { + throw createFlagError('expected --validate-limits to have no value'); + } + + const updateLimits = flags['update-limits'] ?? false; + if (typeof updateLimits !== 'boolean') { + throw createFlagError('expected --update-limits to have no value'); + } + const config = OptimizerConfig.create({ repoRoot: REPO_ROOT, watch, maxWorkerCount, - oss, - dist, + oss: oss && !(validateLimits || updateLimits), + dist: dist || updateLimits, cache, - examples, + examples: examples && !(validateLimits || updateLimits), profileWebpack, extraPluginScanDirs, inspectWorkers, @@ -108,6 +120,11 @@ run( filter, }); + if (validateLimits) { + validateLimitsForAllBundles(log, config); + return; + } + let update$ = runOptimizer(config); if (reportStats) { @@ -120,7 +137,11 @@ run( update$ = update$.pipe(reportOptimizerStats(reporter, config, log)); } - await update$.pipe(logOptimizerState(log, config)).toPromise(); + await lastValueFrom(update$.pipe(logOptimizerState(log, config))); + + if (updateLimits) { + updateBundleLimits(log, config); + } }, { flags: { @@ -134,6 +155,8 @@ run( 'profile', 'inspect-workers', 'report-stats', + 'validate-limits', + 'update-limits', ], string: ['workers', 'scan-dir', 'filter'], default: { @@ -152,10 +175,12 @@ run( --no-cache disable the cache --filter comma-separated list of bundle id filters, results from multiple flags are merged, * and ! are supported --no-examples don't build the example plugins - --dist create bundles that are suitable for inclusion in the Kibana distributable + --dist create bundles that are suitable for inclusion in the Kibana distributable, enabled when running with --update-limits --scan-dir add a directory to the list of directories scanned for plugins (specify as many times as necessary) --no-inspect-workers when inspecting the parent process, don't inspect the workers --report-stats attempt to report stats about this execution of the build to the kibana-ci-stats service using this name + --validate-limits validate the limits.yml config to ensure that there are limits defined for every bundle + --update-limits run a build and rewrite the limits file to include the current bundle sizes +5kb `, }, } diff --git a/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts b/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts index 7458fa13eccb3..8f5a01a4e4a5d 100644 --- a/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/event_stream_helpers.test.ts @@ -18,20 +18,21 @@ */ import * as Rx from 'rxjs'; -import { toArray, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; +import { allValuesFrom } from './rxjs_helpers'; import { summarizeEventStream } from './event_stream_helpers'; it('emits each state with each event, ignoring events when summarizer returns undefined', async () => { const event$ = Rx.of(1, 2, 3, 4, 5); const initial = 0; - const values = await summarizeEventStream(event$, initial, (state, event) => { - if (event % 2) { - return state + event; - } - }) - .pipe(toArray()) - .toPromise(); + const values = await allValuesFrom( + summarizeEventStream(event$, initial, (state, event) => { + if (event % 2) { + return state + event; + } + }) + ); expect(values).toMatchInlineSnapshot(` Array [ @@ -57,15 +58,15 @@ it('emits each state with each event, ignoring events when summarizer returns un it('interleaves injected events when source is synchronous', async () => { const event$ = Rx.of(1, 7); const initial = 0; - const values = await summarizeEventStream(event$, initial, (state, event, injectEvent) => { - if (event < 5) { - injectEvent(event + 2); - } + const values = await allValuesFrom( + summarizeEventStream(event$, initial, (state, event, injectEvent) => { + if (event < 5) { + injectEvent(event + 2); + } - return state + event; - }) - .pipe(toArray()) - .toPromise(); + return state + event; + }) + ); expect(values).toMatchInlineSnapshot(` Array [ @@ -95,15 +96,15 @@ it('interleaves injected events when source is synchronous', async () => { it('interleaves injected events when source is asynchronous', async () => { const event$ = Rx.of(1, 7, Rx.asyncScheduler); const initial = 0; - const values = await summarizeEventStream(event$, initial, (state, event, injectEvent) => { - if (event < 5) { - injectEvent(event + 2); - } + const values = await allValuesFrom( + summarizeEventStream(event$, initial, (state, event, injectEvent) => { + if (event < 5) { + injectEvent(event + 2); + } - return state + event; - }) - .pipe(toArray()) - .toPromise(); + return state + event; + }) + ); expect(values).toMatchInlineSnapshot(` Array [ @@ -133,17 +134,17 @@ it('interleaves injected events when source is asynchronous', async () => { it('interleaves mulitple injected events in order', async () => { const event$ = Rx.of(1); const initial = 0; - const values = await summarizeEventStream(event$, initial, (state, event, injectEvent) => { - if (event < 10) { - injectEvent(10); - injectEvent(20); - injectEvent(30); - } - - return state + event; - }) - .pipe(toArray()) - .toPromise(); + const values = await allValuesFrom( + summarizeEventStream(event$, initial, (state, event, injectEvent) => { + if (event < 10) { + injectEvent(10); + injectEvent(20); + injectEvent(30); + } + + return state + event; + }) + ); expect(values).toMatchInlineSnapshot(` Array [ @@ -179,9 +180,9 @@ it('stops an infinite stream when unsubscribed', async () => { return prev + event; }); - const values = await summarizeEventStream(event$, initial, summarize) - .pipe(take(11), toArray()) - .toPromise(); + const values = await allValuesFrom( + summarizeEventStream(event$, initial, summarize).pipe(take(11)) + ); expect(values).toMatchInlineSnapshot(` Array [ diff --git a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts index dda66c999b8f1..457da9290bbd0 100644 --- a/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts +++ b/packages/kbn-optimizer/src/common/rxjs_helpers.test.ts @@ -19,6 +19,7 @@ import * as Rx from 'rxjs'; import { toArray, map } from 'rxjs/operators'; +import { lastValueFrom } from '@kbn/std'; import { pipeClosure, debounceTimeBuffer, maybeMap, maybe } from './rxjs_helpers'; @@ -36,21 +37,21 @@ describe('pipeClosure()', () => { toArray() ); - await expect(foo$.toPromise()).resolves.toMatchInlineSnapshot(` + await expect(lastValueFrom(foo$)).resolves.toMatchInlineSnapshot(` Array [ 1, 2, 3, ] `); - await expect(foo$.toPromise()).resolves.toMatchInlineSnapshot(` + await expect(lastValueFrom(foo$)).resolves.toMatchInlineSnapshot(` Array [ 2, 4, 6, ] `); - await expect(foo$.toPromise()).resolves.toMatchInlineSnapshot(` + await expect(lastValueFrom(foo$)).resolves.toMatchInlineSnapshot(` Array [ 3, 6, @@ -64,7 +65,7 @@ describe('maybe()', () => { it('filters out undefined values from the stream', async () => { const foo$ = Rx.of(1, undefined, 2, undefined, 3).pipe(maybe(), toArray()); - await expect(foo$.toPromise()).resolves.toEqual([1, 2, 3]); + await expect(lastValueFrom(foo$)).resolves.toEqual([1, 2, 3]); }); }); @@ -75,7 +76,7 @@ describe('maybeMap()', () => { toArray() ); - await expect(foo$.toPromise()).resolves.toEqual([1, 3, 5]); + await expect(lastValueFrom(foo$)).resolves.toEqual([1, 3, 5]); }); }); diff --git a/packages/kbn-optimizer/src/common/rxjs_helpers.ts b/packages/kbn-optimizer/src/common/rxjs_helpers.ts index c6385c22518aa..49bf2d8f145dd 100644 --- a/packages/kbn-optimizer/src/common/rxjs_helpers.ts +++ b/packages/kbn-optimizer/src/common/rxjs_helpers.ts @@ -18,7 +18,8 @@ */ import * as Rx from 'rxjs'; -import { mergeMap, tap, debounceTime, map } from 'rxjs/operators'; +import { mergeMap, tap, debounceTime, map, toArray } from 'rxjs/operators'; +import { firstValueFrom } from '@kbn/std'; type Operator = (source: Rx.Observable) => Rx.Observable; type MapFn = (item: T1, index: number) => T2; @@ -73,3 +74,6 @@ export const debounceTimeBuffer = (ms: number) => }) ); }); + +export const allValuesFrom = (observable: Rx.Observable) => + firstValueFrom(observable.pipe(toArray())); diff --git a/packages/kbn-optimizer/src/index.ts b/packages/kbn-optimizer/src/index.ts index 549e4b13a4ac0..c522ff770d369 100644 --- a/packages/kbn-optimizer/src/index.ts +++ b/packages/kbn-optimizer/src/index.ts @@ -22,3 +22,4 @@ export * from './run_optimizer'; export * from './log_optimizer_state'; export * from './report_optimizer_stats'; export * from './node'; +export * from './limits'; diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index ca6035eb3de38..c7ae8498b17be 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -57,6 +57,7 @@ OptimizerConfig { "cache": true, "dist": false, "inspectWorkers": false, + "limits": "", "maxWorkerCount": 1, "plugins": Array [ Object { diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts index de3838eb92975..a89f84e5c543d 100644 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts @@ -24,10 +24,18 @@ import { inspect } from 'util'; import cpy from 'cpy'; import del from 'del'; -import { toArray, tap, filter } from 'rxjs/operators'; +import { tap, filter } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/utils'; import { ToolingLog } from '@kbn/dev-utils'; -import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '@kbn/optimizer'; +import { + runOptimizer, + OptimizerConfig, + OptimizerUpdate, + logOptimizerState, + readLimits, +} from '@kbn/optimizer'; + +import { allValuesFrom } from '../common'; const TMP_DIR = Path.resolve(__dirname, '../__fixtures__/__tmp__'); const MOCK_REPO_SRC = Path.resolve(__dirname, '../__fixtures__/mock_repo'); @@ -72,15 +80,17 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { dist: false, }); + expect(config.limits).toEqual(readLimits()); + (config as any).limits = ''; + expect(config).toMatchSnapshot('OptimizerConfig'); - const msgs = await runOptimizer(config) - .pipe( + const msgs = await allValuesFrom( + runOptimizer(config).pipe( logOptimizerState(log, config), - filter((x) => x.event?.type !== 'worker stdio'), - toArray() + filter((x) => x.event?.type !== 'worker stdio') ) - .toPromise(); + ); const assert = (statement: string, truth: boolean, altStates?: OptimizerUpdate[]) => { if (!truth) { @@ -199,17 +209,16 @@ it('uses cache on second run and exist cleanly', async () => { dist: false, }); - const msgs = await runOptimizer(config) - .pipe( + const msgs = await allValuesFrom( + runOptimizer(config).pipe( tap((state) => { if (state.event?.type === 'worker stdio') { // eslint-disable-next-line no-console console.log('worker', state.event.stream, state.event.line); } - }), - toArray() + }) ) - .toPromise(); + ); expect(msgs.map((m) => m.state.phase)).toMatchInlineSnapshot(` Array [ @@ -231,7 +240,7 @@ it('prepares assets for distribution', async () => { dist: true, }); - await runOptimizer(config).pipe(logOptimizerState(log, config), toArray()).toPromise(); + await allValuesFrom(runOptimizer(config).pipe(logOptimizerState(log, config))); expectFileMatchesSnapshotWithCompression('plugins/foo/target/public/foo.plugin.js', 'foo bundle'); expectFileMatchesSnapshotWithCompression( diff --git a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts index 48cab508954a0..00e6782128dd9 100644 --- a/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/bundle_cache.test.ts @@ -21,12 +21,11 @@ import Path from 'path'; import cpy from 'cpy'; import del from 'del'; -import { toArray } from 'rxjs/operators'; import { createAbsolutePathSerializer } from '@kbn/dev-utils'; import { getMtimes } from '../optimizer/get_mtimes'; import { OptimizerConfig } from '../optimizer/optimizer_config'; -import { Bundle } from '../common/bundle'; +import { allValuesFrom, Bundle } from '../common'; import { getBundleCacheEvent$ } from '../optimizer/bundle_cache'; const TMP_DIR = Path.resolve(__dirname, '../__fixtures__/__tmp__'); @@ -78,9 +77,7 @@ it('emits "bundle cached" event when everything is updated', async () => { bundleRefExportIds: [], }); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ @@ -119,9 +116,7 @@ it('emits "bundle not cached" event when cacheKey is up to date but caching is d bundleRefExportIds: [], }); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ @@ -160,9 +155,7 @@ it('emits "bundle not cached" event when optimizerCacheKey is missing', async () bundleRefExportIds: [], }); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ @@ -201,9 +194,7 @@ it('emits "bundle not cached" event when optimizerCacheKey is outdated, includes bundleRefExportIds: [], }); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ @@ -247,9 +238,7 @@ it('emits "bundle not cached" event when bundleRefExportIds is outdated, include bundleRefExportIds: ['plugin/bar/public'], }); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ @@ -292,9 +281,7 @@ it('emits "bundle not cached" event when cacheKey is missing', async () => { bundleRefExportIds: [], }); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ @@ -333,9 +320,7 @@ it('emits "bundle not cached" event when cacheKey is outdated', async () => { jest.spyOn(bundle, 'createCacheKey').mockImplementation(() => 'new'); - const cacheEvents = await getBundleCacheEvent$(config, optimizerCacheKey) - .pipe(toArray()) - .toPromise(); + const cacheEvents = await allValuesFrom(getBundleCacheEvent$(config, optimizerCacheKey)); expect(cacheEvents).toMatchInlineSnapshot(` Array [ diff --git a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts index 176b17c979da9..00f3c780adc0a 100644 --- a/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/watch_bundles_for_changes.test.ts @@ -20,6 +20,7 @@ import * as Rx from 'rxjs'; import { map } from 'rxjs/operators'; import ActualWatchpack from 'watchpack'; +import { lastValueFrom } from '@kbn/std'; import { Bundle, ascending } from '../common'; import { watchBundlesForChanges$ } from '../optimizer/watch_bundles_for_changes'; @@ -78,8 +79,8 @@ afterEach(async () => { it('notifies of changes and completes once all bundles have changed', async () => { expect.assertions(18); - const promise = watchBundlesForChanges$(bundleCacheEvent$, Date.now()) - .pipe( + const promise = lastValueFrom( + watchBundlesForChanges$(bundleCacheEvent$, Date.now()).pipe( map((event, i) => { // each time we trigger a change event we get a 'changed detected' event if (i === 0 || i === 2 || i === 4 || i === 6) { @@ -116,7 +117,7 @@ it('notifies of changes and completes once all bundles have changed', async () = } }) ) - .toPromise(); + ); expect(MockWatchPack.mock.instances).toHaveLength(1); const [watcher] = (MockWatchPack.mock.instances as any) as Array>; diff --git a/packages/kbn-optimizer/src/limits.ts b/packages/kbn-optimizer/src/limits.ts new file mode 100644 index 0000000000000..b0fae0901251d --- /dev/null +++ b/packages/kbn-optimizer/src/limits.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 Fs from 'fs'; + +import dedent from 'dedent'; +import Yaml from 'js-yaml'; +import { createFailError, ToolingLog } from '@kbn/dev-utils'; + +import { OptimizerConfig, getMetrics, Limits } from './optimizer'; + +const LIMITS_PATH = require.resolve('../limits.yml'); +const DEFAULT_BUDGET = 15000; + +const diff = (a: T[], b: T[]): T[] => a.filter((item) => !b.includes(item)); + +export function readLimits(): Limits { + let yaml; + try { + yaml = Fs.readFileSync(LIMITS_PATH, 'utf8'); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + + return yaml ? Yaml.safeLoad(yaml) : {}; +} + +export function validateLimitsForAllBundles(log: ToolingLog, config: OptimizerConfig) { + const limitBundleIds = Object.keys(config.limits.pageLoadAssetSize || {}); + const configBundleIds = config.bundles.map((b) => b.id); + + const missingBundleIds = diff(configBundleIds, limitBundleIds); + const extraBundleIds = diff(limitBundleIds, configBundleIds); + + const issues = []; + if (missingBundleIds.length) { + issues.push(`missing: ${missingBundleIds.join(', ')}`); + } + if (extraBundleIds.length) { + issues.push(`extra: ${extraBundleIds.join(', ')}`); + } + if (issues.length) { + throw createFailError( + dedent` + The limits defined in packages/kbn-optimizer/limits.yml are outdated. Please update + this file with a limit (in bytes) for every production bundle. + + ${issues.join('\n ')} + + To automatically update the limits file locally run: + + node scripts/build_kibana_platform_plugins.js --update-limits + + To validate your changes locally run: + + node scripts/build_kibana_platform_plugins.js --validate-limits + ` + '\n' + ); + } + + log.success('limits.yml file valid'); +} + +export function updateBundleLimits(log: ToolingLog, config: OptimizerConfig) { + const metrics = getMetrics(log, config); + + const pageLoadAssetSize: NonNullable = {}; + + for (const metric of metrics.sort((a, b) => a.id.localeCompare(b.id))) { + if (metric.group === 'page load bundle size') { + const existingLimit = config.limits.pageLoadAssetSize?.[metric.id]; + pageLoadAssetSize[metric.id] = + existingLimit != null && existingLimit >= metric.value + ? existingLimit + : metric.value + DEFAULT_BUDGET; + } + } + + const newLimits: Limits = { + pageLoadAssetSize, + }; + + Fs.writeFileSync(LIMITS_PATH, Yaml.safeDump(newLimits)); + log.success(`wrote updated limits to ${LIMITS_PATH}`); +} diff --git a/packages/kbn-optimizer/src/node/cache.ts b/packages/kbn-optimizer/src/node/cache.ts index 7fbf009e38a7d..1ce3b9eeeafd0 100644 --- a/packages/kbn-optimizer/src/node/cache.ts +++ b/packages/kbn-optimizer/src/node/cache.ts @@ -64,6 +64,7 @@ export class Cache { this.codes = LmdbStore.open({ name: 'codes', path: CACHE_DIR, + maxReaders: 500, }); this.atimes = this.codes.openDB({ diff --git a/packages/kbn-optimizer/src/optimizer/get_mtimes.ts b/packages/kbn-optimizer/src/optimizer/get_mtimes.ts index 07777c323637a..b6c3678709880 100644 --- a/packages/kbn-optimizer/src/optimizer/get_mtimes.ts +++ b/packages/kbn-optimizer/src/optimizer/get_mtimes.ts @@ -20,7 +20,8 @@ import Fs from 'fs'; import * as Rx from 'rxjs'; -import { mergeMap, toArray, map, catchError } from 'rxjs/operators'; +import { mergeMap, map, catchError } from 'rxjs/operators'; +import { allValuesFrom } from '../common'; const stat$ = Rx.bindNodeCallback(Fs.stat); @@ -28,20 +29,22 @@ const stat$ = Rx.bindNodeCallback(Fs.stat); * get mtimes of referenced paths concurrently, limit concurrency to 100 */ export async function getMtimes(paths: Iterable) { - return await Rx.from(paths) - .pipe( - // map paths to [path, mtimeMs] entries with concurrency of - // 100 at a time, ignoring missing paths - mergeMap( - (path) => - stat$(path).pipe( - map((stat) => [path, stat.mtimeMs] as const), - catchError((error: any) => (error?.code === 'ENOENT' ? Rx.EMPTY : Rx.throwError(error))) - ), - 100 - ), - toArray(), - map((entries) => new Map(entries)) + return new Map( + await allValuesFrom( + Rx.from(paths).pipe( + // map paths to [path, mtimeMs] entries with concurrency of + // 100 at a time, ignoring missing paths + mergeMap( + (path) => + stat$(path).pipe( + map((stat) => [path, stat.mtimeMs] as const), + catchError((error: any) => + error?.code === 'ENOENT' ? Rx.EMPTY : Rx.throwError(error) + ) + ), + 100 + ) + ) ) - .toPromise(); + ); } diff --git a/packages/kbn-optimizer/src/optimizer/get_output_stats.ts b/packages/kbn-optimizer/src/optimizer/get_output_stats.ts new file mode 100644 index 0000000000000..cc4cd05f42c3f --- /dev/null +++ b/packages/kbn-optimizer/src/optimizer/get_output_stats.ts @@ -0,0 +1,124 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 Fs from 'fs'; +import Path from 'path'; + +import { ToolingLog, CiStatsMetrics } from '@kbn/dev-utils'; +import { OptimizerConfig } from './optimizer_config'; + +const flatten = (arr: Array): T[] => + arr.reduce((acc: T[], item) => acc.concat(item), []); + +interface Entry { + relPath: string; + stats: Fs.Stats; +} + +const IGNORED_EXTNAME = ['.map', '.br', '.gz']; + +const getFiles = (dir: string, parent?: string) => + flatten( + Fs.readdirSync(dir).map((name): Entry | Entry[] => { + const absPath = Path.join(dir, name); + const relPath = parent ? Path.join(parent, name) : name; + const stats = Fs.statSync(absPath); + + if (stats.isDirectory()) { + return getFiles(absPath, relPath); + } + + return { + relPath, + stats, + }; + }) + ).filter((file) => { + const filename = Path.basename(file.relPath); + if (filename.startsWith('.')) { + return false; + } + + const ext = Path.extname(filename); + if (IGNORED_EXTNAME.includes(ext)) { + return false; + } + + return true; + }); + +export function getMetrics(log: ToolingLog, config: OptimizerConfig) { + return flatten( + config.bundles.map((bundle) => { + // make the cache read from the cache file since it was likely updated by the worker + bundle.cache.refresh(); + + const outputFiles = getFiles(bundle.outputDir); + const entryName = `${bundle.id}.${bundle.type}.js`; + const entry = outputFiles.find((f) => f.relPath === entryName); + if (!entry) { + throw new Error( + `Unable to find bundle entry named [${entryName}] in [${bundle.outputDir}]` + ); + } + + const chunkPrefix = `${bundle.id}.chunk.`; + const asyncChunks = outputFiles.filter((f) => f.relPath.startsWith(chunkPrefix)); + const miscFiles = outputFiles.filter((f) => f !== entry && !asyncChunks.includes(f)); + + if (asyncChunks.length) { + log.verbose(bundle.id, 'async chunks', asyncChunks); + } + if (miscFiles.length) { + log.verbose(bundle.id, 'misc files', asyncChunks); + } + + const sumSize = (files: Entry[]) => files.reduce((acc: number, f) => acc + f.stats!.size, 0); + + const bundleMetrics: CiStatsMetrics = [ + { + group: `@kbn/optimizer bundle module count`, + id: bundle.id, + value: bundle.cache.getModuleCount() || 0, + }, + { + group: `page load bundle size`, + id: bundle.id, + value: entry.stats!.size, + limit: config.limits.pageLoadAssetSize?.[bundle.id], + limitConfigPath: `packages/kbn-optimizer/limits.yml`, + }, + { + group: `async chunks size`, + id: bundle.id, + value: sumSize(asyncChunks), + }, + { + group: `miscellaneous assets size`, + id: bundle.id, + value: sumSize(miscFiles), + }, + ]; + + log.debug(bundle.id, 'metrics', bundleMetrics); + + return bundleMetrics; + }) + ); +} diff --git a/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.test.ts b/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.test.ts index 6edcde56e26de..b92eee0a51fd5 100644 --- a/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.test.ts +++ b/packages/kbn-optimizer/src/optimizer/handle_optimizer_completion.test.ts @@ -20,12 +20,11 @@ import * as Rx from 'rxjs'; import { REPO_ROOT } from '@kbn/utils'; -import { Update } from '../common'; +import { Update, allValuesFrom } from '../common'; import { OptimizerState } from './optimizer_state'; import { OptimizerConfig } from './optimizer_config'; import { handleOptimizerCompletion } from './handle_optimizer_completion'; -import { toArray } from 'rxjs/operators'; const createUpdate$ = (phase: OptimizerState['phase']) => Rx.of>({ @@ -44,13 +43,12 @@ const config = (watch?: boolean) => repoRoot: REPO_ROOT, watch, }); -const collect = (stream: Rx.Observable): Promise => stream.pipe(toArray()).toPromise(); it('errors if the optimizer completes when in watch mode', async () => { const update$ = createUpdate$('success'); await expect( - collect(update$.pipe(handleOptimizerCompletion(config(true)))) + allValuesFrom(update$.pipe(handleOptimizerCompletion(config(true)))) ).rejects.toThrowErrorMatchingInlineSnapshot( `"optimizer unexpectedly completed when in watch mode"` ); @@ -60,7 +58,7 @@ it('errors if the optimizer completes in phase "issue"', async () => { const update$ = createUpdate$('issue'); await expect( - collect(update$.pipe(handleOptimizerCompletion(config()))) + allValuesFrom(update$.pipe(handleOptimizerCompletion(config()))) ).rejects.toThrowErrorMatchingInlineSnapshot(`"webpack issue"`); }); @@ -68,7 +66,7 @@ it('errors if the optimizer completes in phase "initializing"', async () => { const update$ = createUpdate$('initializing'); await expect( - collect(update$.pipe(handleOptimizerCompletion(config()))) + allValuesFrom(update$.pipe(handleOptimizerCompletion(config()))) ).rejects.toThrowErrorMatchingInlineSnapshot( `"optimizer unexpectedly exit in phase \\"initializing\\""` ); @@ -78,7 +76,7 @@ it('errors if the optimizer completes in phase "reallocating"', async () => { const update$ = createUpdate$('reallocating'); await expect( - collect(update$.pipe(handleOptimizerCompletion(config()))) + allValuesFrom(update$.pipe(handleOptimizerCompletion(config()))) ).rejects.toThrowErrorMatchingInlineSnapshot( `"optimizer unexpectedly exit in phase \\"reallocating\\""` ); @@ -88,7 +86,7 @@ it('errors if the optimizer completes in phase "running"', async () => { const update$ = createUpdate$('running'); await expect( - collect(update$.pipe(handleOptimizerCompletion(config()))) + allValuesFrom(update$.pipe(handleOptimizerCompletion(config()))) ).rejects.toThrowErrorMatchingInlineSnapshot( `"optimizer unexpectedly exit in phase \\"running\\""` ); @@ -98,7 +96,7 @@ it('passes through errors on the source stream', async () => { const error = new Error('foo'); const update$ = Rx.throwError(error); - await expect(collect(update$.pipe(handleOptimizerCompletion(config())))).rejects.toThrowError( - error - ); + await expect( + allValuesFrom(update$.pipe(handleOptimizerCompletion(config()))) + ).rejects.toThrowError(error); }); diff --git a/packages/kbn-optimizer/src/optimizer/index.ts b/packages/kbn-optimizer/src/optimizer/index.ts index 84fd395e98976..77df112b44351 100644 --- a/packages/kbn-optimizer/src/optimizer/index.ts +++ b/packages/kbn-optimizer/src/optimizer/index.ts @@ -25,3 +25,4 @@ export * from './watch_bundles_for_changes'; export * from './run_workers'; export * from './bundle_cache'; export * from './handle_optimizer_completion'; +export * from './get_output_stats'; diff --git a/packages/kbn-optimizer/src/optimizer/observe_stdio.test.ts b/packages/kbn-optimizer/src/optimizer/observe_stdio.test.ts index 9bf8f9db1fe45..a7c07358fa6d6 100644 --- a/packages/kbn-optimizer/src/optimizer/observe_stdio.test.ts +++ b/packages/kbn-optimizer/src/optimizer/observe_stdio.test.ts @@ -19,7 +19,7 @@ import { Readable } from 'stream'; -import { toArray } from 'rxjs/operators'; +import { allValuesFrom } from '../common'; import { observeStdio$ } from './observe_stdio'; @@ -27,18 +27,18 @@ it('notifies on every line, uncluding partial content at the end without a newli const chunks = [`foo\nba`, `r\nb`, `az`]; await expect( - observeStdio$( - new Readable({ - read() { - this.push(chunks.shift()!); - if (!chunks.length) { - this.push(null); - } - }, - }) + allValuesFrom( + observeStdio$( + new Readable({ + read() { + this.push(chunks.shift()!); + if (!chunks.length) { + this.push(null); + } + }, + }) + ) ) - .pipe(toArray()) - .toPromise() ).resolves.toMatchInlineSnapshot(` Array [ "foo", diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts index fd887e8c2c012..948ba520931e5 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.test.ts @@ -22,6 +22,7 @@ jest.mock('./kibana_platform_plugins.ts'); jest.mock('./get_plugin_bundles.ts'); jest.mock('../common/theme_tags.ts'); jest.mock('./filter_by_id.ts'); +jest.mock('../limits.ts'); jest.mock('os', () => { const realOs = jest.requireActual('os'); @@ -385,6 +386,7 @@ describe('OptimizerConfig::create()', () => { .findKibanaPlatformPlugins; const getPluginBundles: jest.Mock = jest.requireMock('./get_plugin_bundles.ts').getPluginBundles; const filterById: jest.Mock = jest.requireMock('./filter_by_id.ts').filterById; + const readLimits: jest.Mock = jest.requireMock('../limits.ts').readLimits; beforeEach(() => { if ('mock' in OptimizerConfig.parseOptions) { @@ -398,6 +400,7 @@ describe('OptimizerConfig::create()', () => { findKibanaPlatformPlugins.mockReturnValue(Symbol('new platform plugins')); getPluginBundles.mockReturnValue([Symbol('bundle1'), Symbol('bundle2')]); filterById.mockReturnValue(Symbol('filtered bundles')); + readLimits.mockReturnValue(Symbol('limits')); jest.spyOn(OptimizerConfig, 'parseOptions').mockImplementation((): { [key in keyof ParsedOptions]: any; @@ -429,6 +432,7 @@ describe('OptimizerConfig::create()', () => { "cache": Symbol(parsed cache), "dist": Symbol(parsed dist), "inspectWorkers": Symbol(parsed inspect workers), + "limits": Symbol(limits), "maxWorkerCount": Symbol(parsed max worker count), "plugins": Symbol(new platform plugins), "profileWebpack": Symbol(parsed profile webpack), diff --git a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts index b1ab1ebfe49f2..b685d6ea01591 100644 --- a/packages/kbn-optimizer/src/optimizer/optimizer_config.ts +++ b/packages/kbn-optimizer/src/optimizer/optimizer_config.ts @@ -32,6 +32,13 @@ import { import { findKibanaPlatformPlugins, KibanaPlatformPlugin } from './kibana_platform_plugins'; import { getPluginBundles } from './get_plugin_bundles'; import { filterById } from './filter_by_id'; +import { readLimits } from '../limits'; + +export interface Limits { + pageLoadAssetSize?: { + [id: string]: number | undefined; + }; +} function pickMaxWorkerCount(dist: boolean) { // don't break if cpus() returns nothing, or an empty array @@ -238,7 +245,8 @@ export class OptimizerConfig { options.maxWorkerCount, options.dist, options.profileWebpack, - options.themeTags + options.themeTags, + readLimits() ); } @@ -252,7 +260,8 @@ export class OptimizerConfig { public readonly maxWorkerCount: number, public readonly dist: boolean, public readonly profileWebpack: boolean, - public readonly themeTags: ThemeTags + public readonly themeTags: ThemeTags, + public readonly limits: Limits ) {} getWorkerConfig(optimizerCacheKey: unknown): WorkerConfig { diff --git a/packages/kbn-optimizer/src/report_optimizer_stats.ts b/packages/kbn-optimizer/src/report_optimizer_stats.ts index eff2bce0b827e..a0f59a3505e30 100644 --- a/packages/kbn-optimizer/src/report_optimizer_stats.ts +++ b/packages/kbn-optimizer/src/report_optimizer_stats.ts @@ -17,136 +17,41 @@ * under the License. */ -import Fs from 'fs'; -import Path from 'path'; - import { materialize, mergeMap, dematerialize } from 'rxjs/operators'; -import { CiStatsReporter, CiStatsMetrics, ToolingLog } from '@kbn/dev-utils'; +import { CiStatsReporter, ToolingLog } from '@kbn/dev-utils'; import { OptimizerUpdate$ } from './run_optimizer'; -import { OptimizerState, OptimizerConfig } from './optimizer'; +import { OptimizerConfig, getMetrics } from './optimizer'; import { pipeClosure } from './common'; -const flatten = (arr: Array): T[] => - arr.reduce((acc: T[], item) => acc.concat(item), []); - -interface Entry { - relPath: string; - stats: Fs.Stats; -} - -const IGNORED_EXTNAME = ['.map', '.br', '.gz']; - -const getFiles = (dir: string, parent?: string) => - flatten( - Fs.readdirSync(dir).map((name): Entry | Entry[] => { - const absPath = Path.join(dir, name); - const relPath = parent ? Path.join(parent, name) : name; - const stats = Fs.statSync(absPath); - - if (stats.isDirectory()) { - return getFiles(absPath, relPath); - } - - return { - relPath, - stats, - }; - }) - ).filter((file) => { - const filename = Path.basename(file.relPath); - if (filename.startsWith('.')) { - return false; - } - - const ext = Path.extname(filename); - if (IGNORED_EXTNAME.includes(ext)) { - return false; - } - - return true; - }); - export function reportOptimizerStats( reporter: CiStatsReporter, config: OptimizerConfig, log: ToolingLog ) { - return pipeClosure((update$: OptimizerUpdate$) => { - let lastState: OptimizerState | undefined; - return update$.pipe( + return pipeClosure((update$: OptimizerUpdate$) => + update$.pipe( materialize(), mergeMap(async (n) => { - if (n.kind === 'N' && n.value?.state) { - lastState = n.value?.state; - } - - if (n.kind === 'C' && lastState) { - await reporter.metrics( - flatten( - config.bundles.map((bundle) => { - // make the cache read from the cache file since it was likely updated by the worker - bundle.cache.refresh(); - - const outputFiles = getFiles(bundle.outputDir); - const entryName = `${bundle.id}.${bundle.type}.js`; - const entry = outputFiles.find((f) => f.relPath === entryName); - if (!entry) { - throw new Error( - `Unable to find bundle entry named [${entryName}] in [${bundle.outputDir}]` - ); - } - - const chunkPrefix = `${bundle.id}.chunk.`; - const asyncChunks = outputFiles.filter((f) => f.relPath.startsWith(chunkPrefix)); - const miscFiles = outputFiles.filter( - (f) => f !== entry && !asyncChunks.includes(f) - ); - - if (asyncChunks.length) { - log.verbose(bundle.id, 'async chunks', asyncChunks); - } - if (miscFiles.length) { - log.verbose(bundle.id, 'misc files', asyncChunks); - } - - const sumSize = (files: Entry[]) => - files.reduce((acc: number, f) => acc + f.stats!.size, 0); - - const metrics: CiStatsMetrics = [ - { - group: `@kbn/optimizer bundle module count`, - id: bundle.id, - value: bundle.cache.getModuleCount() || 0, - }, - { - group: `page load bundle size`, - id: bundle.id, - value: entry.stats!.size, - }, - { - group: `async chunks size`, - id: bundle.id, - value: sumSize(asyncChunks), - }, - { - group: `miscellaneous assets size`, - id: bundle.id, - value: sumSize(miscFiles), - }, - ]; - - log.info(bundle.id, 'metrics', metrics); - - return metrics; - }) - ) - ); + if (n.kind === 'C') { + const metrics = getMetrics(log, config); + + await reporter.metrics(metrics); + + for (const metric of metrics) { + if (metric.limit != null && metric.value > metric.limit) { + const value = metric.value.toLocaleString(); + const limit = metric.limit.toLocaleString(); + log.warning( + `Metric [${metric.group}] for [${metric.id}] of [${value}] over the limit of [${limit}]` + ); + } + } } return n; }), dematerialize() - ); - }); + ) + ); } diff --git a/packages/kbn-plugin-helpers/package.json b/packages/kbn-plugin-helpers/package.json index a2c4e1e2134e7..1f86122d7e129 100644 --- a/packages/kbn-plugin-helpers/package.json +++ b/packages/kbn-plugin-helpers/package.json @@ -4,6 +4,9 @@ "private": true, "description": "Just some helpers for kibana plugin devs.", "license": "Apache-2.0", + "kibana": { + "devOnly": true + }, "main": "target/index.js", "bin": { "plugin-helpers": "bin/plugin-helpers.js" diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 377d4daf55ea6..363b74d91541d 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -150,7 +150,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(501); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(502); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(144); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -8895,9 +8895,9 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(281); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(400); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(401); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(282); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(401); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(402); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8941,7 +8941,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(274); /* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(279); /* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(276); -/* harmony import */ var _utils_validate_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(280); +/* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(280); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -8998,7 +8998,7 @@ const BootstrapCommand = { } const yarnLock = await Object(_utils_yarn_lock__WEBPACK_IMPORTED_MODULE_6__["readYarnLock"])(kbn); - await Object(_utils_validate_yarn_lock__WEBPACK_IMPORTED_MODULE_7__["validateYarnLock"])(kbn, yarnLock); + await Object(_utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_7__["validateDependencies"])(kbn, yarnLock); await Object(_utils_link_project_executables__WEBPACK_IMPORTED_MODULE_0__["linkProjectExecutables"])(projects, projectGraph); /** * At the end of the bootstrapping process we call all `kbn:bootstrap` scripts @@ -14707,6 +14707,10 @@ class Project { return this.json.kibana && this.json.kibana.clean || {}; } + isFlaggedAsDevOnly() { + return !!(this.json.kibana && this.json.kibana.devOnly); + } + hasScript(name) { return name in this.scripts; } @@ -38023,13 +38027,16 @@ class BootstrapCacheFile { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validateYarnLock", function() { return validateYarnLock; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "validateDependencies", function() { return validateDependencies; }); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(277); /* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(131); -/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(144); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(113); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(131); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(144); +/* harmony import */ var _projects_tree__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(281); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -38053,7 +38060,9 @@ __webpack_require__.r(__webpack_exports__); -async function validateYarnLock(kbn, yarnLock) { + + +async function validateDependencies(kbn, yarnLock) { // look through all of the packages in the yarn.lock file to see if // we have accidentally installed multiple lodash v4 versions const lodash4Versions = new Set(); @@ -38074,8 +38083,8 @@ async function validateYarnLock(kbn, yarnLock) { delete yarnLock[req]; } - await Object(_fs__WEBPACK_IMPORTED_MODULE_2__["writeFile"])(kbn.getAbsolute('yarn.lock'), Object(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__["stringify"])(yarnLock), 'utf8'); - _log__WEBPACK_IMPORTED_MODULE_3__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` + await Object(_fs__WEBPACK_IMPORTED_MODULE_3__["writeFile"])(kbn.getAbsolute('yarn.lock'), Object(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__["stringify"])(yarnLock), 'utf8'); + _log__WEBPACK_IMPORTED_MODULE_4__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` Multiple version of lodash v4 were detected, so they have been removed from the yarn.lock file. Please rerun yarn kbn bootstrap to coalese the @@ -38094,7 +38103,7 @@ async function validateYarnLock(kbn, yarnLock) { // of lodash v3 in the distributable - const prodDependencies = kbn.resolveAllProductionDependencies(yarnLock, _log__WEBPACK_IMPORTED_MODULE_3__["log"]); + const prodDependencies = kbn.resolveAllProductionDependencies(yarnLock, _log__WEBPACK_IMPORTED_MODULE_4__["log"]); const lodash3Versions = new Set(); for (const dep of prodDependencies.values()) { @@ -38105,7 +38114,7 @@ async function validateYarnLock(kbn, yarnLock) { if (lodash3Versions.size) { - _log__WEBPACK_IMPORTED_MODULE_3__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` + _log__WEBPACK_IMPORTED_MODULE_4__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` Due to changes in the yarn.lock file and/or package.json files a version of lodash 3 is now included in the production dependencies. To reduce the size of @@ -38157,7 +38166,7 @@ async function validateYarnLock(kbn, yarnLock) { }) => ` ${range} => ${projects.map(p => p.name).join(', ')}`)], []).join('\n '); if (duplicateRanges) { - _log__WEBPACK_IMPORTED_MODULE_3__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` + _log__WEBPACK_IMPORTED_MODULE_4__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` [single_version_dependencies] Multiple version ranges for the same dependency were found declared across different package.json files. Please consolidate @@ -38171,21 +38180,207 @@ async function validateYarnLock(kbn, yarnLock) { ${duplicateRanges} `); process.exit(1); + } // look for packages that have the the `kibana.devOnly` flag in their package.json + // and make sure they aren't included in the production dependencies of Kibana + + + const devOnlyProjectsInProduction = getDevOnlyProductionDepsTree(kbn, 'kibana'); + + if (devOnlyProjectsInProduction) { + _log__WEBPACK_IMPORTED_MODULE_4__["log"].error(dedent__WEBPACK_IMPORTED_MODULE_1___default.a` + Some of the packages in the production dependency chain for Kibana and X-Pack are + flagged with "kibana.devOnly" in their package.json. Please check changes made to + packages and their dependencies to ensure they don't end up in production. + + The devOnly dependencies that are being dependend on in production are: + + ${Object(_projects_tree__WEBPACK_IMPORTED_MODULE_5__["treeToString"])(devOnlyProjectsInProduction).split('\n').join('\n ')} + `); + process.exit(1); } - _log__WEBPACK_IMPORTED_MODULE_3__["log"].success('yarn.lock analysis completed without any issues'); + _log__WEBPACK_IMPORTED_MODULE_4__["log"].success('yarn.lock analysis completed without any issues'); +} + +function getDevOnlyProductionDepsTree(kbn, projectName) { + const project = kbn.getProject(projectName); + const childProjectNames = [...Object.keys(project.productionDependencies).filter(name => kbn.hasProject(name)), ...(projectName === 'kibana' ? ['x-pack'] : [])]; + const children = childProjectNames.map(n => getDevOnlyProductionDepsTree(kbn, n)).filter(t => !!t); + + if (!children.length && !project.isFlaggedAsDevOnly()) { + return; + } + + const tree = { + name: project.isFlaggedAsDevOnly() ? chalk__WEBPACK_IMPORTED_MODULE_2___default.a.red.bold(projectName) : projectName, + children + }; + return tree; } /***/ }), /* 281 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "renderProjectsTree", function() { return renderProjectsTree; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "treeToString", function() { return treeToString; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(113); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 projectKey = Symbol('__project'); +function renderProjectsTree(rootPath, projects) { + const projectsTree = buildProjectsTree(rootPath, projects); + return treeToString(createTreeStructure(projectsTree)); +} +function treeToString(tree) { + return [tree.name].concat(childrenToStrings(tree.children, '')).join('\n'); +} + +function childrenToStrings(tree, treePrefix) { + if (tree === undefined) { + return []; + } + + let strings = []; + tree.forEach((node, index) => { + const isLastNode = tree.length - 1 === index; + const nodePrefix = isLastNode ? '└── ' : '├── '; + const childPrefix = isLastNode ? ' ' : '│ '; + const childrenPrefix = treePrefix + childPrefix; + strings.push(`${treePrefix}${nodePrefix}${node.name}`); + strings = strings.concat(childrenToStrings(node.children, childrenPrefix)); + }); + return strings; +} + +function createTreeStructure(tree) { + let name; + const children = []; + + for (const [dir, project] of tree.entries()) { + // This is a leaf node (aka a project) + if (typeof project === 'string') { + name = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(project); + continue; + } // If there's only one project and the key indicates it's a leaf node, we + // know that we're at a package folder that contains a package.json, so we + // "inline it" so we don't get unnecessary levels, i.e. we'll just see + // `foo` instead of `foo -> foo`. + + + if (project.size === 1 && project.has(projectKey)) { + const projectName = project.get(projectKey); + children.push({ + children: [], + name: dirOrProjectName(dir, projectName) + }); + continue; + } + + const subtree = createTreeStructure(project); // If the name is specified, we know there's a package at the "root" of the + // subtree itself. + + if (subtree.name !== undefined) { + const projectName = subtree.name; + children.push({ + children: subtree.children, + name: dirOrProjectName(dir, projectName) + }); + continue; + } // Special-case whenever we have one child, so we don't get unnecessary + // folders in the output. E.g. instead of `foo -> bar -> baz` we get + // `foo/bar/baz` instead. + + + if (subtree.children && subtree.children.length === 1) { + const child = subtree.children[0]; + const newName = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(dir.toString(), child.name)); + children.push({ + children: child.children, + name: newName + }); + continue; + } + + children.push({ + children: subtree.children, + name: chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(dir.toString()) + }); + } + + return { + name, + children + }; +} + +function dirOrProjectName(dir, projectName) { + return dir === projectName ? chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(dir) : chalk__WEBPACK_IMPORTED_MODULE_0___default.a`{dim ${dir.toString()} ({reset.green ${projectName}})}`; +} + +function buildProjectsTree(rootPath, projects) { + const tree = new Map(); + + for (const project of projects.values()) { + if (rootPath === project.path) { + tree.set(projectKey, project.name); + } else { + const relativeProjectPath = path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(rootPath, project.path); + addProjectToTree(tree, relativeProjectPath.split(path__WEBPACK_IMPORTED_MODULE_1___default.a.sep), project); + } + } + + return tree; +} + +function addProjectToTree(tree, pathParts, project) { + if (pathParts.length === 0) { + tree.set(projectKey, project.name); + } else { + const [currentDir, ...rest] = pathParts; + + if (!tree.has(currentDir)) { + tree.set(currentDir, new Map()); + } + + const subtree = tree.get(currentDir); + addProjectToTree(subtree, rest, project); + } +} + +/***/ }), +/* 282 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(282); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(283); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(368); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(369); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); @@ -38285,21 +38480,21 @@ const CleanCommand = { }; /***/ }), -/* 282 */ +/* 283 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const path = __webpack_require__(4); -const globby = __webpack_require__(283); -const isGlob = __webpack_require__(295); -const slash = __webpack_require__(359); +const globby = __webpack_require__(284); +const isGlob = __webpack_require__(296); +const slash = __webpack_require__(360); const gracefulFs = __webpack_require__(133); -const isPathCwd = __webpack_require__(361); -const isPathInside = __webpack_require__(362); -const rimraf = __webpack_require__(363); -const pMap = __webpack_require__(364); +const isPathCwd = __webpack_require__(362); +const isPathInside = __webpack_require__(363); +const rimraf = __webpack_require__(364); +const pMap = __webpack_require__(365); const rimrafP = promisify(rimraf); @@ -38413,19 +38608,19 @@ module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options /***/ }), -/* 283 */ +/* 284 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(284); -const merge2 = __webpack_require__(285); +const arrayUnion = __webpack_require__(285); +const merge2 = __webpack_require__(286); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(286); -const dirGlob = __webpack_require__(355); -const gitignore = __webpack_require__(357); -const {FilterStream, UniqueStream} = __webpack_require__(360); +const fastGlob = __webpack_require__(287); +const dirGlob = __webpack_require__(356); +const gitignore = __webpack_require__(358); +const {FilterStream, UniqueStream} = __webpack_require__(361); const DEFAULT_FILTER = () => false; @@ -38598,7 +38793,7 @@ module.exports.gitignore = gitignore; /***/ }), -/* 284 */ +/* 285 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38610,7 +38805,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 285 */ +/* 286 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38761,17 +38956,17 @@ function pauseStreams (streams, options) { /***/ }), -/* 286 */ +/* 287 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const taskManager = __webpack_require__(287); -const async_1 = __webpack_require__(316); -const stream_1 = __webpack_require__(351); -const sync_1 = __webpack_require__(352); -const settings_1 = __webpack_require__(354); -const utils = __webpack_require__(288); +const taskManager = __webpack_require__(288); +const async_1 = __webpack_require__(317); +const stream_1 = __webpack_require__(352); +const sync_1 = __webpack_require__(353); +const settings_1 = __webpack_require__(355); +const utils = __webpack_require__(289); async function FastGlob(source, options) { assertPatternsInput(source); const works = getWorks(source, async_1.default, options); @@ -38835,13 +39030,13 @@ module.exports = FastGlob; /***/ }), -/* 287 */ +/* 288 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(288); +const utils = __webpack_require__(289); function generate(patterns, settings) { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); @@ -38906,30 +39101,30 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 288 */ +/* 289 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const array = __webpack_require__(289); +const array = __webpack_require__(290); exports.array = array; -const errno = __webpack_require__(290); +const errno = __webpack_require__(291); exports.errno = errno; -const fs = __webpack_require__(291); +const fs = __webpack_require__(292); exports.fs = fs; -const path = __webpack_require__(292); +const path = __webpack_require__(293); exports.path = path; -const pattern = __webpack_require__(293); +const pattern = __webpack_require__(294); exports.pattern = pattern; -const stream = __webpack_require__(314); +const stream = __webpack_require__(315); exports.stream = stream; -const string = __webpack_require__(315); +const string = __webpack_require__(316); exports.string = string; /***/ }), -/* 289 */ +/* 290 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38957,7 +39152,7 @@ exports.splitWhen = splitWhen; /***/ }), -/* 290 */ +/* 291 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38970,7 +39165,7 @@ exports.isEnoentCodeError = isEnoentCodeError; /***/ }), -/* 291 */ +/* 292 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -38995,7 +39190,7 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 292 */ +/* 293 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -39034,16 +39229,16 @@ exports.removeLeadingDotSegment = removeLeadingDotSegment; /***/ }), -/* 293 */ +/* 294 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const globParent = __webpack_require__(294); -const micromatch = __webpack_require__(297); -const picomatch = __webpack_require__(308); +const globParent = __webpack_require__(295); +const micromatch = __webpack_require__(298); +const picomatch = __webpack_require__(309); const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; const COMMON_GLOB_SYMBOLS_RE = /[*?]|^!/; @@ -39153,13 +39348,13 @@ exports.matchAny = matchAny; /***/ }), -/* 294 */ +/* 295 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isGlob = __webpack_require__(295); +var isGlob = __webpack_require__(296); var pathPosixDirname = __webpack_require__(4).posix.dirname; var isWin32 = __webpack_require__(121).platform() === 'win32'; @@ -39201,7 +39396,7 @@ module.exports = function globParent(str, opts) { /***/ }), -/* 295 */ +/* 296 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -39211,7 +39406,7 @@ module.exports = function globParent(str, opts) { * Released under the MIT License. */ -var isExtglob = __webpack_require__(296); +var isExtglob = __webpack_require__(297); var chars = { '{': '}', '(': ')', '[': ']'}; var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; @@ -39255,7 +39450,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 296 */ +/* 297 */ /***/ (function(module, exports) { /*! @@ -39281,16 +39476,16 @@ module.exports = function isExtglob(str) { /***/ }), -/* 297 */ +/* 298 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const util = __webpack_require__(112); -const braces = __webpack_require__(298); -const picomatch = __webpack_require__(308); -const utils = __webpack_require__(311); +const braces = __webpack_require__(299); +const picomatch = __webpack_require__(309); +const utils = __webpack_require__(312); const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); /** @@ -39755,16 +39950,16 @@ module.exports = micromatch; /***/ }), -/* 298 */ +/* 299 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(299); -const compile = __webpack_require__(301); -const expand = __webpack_require__(305); -const parse = __webpack_require__(306); +const stringify = __webpack_require__(300); +const compile = __webpack_require__(302); +const expand = __webpack_require__(306); +const parse = __webpack_require__(307); /** * Expand the given pattern or create a regex-compatible string. @@ -39932,13 +40127,13 @@ module.exports = braces; /***/ }), -/* 299 */ +/* 300 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(300); +const utils = __webpack_require__(301); module.exports = (ast, options = {}) => { let stringify = (node, parent = {}) => { @@ -39971,7 +40166,7 @@ module.exports = (ast, options = {}) => { /***/ }), -/* 300 */ +/* 301 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40090,14 +40285,14 @@ exports.flatten = (...args) => { /***/ }), -/* 301 */ +/* 302 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(302); -const utils = __webpack_require__(300); +const fill = __webpack_require__(303); +const utils = __webpack_require__(301); const compile = (ast, options = {}) => { let walk = (node, parent = {}) => { @@ -40154,7 +40349,7 @@ module.exports = compile; /***/ }), -/* 302 */ +/* 303 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40168,7 +40363,7 @@ module.exports = compile; const util = __webpack_require__(112); -const toRegexRange = __webpack_require__(303); +const toRegexRange = __webpack_require__(304); const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); @@ -40410,7 +40605,7 @@ module.exports = fill; /***/ }), -/* 303 */ +/* 304 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40423,7 +40618,7 @@ module.exports = fill; -const isNumber = __webpack_require__(304); +const isNumber = __webpack_require__(305); const toRegexRange = (min, max, options) => { if (isNumber(min) === false) { @@ -40705,7 +40900,7 @@ module.exports = toRegexRange; /***/ }), -/* 304 */ +/* 305 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -40730,15 +40925,15 @@ module.exports = function(num) { /***/ }), -/* 305 */ +/* 306 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(302); -const stringify = __webpack_require__(299); -const utils = __webpack_require__(300); +const fill = __webpack_require__(303); +const stringify = __webpack_require__(300); +const utils = __webpack_require__(301); const append = (queue = '', stash = '', enclose = false) => { let result = []; @@ -40850,13 +41045,13 @@ module.exports = expand; /***/ }), -/* 306 */ +/* 307 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(299); +const stringify = __webpack_require__(300); /** * Constants @@ -40878,7 +41073,7 @@ const { CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(307); +} = __webpack_require__(308); /** * parse @@ -41190,7 +41385,7 @@ module.exports = parse; /***/ }), -/* 307 */ +/* 308 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -41254,27 +41449,27 @@ module.exports = { /***/ }), -/* 308 */ +/* 309 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(309); +module.exports = __webpack_require__(310); /***/ }), -/* 309 */ +/* 310 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const scan = __webpack_require__(310); -const parse = __webpack_require__(313); -const utils = __webpack_require__(311); -const constants = __webpack_require__(312); +const scan = __webpack_require__(311); +const parse = __webpack_require__(314); +const utils = __webpack_require__(312); +const constants = __webpack_require__(313); const isObject = val => val && typeof val === 'object' && !Array.isArray(val); /** @@ -41610,13 +41805,13 @@ module.exports = picomatch; /***/ }), -/* 310 */ +/* 311 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(311); +const utils = __webpack_require__(312); const { CHAR_ASTERISK, /* * */ CHAR_AT, /* @ */ @@ -41633,7 +41828,7 @@ const { CHAR_RIGHT_CURLY_BRACE, /* } */ CHAR_RIGHT_PARENTHESES, /* ) */ CHAR_RIGHT_SQUARE_BRACKET /* ] */ -} = __webpack_require__(312); +} = __webpack_require__(313); const isPathSeparator = code => { return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; @@ -42000,7 +42195,7 @@ module.exports = scan; /***/ }), -/* 311 */ +/* 312 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42013,7 +42208,7 @@ const { REGEX_REMOVE_BACKSLASH, REGEX_SPECIAL_CHARS, REGEX_SPECIAL_CHARS_GLOBAL -} = __webpack_require__(312); +} = __webpack_require__(313); exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); @@ -42071,7 +42266,7 @@ exports.wrapOutput = (input, state = {}, options = {}) => { /***/ }), -/* 312 */ +/* 313 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -42257,14 +42452,14 @@ module.exports = { /***/ }), -/* 313 */ +/* 314 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const constants = __webpack_require__(312); -const utils = __webpack_require__(311); +const constants = __webpack_require__(313); +const utils = __webpack_require__(312); /** * Constants @@ -43342,13 +43537,13 @@ module.exports = parse; /***/ }), -/* 314 */ +/* 315 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const merge2 = __webpack_require__(285); +const merge2 = __webpack_require__(286); function merge(streams) { const mergedStream = merge2(streams); streams.forEach((stream) => { @@ -43365,7 +43560,7 @@ function propagateCloseEventToSources(streams) { /***/ }), -/* 315 */ +/* 316 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43382,14 +43577,14 @@ exports.isEmpty = isEmpty; /***/ }), -/* 316 */ +/* 317 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(317); -const provider_1 = __webpack_require__(344); +const stream_1 = __webpack_require__(318); +const provider_1 = __webpack_require__(345); class ProviderAsync extends provider_1.default { constructor() { super(...arguments); @@ -43417,16 +43612,16 @@ exports.default = ProviderAsync; /***/ }), -/* 317 */ +/* 318 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); -const fsStat = __webpack_require__(318); -const fsWalk = __webpack_require__(323); -const reader_1 = __webpack_require__(343); +const fsStat = __webpack_require__(319); +const fsWalk = __webpack_require__(324); +const reader_1 = __webpack_require__(344); class ReaderStream extends reader_1.default { constructor() { super(...arguments); @@ -43479,15 +43674,15 @@ exports.default = ReaderStream; /***/ }), -/* 318 */ +/* 319 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(319); -const sync = __webpack_require__(320); -const settings_1 = __webpack_require__(321); +const async = __webpack_require__(320); +const sync = __webpack_require__(321); +const settings_1 = __webpack_require__(322); exports.Settings = settings_1.default; function stat(path, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -43510,7 +43705,7 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 319 */ +/* 320 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43548,7 +43743,7 @@ function callSuccessCallback(callback, result) { /***/ }), -/* 320 */ +/* 321 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43577,13 +43772,13 @@ exports.read = read; /***/ }), -/* 321 */ +/* 322 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(322); +const fs = __webpack_require__(323); class Settings { constructor(_options = {}) { this._options = _options; @@ -43600,7 +43795,7 @@ exports.default = Settings; /***/ }), -/* 322 */ +/* 323 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -43623,16 +43818,16 @@ exports.createFileSystemAdapter = createFileSystemAdapter; /***/ }), -/* 323 */ +/* 324 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(324); -const stream_1 = __webpack_require__(339); -const sync_1 = __webpack_require__(340); -const settings_1 = __webpack_require__(342); +const async_1 = __webpack_require__(325); +const stream_1 = __webpack_require__(340); +const sync_1 = __webpack_require__(341); +const settings_1 = __webpack_require__(343); exports.Settings = settings_1.default; function walk(directory, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -43662,13 +43857,13 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 324 */ +/* 325 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(325); +const async_1 = __webpack_require__(326); class AsyncProvider { constructor(_root, _settings) { this._root = _root; @@ -43699,17 +43894,17 @@ function callSuccessCallback(callback, entries) { /***/ }), -/* 325 */ +/* 326 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const events_1 = __webpack_require__(156); -const fsScandir = __webpack_require__(326); -const fastq = __webpack_require__(335); -const common = __webpack_require__(337); -const reader_1 = __webpack_require__(338); +const fsScandir = __webpack_require__(327); +const fastq = __webpack_require__(336); +const common = __webpack_require__(338); +const reader_1 = __webpack_require__(339); class AsyncReader extends reader_1.default { constructor(_root, _settings) { super(_root, _settings); @@ -43799,15 +43994,15 @@ exports.default = AsyncReader; /***/ }), -/* 326 */ +/* 327 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(327); -const sync = __webpack_require__(332); -const settings_1 = __webpack_require__(333); +const async = __webpack_require__(328); +const sync = __webpack_require__(333); +const settings_1 = __webpack_require__(334); exports.Settings = settings_1.default; function scandir(path, optionsOrSettingsOrCallback, callback) { if (typeof optionsOrSettingsOrCallback === 'function') { @@ -43830,16 +44025,16 @@ function getSettings(settingsOrOptions = {}) { /***/ }), -/* 327 */ +/* 328 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(318); -const rpl = __webpack_require__(328); -const constants_1 = __webpack_require__(329); -const utils = __webpack_require__(330); +const fsStat = __webpack_require__(319); +const rpl = __webpack_require__(329); +const constants_1 = __webpack_require__(330); +const utils = __webpack_require__(331); function read(directory, settings, callback) { if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { return readdirWithFileTypes(directory, settings, callback); @@ -43927,7 +44122,7 @@ function callSuccessCallback(callback, result) { /***/ }), -/* 328 */ +/* 329 */ /***/ (function(module, exports) { module.exports = runParallel @@ -43981,7 +44176,7 @@ function runParallel (tasks, cb) { /***/ }), -/* 329 */ +/* 330 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44001,18 +44196,18 @@ exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = IS_MATCHED_BY_MAJOR || IS_MATCHED_B /***/ }), -/* 330 */ +/* 331 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(331); +const fs = __webpack_require__(332); exports.fs = fs; /***/ }), -/* 331 */ +/* 332 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44037,15 +44232,15 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 332 */ +/* 333 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(318); -const constants_1 = __webpack_require__(329); -const utils = __webpack_require__(330); +const fsStat = __webpack_require__(319); +const constants_1 = __webpack_require__(330); +const utils = __webpack_require__(331); function read(directory, settings) { if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { return readdirWithFileTypes(directory, settings); @@ -44096,15 +44291,15 @@ exports.readdir = readdir; /***/ }), -/* 333 */ +/* 334 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsStat = __webpack_require__(318); -const fs = __webpack_require__(334); +const fsStat = __webpack_require__(319); +const fs = __webpack_require__(335); class Settings { constructor(_options = {}) { this._options = _options; @@ -44127,7 +44322,7 @@ exports.default = Settings; /***/ }), -/* 334 */ +/* 335 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44152,13 +44347,13 @@ exports.createFileSystemAdapter = createFileSystemAdapter; /***/ }), -/* 335 */ +/* 336 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var reusify = __webpack_require__(336) +var reusify = __webpack_require__(337) function fastqueue (context, worker, concurrency) { if (typeof context === 'function') { @@ -44332,7 +44527,7 @@ module.exports = fastqueue /***/ }), -/* 336 */ +/* 337 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44372,7 +44567,7 @@ module.exports = reusify /***/ }), -/* 337 */ +/* 338 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -44403,13 +44598,13 @@ exports.joinPathSegments = joinPathSegments; /***/ }), -/* 338 */ +/* 339 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const common = __webpack_require__(337); +const common = __webpack_require__(338); class Reader { constructor(_root, _settings) { this._root = _root; @@ -44421,14 +44616,14 @@ exports.default = Reader; /***/ }), -/* 339 */ +/* 340 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); -const async_1 = __webpack_require__(325); +const async_1 = __webpack_require__(326); class StreamProvider { constructor(_root, _settings) { this._root = _root; @@ -44458,13 +44653,13 @@ exports.default = StreamProvider; /***/ }), -/* 340 */ +/* 341 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(341); +const sync_1 = __webpack_require__(342); class SyncProvider { constructor(_root, _settings) { this._root = _root; @@ -44479,15 +44674,15 @@ exports.default = SyncProvider; /***/ }), -/* 341 */ +/* 342 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsScandir = __webpack_require__(326); -const common = __webpack_require__(337); -const reader_1 = __webpack_require__(338); +const fsScandir = __webpack_require__(327); +const common = __webpack_require__(338); +const reader_1 = __webpack_require__(339); class SyncReader extends reader_1.default { constructor() { super(...arguments); @@ -44545,14 +44740,14 @@ exports.default = SyncReader; /***/ }), -/* 342 */ +/* 343 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsScandir = __webpack_require__(326); +const fsScandir = __webpack_require__(327); class Settings { constructor(_options = {}) { this._options = _options; @@ -44578,15 +44773,15 @@ exports.default = Settings; /***/ }), -/* 343 */ +/* 344 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const fsStat = __webpack_require__(318); -const utils = __webpack_require__(288); +const fsStat = __webpack_require__(319); +const utils = __webpack_require__(289); class Reader { constructor(_settings) { this._settings = _settings; @@ -44618,17 +44813,17 @@ exports.default = Reader; /***/ }), -/* 344 */ +/* 345 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const deep_1 = __webpack_require__(345); -const entry_1 = __webpack_require__(348); -const error_1 = __webpack_require__(349); -const entry_2 = __webpack_require__(350); +const deep_1 = __webpack_require__(346); +const entry_1 = __webpack_require__(349); +const error_1 = __webpack_require__(350); +const entry_2 = __webpack_require__(351); class Provider { constructor(_settings) { this._settings = _settings; @@ -44673,14 +44868,14 @@ exports.default = Provider; /***/ }), -/* 345 */ +/* 346 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(288); -const partial_1 = __webpack_require__(346); +const utils = __webpack_require__(289); +const partial_1 = __webpack_require__(347); class DeepFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -44734,13 +44929,13 @@ exports.default = DeepFilter; /***/ }), -/* 346 */ +/* 347 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const matcher_1 = __webpack_require__(347); +const matcher_1 = __webpack_require__(348); class PartialMatcher extends matcher_1.default { match(filepath) { const parts = filepath.split('/'); @@ -44779,13 +44974,13 @@ exports.default = PartialMatcher; /***/ }), -/* 347 */ +/* 348 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(288); +const utils = __webpack_require__(289); class Matcher { constructor(_patterns, _settings, _micromatchOptions) { this._patterns = _patterns; @@ -44836,13 +45031,13 @@ exports.default = Matcher; /***/ }), -/* 348 */ +/* 349 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(288); +const utils = __webpack_require__(289); class EntryFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -44898,13 +45093,13 @@ exports.default = EntryFilter; /***/ }), -/* 349 */ +/* 350 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(288); +const utils = __webpack_require__(289); class ErrorFilter { constructor(_settings) { this._settings = _settings; @@ -44920,13 +45115,13 @@ exports.default = ErrorFilter; /***/ }), -/* 350 */ +/* 351 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(288); +const utils = __webpack_require__(289); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -44953,15 +45148,15 @@ exports.default = EntryTransformer; /***/ }), -/* 351 */ +/* 352 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); -const stream_2 = __webpack_require__(317); -const provider_1 = __webpack_require__(344); +const stream_2 = __webpack_require__(318); +const provider_1 = __webpack_require__(345); class ProviderStream extends provider_1.default { constructor() { super(...arguments); @@ -44991,14 +45186,14 @@ exports.default = ProviderStream; /***/ }), -/* 352 */ +/* 353 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(353); -const provider_1 = __webpack_require__(344); +const sync_1 = __webpack_require__(354); +const provider_1 = __webpack_require__(345); class ProviderSync extends provider_1.default { constructor() { super(...arguments); @@ -45021,15 +45216,15 @@ exports.default = ProviderSync; /***/ }), -/* 353 */ +/* 354 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(318); -const fsWalk = __webpack_require__(323); -const reader_1 = __webpack_require__(343); +const fsStat = __webpack_require__(319); +const fsWalk = __webpack_require__(324); +const reader_1 = __webpack_require__(344); class ReaderSync extends reader_1.default { constructor() { super(...arguments); @@ -45071,7 +45266,7 @@ exports.default = ReaderSync; /***/ }), -/* 354 */ +/* 355 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45130,13 +45325,13 @@ exports.default = Settings; /***/ }), -/* 355 */ +/* 356 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(356); +const pathType = __webpack_require__(357); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -45212,7 +45407,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 356 */ +/* 357 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45262,7 +45457,7 @@ exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 357 */ +/* 358 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45270,9 +45465,9 @@ exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); const {promisify} = __webpack_require__(112); const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(286); -const gitIgnore = __webpack_require__(358); -const slash = __webpack_require__(359); +const fastGlob = __webpack_require__(287); +const gitIgnore = __webpack_require__(359); +const slash = __webpack_require__(360); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -45386,7 +45581,7 @@ module.exports.sync = options => { /***/ }), -/* 358 */ +/* 359 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -45989,7 +46184,7 @@ if ( /***/ }), -/* 359 */ +/* 360 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46007,7 +46202,7 @@ module.exports = path => { /***/ }), -/* 360 */ +/* 361 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46060,7 +46255,7 @@ module.exports = { /***/ }), -/* 361 */ +/* 362 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46082,7 +46277,7 @@ module.exports = path_ => { /***/ }), -/* 362 */ +/* 363 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46110,7 +46305,7 @@ module.exports = (childPath, parentPath) => { /***/ }), -/* 363 */ +/* 364 */ /***/ (function(module, exports, __webpack_require__) { const assert = __webpack_require__(140) @@ -46476,12 +46671,12 @@ rimraf.sync = rimrafSync /***/ }), -/* 364 */ +/* 365 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(365); +const AggregateError = __webpack_require__(366); module.exports = async ( iterable, @@ -46564,13 +46759,13 @@ module.exports = async ( /***/ }), -/* 365 */ +/* 366 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const indentString = __webpack_require__(366); -const cleanStack = __webpack_require__(367); +const indentString = __webpack_require__(367); +const cleanStack = __webpack_require__(368); const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); @@ -46618,7 +46813,7 @@ module.exports = AggregateError; /***/ }), -/* 366 */ +/* 367 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46660,7 +46855,7 @@ module.exports = (string, count = 1, options) => { /***/ }), -/* 367 */ +/* 368 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46707,20 +46902,20 @@ module.exports = (stack, options) => { /***/ }), -/* 368 */ +/* 369 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readline = __webpack_require__(369); -const chalk = __webpack_require__(370); -const cliCursor = __webpack_require__(377); -const cliSpinners = __webpack_require__(381); -const logSymbols = __webpack_require__(383); -const stripAnsi = __webpack_require__(392); -const wcwidth = __webpack_require__(394); -const isInteractive = __webpack_require__(398); -const MuteStream = __webpack_require__(399); +const readline = __webpack_require__(370); +const chalk = __webpack_require__(371); +const cliCursor = __webpack_require__(378); +const cliSpinners = __webpack_require__(382); +const logSymbols = __webpack_require__(384); +const stripAnsi = __webpack_require__(393); +const wcwidth = __webpack_require__(395); +const isInteractive = __webpack_require__(399); +const MuteStream = __webpack_require__(400); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); @@ -47073,23 +47268,23 @@ module.exports.promise = (action, options) => { /***/ }), -/* 369 */ +/* 370 */ /***/ (function(module, exports) { module.exports = require("readline"); /***/ }), -/* 370 */ +/* 371 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiStyles = __webpack_require__(371); +const ansiStyles = __webpack_require__(372); const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex -} = __webpack_require__(375); +} = __webpack_require__(376); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -47290,7 +47485,7 @@ const chalkTag = (chalk, ...strings) => { } if (template === undefined) { - template = __webpack_require__(376); + template = __webpack_require__(377); } return template(chalk, parts.join('')); @@ -47319,7 +47514,7 @@ module.exports = chalk; /***/ }), -/* 371 */ +/* 372 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47365,7 +47560,7 @@ const setLazyProperty = (object, property, get) => { let colorConvert; const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { if (colorConvert === undefined) { - colorConvert = __webpack_require__(372); + colorConvert = __webpack_require__(373); } const offset = isBackground ? 10 : 0; @@ -47490,11 +47685,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 372 */ +/* 373 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(373); -const route = __webpack_require__(374); +const conversions = __webpack_require__(374); +const route = __webpack_require__(375); const convert = {}; @@ -47577,7 +47772,7 @@ module.exports = convert; /***/ }), -/* 373 */ +/* 374 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ @@ -48422,10 +48617,10 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 374 */ +/* 375 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(373); +const conversions = __webpack_require__(374); /* This function routes a model to all other models. @@ -48525,7 +48720,7 @@ module.exports = function (fromModel) { /***/ }), -/* 375 */ +/* 376 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48571,7 +48766,7 @@ module.exports = { /***/ }), -/* 376 */ +/* 377 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48712,12 +48907,12 @@ module.exports = (chalk, temporary) => { /***/ }), -/* 377 */ +/* 378 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(378); +const restoreCursor = __webpack_require__(379); let isHidden = false; @@ -48754,12 +48949,12 @@ exports.toggle = (force, writableStream) => { /***/ }), -/* 378 */ +/* 379 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const onetime = __webpack_require__(379); +const onetime = __webpack_require__(380); const signalExit = __webpack_require__(218); module.exports = onetime(() => { @@ -48770,12 +48965,12 @@ module.exports = onetime(() => { /***/ }), -/* 379 */ +/* 380 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(380); +const mimicFn = __webpack_require__(381); const calledFunctions = new WeakMap(); @@ -48827,7 +49022,7 @@ module.exports.callCount = fn => { /***/ }), -/* 380 */ +/* 381 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48847,13 +49042,13 @@ module.exports.default = mimicFn; /***/ }), -/* 381 */ +/* 382 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const spinners = Object.assign({}, __webpack_require__(382)); +const spinners = Object.assign({}, __webpack_require__(383)); const spinnersList = Object.keys(spinners); @@ -48871,18 +49066,18 @@ module.exports.default = spinners; /***/ }), -/* 382 */ +/* 383 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]}}"); /***/ }), -/* 383 */ +/* 384 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(384); +const chalk = __webpack_require__(385); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -48904,16 +49099,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 384 */ +/* 385 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(179); -const ansiStyles = __webpack_require__(385); -const stdoutColor = __webpack_require__(390).stdout; +const ansiStyles = __webpack_require__(386); +const stdoutColor = __webpack_require__(391).stdout; -const template = __webpack_require__(391); +const template = __webpack_require__(392); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -49139,12 +49334,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 385 */ +/* 386 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(386); +const colorConvert = __webpack_require__(387); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -49312,11 +49507,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 386 */ +/* 387 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(387); -var route = __webpack_require__(389); +var conversions = __webpack_require__(388); +var route = __webpack_require__(390); var convert = {}; @@ -49396,11 +49591,11 @@ module.exports = convert; /***/ }), -/* 387 */ +/* 388 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(388); +var cssKeywords = __webpack_require__(389); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -50270,7 +50465,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 388 */ +/* 389 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50429,10 +50624,10 @@ module.exports = { /***/ }), -/* 389 */ +/* 390 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(387); +var conversions = __webpack_require__(388); /* this function routes a model to all other models. @@ -50532,7 +50727,7 @@ module.exports = function (fromModel) { /***/ }), -/* 390 */ +/* 391 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50670,7 +50865,7 @@ module.exports = { /***/ }), -/* 391 */ +/* 392 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50805,18 +51000,18 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 392 */ +/* 393 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(393); +const ansiRegex = __webpack_require__(394); module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 393 */ +/* 394 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50833,14 +51028,14 @@ module.exports = ({onlyFirst = false} = {}) => { /***/ }), -/* 394 */ +/* 395 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var defaults = __webpack_require__(395) -var combining = __webpack_require__(397) +var defaults = __webpack_require__(396) +var combining = __webpack_require__(398) var DEFAULTS = { nul: 0, @@ -50939,10 +51134,10 @@ function bisearch(ucs) { /***/ }), -/* 395 */ +/* 396 */ /***/ (function(module, exports, __webpack_require__) { -var clone = __webpack_require__(396); +var clone = __webpack_require__(397); module.exports = function(options, defaults) { options = options || {}; @@ -50957,7 +51152,7 @@ module.exports = function(options, defaults) { }; /***/ }), -/* 396 */ +/* 397 */ /***/ (function(module, exports, __webpack_require__) { var clone = (function() { @@ -51129,7 +51324,7 @@ if ( true && module.exports) { /***/ }), -/* 397 */ +/* 398 */ /***/ (function(module, exports) { module.exports = [ @@ -51185,7 +51380,7 @@ module.exports = [ /***/ }), -/* 398 */ +/* 399 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51201,7 +51396,7 @@ module.exports = ({stream = process.stdout} = {}) => { /***/ }), -/* 399 */ +/* 400 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -51352,7 +51547,7 @@ MuteStream.prototype.close = proxy('close') /***/ }), -/* 400 */ +/* 401 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51413,7 +51608,7 @@ const RunCommand = { }; /***/ }), -/* 401 */ +/* 402 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -51423,7 +51618,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(144); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(145); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(146); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(402); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(403); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51508,14 +51703,14 @@ const WatchCommand = { }; /***/ }), -/* 402 */ +/* 403 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(403); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(404); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -51582,141 +51777,141 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 403 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(404); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(405); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(405); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(406); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(406); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(407); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(407); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(408); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(408); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(409); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(409); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(410); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(410); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(411); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(411); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(412); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(412); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(413); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(413); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(414); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(414); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(415); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); /* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(415); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(416); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(416); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(417); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(417); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(418); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(418); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(419); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(419); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(420); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(420); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(421); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(421); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(422); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(423); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(424); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(424); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(425); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(425); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(426); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(426); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(427); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(427); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(428); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(428); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(429); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(431); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(432); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(433); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(433); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(434); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(434); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(435); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(435); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(436); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); /* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(436); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(437); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(437); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(438); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(438); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(439); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(439); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(440); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); /* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(440); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(441); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(441); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(442); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(442); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(443); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); /* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(444); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(445); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(445); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(446); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(446); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(447); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(449); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(450); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); /* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81); @@ -51727,175 +51922,175 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(450); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(451); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(451); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(452); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(452); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(453); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(453); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(454); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); /* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(454); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(455); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(455); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(456); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(456); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(457); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(457); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(458); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(458); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(459); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(459); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(460); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(460); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(461); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(461); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(462); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(462); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(463); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(447); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(448); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(463); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(464); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(464); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(465); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(465); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(466); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(466); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(467); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); /* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(467); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(468); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(468); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(469); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(448); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(449); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(469); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(470); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(470); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(471); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(471); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(472); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(472); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(473); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(473); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(474); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(474); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(475); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(475); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(476); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(476); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(477); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(477); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(478); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(478); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(479); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(480); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(481); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(481); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(482); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(482); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(483); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(430); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(431); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(443); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(444); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(483); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(484); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(484); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(485); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(485); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(486); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(486); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(487); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(487); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(488); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(429); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(430); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(488); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(489); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(489); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(490); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(490); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(491); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(491); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(492); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(492); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(493); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(493); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(494); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(494); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(495); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(495); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(496); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(496); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(497); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(497); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(498); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(498); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(499); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(499); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(500); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(500); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(501); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -52006,7 +52201,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 404 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52085,14 +52280,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 405 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(404); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(405); /* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -52108,7 +52303,7 @@ function auditTime(duration, scheduler) { /***/ }), -/* 406 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52155,7 +52350,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 407 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52256,7 +52451,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 408 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52417,7 +52612,7 @@ function dispatchBufferClose(arg) { /***/ }), -/* 409 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52536,7 +52731,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 410 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52629,7 +52824,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 411 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52689,7 +52884,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 412 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52705,7 +52900,7 @@ function combineAll(project) { /***/ }), -/* 413 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52737,7 +52932,7 @@ function combineLatest() { /***/ }), -/* 414 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52757,7 +52952,7 @@ function concat() { /***/ }), -/* 415 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52773,13 +52968,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 416 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(415); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(416); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -52789,7 +52984,7 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 417 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52854,7 +53049,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 418 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52939,7 +53134,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 419 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53015,7 +53210,7 @@ function dispatchNext(subscriber) { /***/ }), -/* 420 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53065,7 +53260,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 421 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53073,7 +53268,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(422); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(423); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); /* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -53172,7 +53367,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 422 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53186,7 +53381,7 @@ function isDate(value) { /***/ }), -/* 423 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53332,7 +53527,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 424 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53370,7 +53565,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 425 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53446,7 +53641,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 426 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53517,13 +53712,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 427 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(426); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(427); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -53533,7 +53728,7 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 428 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53541,9 +53736,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); /* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(429); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(420); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(430); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(430); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(421); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(431); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -53565,7 +53760,7 @@ function elementAt(index, defaultValue) { /***/ }), -/* 429 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53631,7 +53826,7 @@ function defaultErrorFactory() { /***/ }), -/* 430 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53693,7 +53888,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 431 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53715,7 +53910,7 @@ function endWith() { /***/ }), -/* 432 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53777,7 +53972,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 433 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53831,7 +54026,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 434 */ +/* 435 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53925,7 +54120,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 435 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54037,7 +54232,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 436 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54075,7 +54270,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 437 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54147,13 +54342,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 438 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(437); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(438); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -54163,7 +54358,7 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 439 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54171,9 +54366,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(430); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(420); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(429); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(431); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(421); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(430); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -54190,7 +54385,7 @@ function first(predicate, defaultValue) { /***/ }), -/* 440 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54227,7 +54422,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 441 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54271,7 +54466,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 442 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54279,9 +54474,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(443); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(429); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(420); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(444); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(430); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(421); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -54298,7 +54493,7 @@ function last(predicate, defaultValue) { /***/ }), -/* 443 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54375,7 +54570,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 444 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54414,7 +54609,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 445 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54464,13 +54659,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 446 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(447); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(448); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -54483,15 +54678,15 @@ function max(comparer) { /***/ }), -/* 447 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(448); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(443); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(420); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(449); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(444); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(421); /* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -54512,7 +54707,7 @@ function reduce(accumulator, seed) { /***/ }), -/* 448 */ +/* 449 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54594,7 +54789,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 449 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54614,7 +54809,7 @@ function merge() { /***/ }), -/* 450 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54639,7 +54834,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 451 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54748,13 +54943,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 452 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(447); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(448); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -54767,7 +54962,7 @@ function min(comparer) { /***/ }), -/* 453 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54816,7 +55011,7 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 454 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54906,7 +55101,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 455 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54954,7 +55149,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 456 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54977,7 +55172,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 457 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55017,14 +55212,14 @@ function plucker(props, length) { /***/ }), -/* 458 */ +/* 459 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(27); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(453); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(454); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -55037,14 +55232,14 @@ function publish(selector) { /***/ }), -/* 459 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); /* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(32); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(453); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(454); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -55055,14 +55250,14 @@ function publishBehavior(value) { /***/ }), -/* 460 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); /* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(50); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(453); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(454); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -55073,14 +55268,14 @@ function publishLast() { /***/ }), -/* 461 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); /* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(33); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(453); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(454); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -55096,7 +55291,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 462 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55123,7 +55318,7 @@ function race() { /***/ }), -/* 463 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55188,7 +55383,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 464 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55282,7 +55477,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 465 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55335,7 +55530,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 466 */ +/* 467 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55421,7 +55616,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 467 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55476,7 +55671,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 468 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55536,7 +55731,7 @@ function dispatchNotification(state) { /***/ }), -/* 469 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55659,13 +55854,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 470 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(453); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(454); /* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); /** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ @@ -55682,7 +55877,7 @@ function share() { /***/ }), -/* 471 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55751,7 +55946,7 @@ function shareReplayOperator(_a) { /***/ }), -/* 472 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55831,7 +56026,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 473 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55873,7 +56068,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 474 */ +/* 475 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55935,7 +56130,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 475 */ +/* 476 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55992,7 +56187,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 476 */ +/* 477 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56048,7 +56243,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 477 */ +/* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56077,13 +56272,13 @@ function startWith() { /***/ }), -/* 478 */ +/* 479 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(479); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(480); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -56108,7 +56303,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 479 */ +/* 480 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56172,13 +56367,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 480 */ +/* 481 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(481); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(482); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -56190,7 +56385,7 @@ function switchAll() { /***/ }), -/* 481 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56278,13 +56473,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 482 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(481); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(482); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -56294,7 +56489,7 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 483 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56342,7 +56537,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 484 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56410,7 +56605,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 485 */ +/* 486 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56498,7 +56693,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 486 */ +/* 487 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56600,7 +56795,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 487 */ +/* 488 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56609,7 +56804,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(486); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(487); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -56698,7 +56893,7 @@ function dispatchNext(arg) { /***/ }), -/* 488 */ +/* 489 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56706,7 +56901,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(448); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(449); /* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91); /* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -56742,7 +56937,7 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 489 */ +/* 490 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56750,7 +56945,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); /* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(490); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(491); /* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49); /** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ @@ -56767,7 +56962,7 @@ function timeout(due, scheduler) { /***/ }), -/* 490 */ +/* 491 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56775,7 +56970,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(422); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(423); /* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */ @@ -56846,7 +57041,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 491 */ +/* 492 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56876,13 +57071,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 492 */ +/* 493 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(447); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(448); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -56899,7 +57094,7 @@ function toArray() { /***/ }), -/* 493 */ +/* 494 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56977,7 +57172,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 494 */ +/* 495 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57067,7 +57262,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 495 */ +/* 496 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57237,7 +57432,7 @@ function dispatchWindowClose(state) { /***/ }), -/* 496 */ +/* 497 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57380,7 +57575,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 497 */ +/* 498 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57477,7 +57672,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 498 */ +/* 499 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57572,7 +57767,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 499 */ +/* 500 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57594,7 +57789,7 @@ function zip() { /***/ }), -/* 500 */ +/* 501 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57610,7 +57805,7 @@ function zipAll(project) { /***/ }), -/* 501 */ +/* 502 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57619,7 +57814,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(163); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(144); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(146); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(502); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(281); /* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(503); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } @@ -57701,159 +57896,6 @@ function toArray(value) { return Array.isArray(value) ? value : [value]; } -/***/ }), -/* 502 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "renderProjectsTree", function() { return renderProjectsTree; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(113); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT 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 projectKey = Symbol('__project'); -function renderProjectsTree(rootPath, projects) { - const projectsTree = buildProjectsTree(rootPath, projects); - return treeToString(createTreeStructure(projectsTree)); -} - -function treeToString(tree) { - return [tree.name].concat(childrenToStrings(tree.children, '')).join('\n'); -} - -function childrenToStrings(tree, treePrefix) { - if (tree === undefined) { - return []; - } - - let strings = []; - tree.forEach((node, index) => { - const isLastNode = tree.length - 1 === index; - const nodePrefix = isLastNode ? '└── ' : '├── '; - const childPrefix = isLastNode ? ' ' : '│ '; - const childrenPrefix = treePrefix + childPrefix; - strings.push(`${treePrefix}${nodePrefix}${node.name}`); - strings = strings.concat(childrenToStrings(node.children, childrenPrefix)); - }); - return strings; -} - -function createTreeStructure(tree) { - let name; - const children = []; - - for (const [dir, project] of tree.entries()) { - // This is a leaf node (aka a project) - if (typeof project === 'string') { - name = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(project); - continue; - } // If there's only one project and the key indicates it's a leaf node, we - // know that we're at a package folder that contains a package.json, so we - // "inline it" so we don't get unnecessary levels, i.e. we'll just see - // `foo` instead of `foo -> foo`. - - - if (project.size === 1 && project.has(projectKey)) { - const projectName = project.get(projectKey); - children.push({ - children: [], - name: dirOrProjectName(dir, projectName) - }); - continue; - } - - const subtree = createTreeStructure(project); // If the name is specified, we know there's a package at the "root" of the - // subtree itself. - - if (subtree.name !== undefined) { - const projectName = subtree.name; - children.push({ - children: subtree.children, - name: dirOrProjectName(dir, projectName) - }); - continue; - } // Special-case whenever we have one child, so we don't get unnecessary - // folders in the output. E.g. instead of `foo -> bar -> baz` we get - // `foo/bar/baz` instead. - - - if (subtree.children && subtree.children.length === 1) { - const child = subtree.children[0]; - const newName = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(dir.toString(), child.name)); - children.push({ - children: child.children, - name: newName - }); - continue; - } - - children.push({ - children: subtree.children, - name: chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(dir.toString()) - }); - } - - return { - name, - children - }; -} - -function dirOrProjectName(dir, projectName) { - return dir === projectName ? chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(dir) : chalk__WEBPACK_IMPORTED_MODULE_0___default.a`{dim ${dir.toString()} ({reset.green ${projectName}})}`; -} - -function buildProjectsTree(rootPath, projects) { - const tree = new Map(); - - for (const project of projects.values()) { - if (rootPath === project.path) { - tree.set(projectKey, project.name); - } else { - const relativeProjectPath = path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(rootPath, project.path); - addProjectToTree(tree, relativeProjectPath.split(path__WEBPACK_IMPORTED_MODULE_1___default.a.sep), project); - } - } - - return tree; -} - -function addProjectToTree(tree, pathParts, project) { - if (pathParts.length === 0) { - tree.set(projectKey, project.name); - } else { - const [currentDir, ...rest] = pathParts; - - if (!tree.has(currentDir)) { - tree.set(currentDir, new Map()); - } - - const subtree = tree.get(currentDir); - addProjectToTree(subtree, rest, project); - } -} - /***/ }), /* 503 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { @@ -57865,7 +57907,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(504); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(362); +/* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(363); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(276); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(146); @@ -58157,7 +58199,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(510); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(282); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(283); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); @@ -58308,7 +58350,7 @@ const os = __webpack_require__(121); const pAll = __webpack_require__(511); const arrify = __webpack_require__(513); const globby = __webpack_require__(514); -const isGlob = __webpack_require__(295); +const isGlob = __webpack_require__(296); const cpFile = __webpack_require__(714); const junk = __webpack_require__(724); const CpyError = __webpack_require__(725); @@ -59026,7 +59068,7 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); var globParent = __webpack_require__(522); -var isGlob = __webpack_require__(295); +var isGlob = __webpack_require__(296); var micromatch = __webpack_require__(525); var GLOBSTAR = '**'; /** @@ -59214,7 +59256,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(296); +var isExtglob = __webpack_require__(297); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -82869,7 +82911,7 @@ exports.flatten = flatten; "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(285); +var merge2 = __webpack_require__(286); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 8ffd86b84bf76..944fcf5998637 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -4,6 +4,9 @@ "version": "1.0.0", "license": "Apache-2.0", "private": true, + "kibana": { + "devOnly": true + }, "scripts": { "build": "webpack", "kbn:watch": "webpack --watch --progress", diff --git a/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap b/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap deleted file mode 100644 index be146d710c87a..0000000000000 --- a/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap +++ /dev/null @@ -1,159 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`calls "kbn:bootstrap" scripts and links executables after installing deps: link bins 1`] = ` -Array [ - Array [ - Map { - "kibana" => Project { - "allDependencies": Object { - "bar": "1.0.0", - }, - "devDependencies": Object {}, - "isWorkspaceProject": false, - "isWorkspaceRoot": true, - "json": Object { - "dependencies": Object { - "bar": "1.0.0", - }, - "name": "kibana", - "version": "1.0.0", - "workspaces": Object { - "packages": Array [ - "packages/*", - ], - }, - }, - "nodeModulesLocation": "/packages/kbn-pm/src/commands/node_modules", - "packageJsonLocation": "/packages/kbn-pm/src/commands/package.json", - "path": "/packages/kbn-pm/src/commands", - "productionDependencies": Object { - "bar": "1.0.0", - }, - "scripts": Object {}, - "targetLocation": "/packages/kbn-pm/src/commands/target", - "version": "1.0.0", - }, - "bar" => Project { - "allDependencies": Object {}, - "devDependencies": Object {}, - "isWorkspaceProject": false, - "isWorkspaceRoot": false, - "json": Object { - "name": "bar", - "scripts": Object { - "kbn:bootstrap": "node ./bar.js", - }, - "version": "1.0.0", - }, - "nodeModulesLocation": "/packages/kbn-pm/src/commands/packages/bar/node_modules", - "packageJsonLocation": "/packages/kbn-pm/src/commands/packages/bar/package.json", - "path": "/packages/kbn-pm/src/commands/packages/bar", - "productionDependencies": Object {}, - "scripts": Object { - "kbn:bootstrap": "node ./bar.js", - }, - "targetLocation": "/packages/kbn-pm/src/commands/packages/bar/target", - "version": "1.0.0", - }, - }, - Map { - "kibana" => Array [ - Project { - "allDependencies": Object {}, - "devDependencies": Object {}, - "isWorkspaceProject": false, - "isWorkspaceRoot": false, - "json": Object { - "name": "bar", - "scripts": Object { - "kbn:bootstrap": "node ./bar.js", - }, - "version": "1.0.0", - }, - "nodeModulesLocation": "/packages/kbn-pm/src/commands/packages/bar/node_modules", - "packageJsonLocation": "/packages/kbn-pm/src/commands/packages/bar/package.json", - "path": "/packages/kbn-pm/src/commands/packages/bar", - "productionDependencies": Object {}, - "scripts": Object { - "kbn:bootstrap": "node ./bar.js", - }, - "targetLocation": "/packages/kbn-pm/src/commands/packages/bar/target", - "version": "1.0.0", - }, - ], - "bar" => Array [], - }, - ], -] -`; - -exports[`calls "kbn:bootstrap" scripts and links executables after installing deps: script 1`] = ` -Array [ - Array [ - Object { - "args": Array [], - "debug": undefined, - "pkg": Project { - "allDependencies": Object {}, - "devDependencies": Object {}, - "isWorkspaceProject": false, - "isWorkspaceRoot": false, - "json": Object { - "name": "bar", - "scripts": Object { - "kbn:bootstrap": "node ./bar.js", - }, - "version": "1.0.0", - }, - "nodeModulesLocation": "/packages/kbn-pm/src/commands/packages/bar/node_modules", - "packageJsonLocation": "/packages/kbn-pm/src/commands/packages/bar/package.json", - "path": "/packages/kbn-pm/src/commands/packages/bar", - "productionDependencies": Object {}, - "scripts": Object { - "kbn:bootstrap": "node ./bar.js", - }, - "targetLocation": "/packages/kbn-pm/src/commands/packages/bar/target", - "version": "1.0.0", - }, - "script": "kbn:bootstrap", - }, - ], -] -`; - -exports[`does not run installer if no deps in package: install in dir 1`] = ` -Array [ - Array [ - "/packages/kbn-pm/src/commands", - Array [], - ], -] -`; - -exports[`handles "frozen-lockfile": install in dir 1`] = ` -Array [ - Array [ - "/packages/kbn-pm/src/commands", - Array [ - "--frozen-lockfile", - ], - ], -] -`; - -exports[`handles dependencies of dependencies: install in dir 1`] = ` -Array [ - Array [ - "/packages/kbn-pm/src/commands", - Array [], - ], - Array [ - "/packages/kbn-pm/src/commands/packages/bar", - Array [], - ], - Array [ - "/packages/kbn-pm/src/commands/packages/foo", - Array [], - ], -] -`; diff --git a/packages/kbn-pm/src/commands/bootstrap.test.ts b/packages/kbn-pm/src/commands/bootstrap.test.ts deleted file mode 100644 index 956c4e72933ba..0000000000000 --- a/packages/kbn-pm/src/commands/bootstrap.test.ts +++ /dev/null @@ -1,246 +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. - */ - -jest.mock('../utils/scripts'); -jest.mock('../utils/link_project_executables'); -jest.mock('../utils/validate_yarn_lock'); - -import { resolve } from 'path'; - -import { ToolingLogCollectingWriter } from '@kbn/dev-utils/tooling_log'; - -import { absolutePathSnapshotSerializer, stripAnsiSnapshotSerializer } from '../test_helpers'; -import { linkProjectExecutables } from '../utils/link_project_executables'; -import { IPackageJson } from '../utils/package_json'; -import { Project } from '../utils/project'; -import { buildProjectGraph } from '../utils/projects'; -import { installInDir, runScriptInPackageStreaming, yarnWorkspacesInfo } from '../utils/scripts'; -import { BootstrapCommand } from './bootstrap'; -import { Kibana } from '../utils/kibana'; -import { log } from '../utils/log'; - -const mockInstallInDir = installInDir as jest.Mock; -const mockRunScriptInPackageStreaming = runScriptInPackageStreaming as jest.Mock; -const mockLinkProjectExecutables = linkProjectExecutables as jest.Mock; -const mockYarnWorkspacesInfo = yarnWorkspacesInfo as jest.Mock; - -const logWriter = new ToolingLogCollectingWriter('debug'); -log.setLogLevel('silent'); -log.setWriters([logWriter]); -beforeEach(() => { - logWriter.messages.length = 0; -}); - -const createProject = (packageJson: IPackageJson, path = '.') => { - const project = new Project( - { - name: 'kibana', - version: '1.0.0', - ...packageJson, - }, - resolve(__dirname, path) - ); - - if (packageJson.workspaces) { - project.isWorkspaceRoot = true; - } - - return project; -}; -expect.addSnapshotSerializer(absolutePathSnapshotSerializer); -expect.addSnapshotSerializer(stripAnsiSnapshotSerializer); - -beforeEach(() => { - mockYarnWorkspacesInfo.mockResolvedValue({}); -}); - -afterEach(() => { - jest.resetAllMocks(); - jest.restoreAllMocks(); -}); - -test('handles dependencies of dependencies', async () => { - const kibana = createProject({ - dependencies: { - bar: '1.0.0', - }, - workspaces: { - packages: ['packages/*'], - }, - }); - const foo = createProject( - { - dependencies: { - bar: 'link:../bar', - }, - name: 'foo', - }, - 'packages/foo' - ); - const bar = createProject( - { - dependencies: { - baz: 'link:../baz', - }, - name: 'bar', - }, - 'packages/bar' - ); - const baz = createProject( - { - name: 'baz', - }, - 'packages/baz' - ); - - const projects = new Map([ - ['kibana', kibana], - ['foo', foo], - ['bar', bar], - ['baz', baz], - ]); - const kbn = new Kibana(projects); - const projectGraph = buildProjectGraph(projects); - - await BootstrapCommand.run(projects, projectGraph, { - extraArgs: [], - options: {}, - rootPath: '', - kbn, - }); - - expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir'); - expect(logWriter.messages).toMatchInlineSnapshot(` - Array [ - info [kibana] running yarn, - "", - "", - info [bar] running yarn, - "", - "", - info [foo] running yarn, - "", - "", - ] - `); -}); - -test('does not run installer if no deps in package', async () => { - const kibana = createProject({ - dependencies: { - bar: '1.0.0', - }, - workspaces: { - packages: ['packages/*'], - }, - }); - // bar has no dependencies - const bar = createProject( - { - name: 'bar', - }, - 'packages/bar' - ); - - const projects = new Map([ - ['kibana', kibana], - ['bar', bar], - ]); - const kbn = new Kibana(projects); - const projectGraph = buildProjectGraph(projects); - - await BootstrapCommand.run(projects, projectGraph, { - extraArgs: [], - options: {}, - rootPath: '', - kbn, - }); - - expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir'); - expect(logWriter.messages).toMatchInlineSnapshot(` - Array [ - info [kibana] running yarn, - "", - "", - ] - `); -}); - -test('handles "frozen-lockfile"', async () => { - const kibana = createProject({ - dependencies: { - foo: '2.2.0', - }, - workspaces: { - packages: ['packages/*'], - }, - }); - - const projects = new Map([['kibana', kibana]]); - const kbn = new Kibana(projects); - const projectGraph = buildProjectGraph(projects); - - await BootstrapCommand.run(projects, projectGraph, { - extraArgs: [], - options: { - 'frozen-lockfile': true, - }, - rootPath: '', - kbn, - }); - - expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir'); -}); - -test('calls "kbn:bootstrap" scripts and links executables after installing deps', async () => { - const kibana = createProject({ - dependencies: { - bar: '1.0.0', - }, - workspaces: { - packages: ['packages/*'], - }, - }); - const bar = createProject( - { - name: 'bar', - scripts: { - 'kbn:bootstrap': 'node ./bar.js', - }, - }, - 'packages/bar' - ); - - const projects = new Map([ - ['kibana', kibana], - ['bar', bar], - ]); - const kbn = new Kibana(projects); - const projectGraph = buildProjectGraph(projects); - - await BootstrapCommand.run(projects, projectGraph, { - extraArgs: [], - options: {}, - rootPath: '', - kbn, - }); - - expect(mockLinkProjectExecutables.mock.calls).toMatchSnapshot('link bins'); - expect(mockRunScriptInPackageStreaming.mock.calls).toMatchSnapshot('script'); -}); diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 7cf89c5f08f96..caa16fbc94702 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -26,7 +26,7 @@ import { ICommand } from './'; import { getAllChecksums } from '../utils/project_checksums'; import { BootstrapCacheFile } from '../utils/bootstrap_cache_file'; import { readYarnLock } from '../utils/yarn_lock'; -import { validateYarnLock } from '../utils/validate_yarn_lock'; +import { validateDependencies } from '../utils/validate_dependencies'; export const BootstrapCommand: ICommand = { description: 'Install dependencies and crosslink projects', @@ -58,7 +58,7 @@ export const BootstrapCommand: ICommand = { const yarnLock = await readYarnLock(kbn); - await validateYarnLock(kbn, yarnLock); + await validateDependencies(kbn, yarnLock); await linkProjectExecutables(projects, projectGraph); diff --git a/packages/kbn-pm/src/utils/project.ts b/packages/kbn-pm/src/utils/project.ts index 8f45df52c7a2f..4e4d76544aa35 100644 --- a/packages/kbn-pm/src/utils/project.ts +++ b/packages/kbn-pm/src/utils/project.ts @@ -153,6 +153,10 @@ export class Project { return (this.json.kibana && this.json.kibana.clean) || {}; } + public isFlaggedAsDevOnly() { + return !!(this.json.kibana && this.json.kibana.devOnly); + } + public hasScript(name: string) { return name in this.scripts; } diff --git a/packages/kbn-pm/src/utils/projects_tree.ts b/packages/kbn-pm/src/utils/projects_tree.ts index c7a13ce2de348..4ba000bc1b158 100644 --- a/packages/kbn-pm/src/utils/projects_tree.ts +++ b/packages/kbn-pm/src/utils/projects_tree.ts @@ -29,7 +29,7 @@ export function renderProjectsTree(rootPath: string, projects: Map {} -function treeToString(tree: ITree) { +export function treeToString(tree: ITree) { return [tree.name].concat(childrenToStrings(tree.children, '')).join('\n'); } diff --git a/packages/kbn-pm/src/utils/validate_yarn_lock.ts b/packages/kbn-pm/src/utils/validate_dependencies.ts similarity index 77% rename from packages/kbn-pm/src/utils/validate_yarn_lock.ts rename to packages/kbn-pm/src/utils/validate_dependencies.ts index ec853a3a958fb..045d6332dcc29 100644 --- a/packages/kbn-pm/src/utils/validate_yarn_lock.ts +++ b/packages/kbn-pm/src/utils/validate_dependencies.ts @@ -20,14 +20,16 @@ // @ts-expect-error published types are useless import { stringify as stringifyLockfile } from '@yarnpkg/lockfile'; import dedent from 'dedent'; +import chalk from 'chalk'; import { writeFile } from './fs'; import { Kibana } from './kibana'; import { YarnLock } from './yarn_lock'; import { log } from './log'; import { Project } from './project'; +import { ITree, treeToString } from './projects_tree'; -export async function validateYarnLock(kbn: Kibana, yarnLock: YarnLock) { +export async function validateDependencies(kbn: Kibana, yarnLock: YarnLock) { // look through all of the packages in the yarn.lock file to see if // we have accidentally installed multiple lodash v4 versions const lodash4Versions = new Set(); @@ -157,5 +159,45 @@ export async function validateYarnLock(kbn: Kibana, yarnLock: YarnLock) { process.exit(1); } + // look for packages that have the the `kibana.devOnly` flag in their package.json + // and make sure they aren't included in the production dependencies of Kibana + const devOnlyProjectsInProduction = getDevOnlyProductionDepsTree(kbn, 'kibana'); + if (devOnlyProjectsInProduction) { + log.error(dedent` + Some of the packages in the production dependency chain for Kibana and X-Pack are + flagged with "kibana.devOnly" in their package.json. Please check changes made to + packages and their dependencies to ensure they don't end up in production. + + The devOnly dependencies that are being dependend on in production are: + + ${treeToString(devOnlyProjectsInProduction).split('\n').join('\n ')} + `); + + process.exit(1); + } + log.success('yarn.lock analysis completed without any issues'); } + +function getDevOnlyProductionDepsTree(kbn: Kibana, projectName: string) { + const project = kbn.getProject(projectName); + const childProjectNames = [ + ...Object.keys(project.productionDependencies).filter((name) => kbn.hasProject(name)), + ...(projectName === 'kibana' ? ['x-pack'] : []), + ]; + + const children = childProjectNames + .map((n) => getDevOnlyProductionDepsTree(kbn, n)) + .filter((t): t is ITree => !!t); + + if (!children.length && !project.isFlaggedAsDevOnly()) { + return; + } + + const tree: ITree = { + name: project.isFlaggedAsDevOnly() ? chalk.red.bold(projectName) : projectName, + children, + }; + + return tree; +} diff --git a/packages/kbn-release-notes/package.json b/packages/kbn-release-notes/package.json index 268530c22399a..e3306b7a54917 100644 --- a/packages/kbn-release-notes/package.json +++ b/packages/kbn-release-notes/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "license": "Apache-2.0", "main": "target/index.js", + "kibana": { + "devOnly": true + }, "scripts": { "kbn:bootstrap": "tsc", "kbn:watch": "tsc --watch" diff --git a/packages/kbn-release-notes/src/lib/get_note_from_description.test.ts b/packages/kbn-release-notes/src/lib/get_note_from_description.test.ts index 23dcb302f090d..22b9713b78332 100644 --- a/packages/kbn-release-notes/src/lib/get_note_from_description.test.ts +++ b/packages/kbn-release-notes/src/lib/get_note_from_description.test.ts @@ -35,7 +35,8 @@ it('extracts expected components from html', () => { ## Release Note: Checkout this feature - `) + `), + 'release note' ) ).toMatchInlineSnapshot(`"Checkout this feature"`); @@ -46,10 +47,11 @@ it('extracts expected components from html', () => { Fixes: #1234 - #### Release Note: + #### Dev docs: We fixed an issue - `) + `), + 'dev docs' ) ).toMatchInlineSnapshot(`"We fixed an issue"`); @@ -60,8 +62,9 @@ it('extracts expected components from html', () => { Fixes: #1234 - Release note: Checkout feature foo - `) + OTHER TITLE: Checkout feature foo + `), + 'other title' ) ).toMatchInlineSnapshot(`"Checkout feature foo"`); @@ -73,7 +76,8 @@ it('extracts expected components from html', () => { My PR description release note : bar - `) + `), + 'release note' ) ).toMatchInlineSnapshot(`"bar"`); }); diff --git a/packages/kbn-release-notes/src/lib/get_note_from_description.ts b/packages/kbn-release-notes/src/lib/get_note_from_description.ts index 57df203470a5a..0d9135c431e36 100644 --- a/packages/kbn-release-notes/src/lib/get_note_from_description.ts +++ b/packages/kbn-release-notes/src/lib/get_note_from_description.ts @@ -19,11 +19,12 @@ import cheerio from 'cheerio'; -export function getNoteFromDescription(descriptionHtml: string) { +export function getNoteFromDescription(descriptionHtml: string, header: string) { + const re = new RegExp(`^(\\s*${header.toLowerCase()}(?:s)?\\s*:?\\s*)`, 'i'); const $ = cheerio.load(descriptionHtml); for (const el of $('p,h1,h2,h3,h4,h5').toArray()) { const text = $(el).text(); - const match = text.match(/^(\s*release note(?:s)?\s*:?\s*)/i); + const match = text.match(re); if (!match) { continue; diff --git a/packages/kbn-release-notes/src/lib/pr_api.ts b/packages/kbn-release-notes/src/lib/pr_api.ts index 1f26aa7ad86c3..5fa3dfdba10ef 100644 --- a/packages/kbn-release-notes/src/lib/pr_api.ts +++ b/packages/kbn-release-notes/src/lib/pr_api.ts @@ -178,7 +178,9 @@ export class PrApi { versions: labels .map((l) => Version.fromLabel(l)) .filter((v): v is Version => v instanceof Version), - note: getNoteFromDescription(node.bodyHTML), + note: + getNoteFromDescription(node.bodyHTML, 'release note') || + getNoteFromDescription(node.bodyHTML, 'dev docs'), }; } diff --git a/packages/kbn-spec-to-console/package.json b/packages/kbn-spec-to-console/package.json index 557f38ec740fc..9684201c72384 100644 --- a/packages/kbn-spec-to-console/package.json +++ b/packages/kbn-spec-to-console/package.json @@ -12,6 +12,9 @@ }, "author": "", "license": "Apache-2.0", + "kibana": { + "devOnly": true + }, "bugs": { "url": "https://github.com/jbudz/spec-to-console/issues" }, diff --git a/packages/kbn-std/package.json b/packages/kbn-std/package.json index a931dd3f3154d..8a5e885c456cd 100644 --- a/packages/kbn-std/package.json +++ b/packages/kbn-std/package.json @@ -9,12 +9,12 @@ "build": "tsc", "kbn:bootstrap": "yarn build" }, + "dependencies": { + "lodash": "^4.17.20" + }, "devDependencies": { + "@kbn/utility-types": "1.0.0", "typescript": "4.0.2", "tsd": "^0.13.1" - }, - "dependencies": { - "@kbn/utility-types": "1.0.0", - "lodash": "^4.17.20" } } diff --git a/packages/kbn-std/src/index.ts b/packages/kbn-std/src/index.ts index d9d3ec4b0d52b..c111428017539 100644 --- a/packages/kbn-std/src/index.ts +++ b/packages/kbn-std/src/index.ts @@ -27,3 +27,4 @@ export { withTimeout } from './promise'; export { isRelativeUrl, modifyUrl, getUrlOrigin, URLMeaningfulParts } from './url'; export { unset } from './unset'; export { getFlattenedObject } from './get_flattened_object'; +export * from './rxjs_7'; diff --git a/packages/kbn-std/src/rxjs_7.test.ts b/packages/kbn-std/src/rxjs_7.test.ts new file mode 100644 index 0000000000000..ff1026e23b7ef --- /dev/null +++ b/packages/kbn-std/src/rxjs_7.test.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 * as Rx from 'rxjs'; + +import { firstValueFrom, lastValueFrom } from './rxjs_7'; + +// create an empty observable that completes with no notifications +// after a delay to ensure helpers aren't checking for the EMPTY constant +function empty() { + return new Rx.Observable((subscriber) => { + setTimeout(() => { + subscriber.complete(); + }, 0); + }); +} + +describe('firstValueFrom()', () => { + it('resolves to the first value from the observable', async () => { + await expect(firstValueFrom(Rx.of(1, 2, 3))).resolves.toBe(1); + }); + + it('rejects if the observable is empty', async () => { + await expect(firstValueFrom(empty())).rejects.toThrowErrorMatchingInlineSnapshot( + `"no elements in sequence"` + ); + }); + + it('unsubscribes from a source observable that emits synchronously', async () => { + const values = [1, 2, 3, 4]; + let unsubscribed = false; + const source = new Rx.Observable((subscriber) => { + while (!subscriber.closed && values.length) { + subscriber.next(values.shift()!); + } + unsubscribed = subscriber.closed; + subscriber.complete(); + }); + + await expect(firstValueFrom(source)).resolves.toMatchInlineSnapshot(`1`); + if (!unsubscribed) { + throw new Error('expected source to be unsubscribed'); + } + expect(values).toEqual([2, 3, 4]); + }); + + it('unsubscribes from the source observable after first async notification', async () => { + const values = [1, 2, 3, 4]; + let unsubscribed = false; + const source = new Rx.Observable((subscriber) => { + setTimeout(() => { + while (!subscriber.closed) { + subscriber.next(values.shift()!); + } + unsubscribed = subscriber.closed; + }); + }); + + await expect(firstValueFrom(source)).resolves.toMatchInlineSnapshot(`1`); + if (!unsubscribed) { + throw new Error('expected source to be unsubscribed'); + } + expect(values).toEqual([2, 3, 4]); + }); +}); + +describe('lastValueFrom()', () => { + it('resolves to the last value from the observable', async () => { + await expect(lastValueFrom(Rx.of(1, 2, 3))).resolves.toBe(3); + }); + + it('rejects if the observable is empty', async () => { + await expect(lastValueFrom(empty())).rejects.toThrowErrorMatchingInlineSnapshot( + `"no elements in sequence"` + ); + }); +}); diff --git a/src/plugins/vis_type_timelion/public/flot/index.js b/packages/kbn-std/src/rxjs_7.ts similarity index 68% rename from src/plugins/vis_type_timelion/public/flot/index.js rename to packages/kbn-std/src/rxjs_7.ts index a066fd3ab8607..cb10f9de880fd 100644 --- a/src/plugins/vis_type_timelion/public/flot/index.js +++ b/packages/kbn-std/src/rxjs_7.ts @@ -17,10 +17,14 @@ * under the License. */ -import './jquery.flot'; -import './jquery.flot.time'; -import './jquery.flot.symbol'; -import './jquery.flot.crosshair'; -import './jquery.flot.selection'; -import './jquery.flot.stack'; -import './jquery.flot.axislabels'; +import { Observable } from 'rxjs'; +import { first, last } from 'rxjs/operators'; + +export function firstValueFrom(source: Observable) { + // we can't use SafeSubscriber the same way that RxJS 7 does, so instead we + return source.pipe(first()).toPromise(); +} + +export function lastValueFrom(source: Observable) { + return source.pipe(last()).toPromise(); +} diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json index 58359159e950d..5c57f6893d0c8 100644 --- a/packages/kbn-storybook/package.json +++ b/packages/kbn-storybook/package.json @@ -4,6 +4,9 @@ "private": true, "license": "Apache-2.0", "main": "./target/index.js", + "kibana": { + "devOnly": true + }, "dependencies": { "@kbn/dev-utils": "1.0.0", "@storybook/addon-actions": "^6.0.16", diff --git a/packages/kbn-telemetry-tools/package.json b/packages/kbn-telemetry-tools/package.json index 4318cbcf2ec4e..cda2998901d56 100644 --- a/packages/kbn-telemetry-tools/package.json +++ b/packages/kbn-telemetry-tools/package.json @@ -4,6 +4,9 @@ "license": "Apache-2.0", "main": "./target/index.js", "private": true, + "kibana": { + "devOnly": true + }, "scripts": { "build": "babel src --out-dir target --delete-dir-on-start --extensions .ts --source-maps=inline", "kbn:bootstrap": "yarn build", diff --git a/packages/kbn-test-subj-selector/package.json b/packages/kbn-test-subj-selector/package.json index 82a26dc4807be..b823c68f9560b 100755 --- a/packages/kbn-test-subj-selector/package.json +++ b/packages/kbn-test-subj-selector/package.json @@ -5,5 +5,8 @@ "main": "index.js", "keywords": [], "author": "Spencer Alger ", - "license": "Apache-2.0" + "license": "Apache-2.0", + "kibana": { + "devOnly": true + } } diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 4e86ec4bd72e0..f5b34314052cc 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -9,6 +9,9 @@ "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" }, + "kibana": { + "devOnly": true + }, "devDependencies": { "@babel/cli": "^7.10.5", "@kbn/babel-preset": "1.0.0", diff --git a/packages/kbn-test/src/index.ts b/packages/kbn-test/src/index.ts index 8da289f252f83..bc487782a4a31 100644 --- a/packages/kbn-test/src/index.ts +++ b/packages/kbn-test/src/index.ts @@ -48,7 +48,7 @@ export { runFtrCli } from './functional_test_runner/cli'; export { runFailedTestsReporterCli } from './failed_tests_reporter'; -export { makeJunitReportPath } from './junit_report_path'; +export { getUniqueJunitReportPath } from './junit_report_path'; export { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; diff --git a/packages/kbn-test/src/junit_report_path.ts b/packages/kbn-test/src/junit_report_path.ts index 90405d7a89c02..8843204097e85 100644 --- a/packages/kbn-test/src/junit_report_path.ts +++ b/packages/kbn-test/src/junit_report_path.ts @@ -17,14 +17,24 @@ * under the License. */ -import { resolve } from 'path'; +import Fs from 'fs'; +import Path from 'path'; + import { CI_PARALLEL_PROCESS_PREFIX } from './ci_parallel_process_prefix'; -export function makeJunitReportPath(rootDirectory: string, reportName: string) { - return resolve( +export function getUniqueJunitReportPath( + rootDirectory: string, + reportName: string, + counter?: number +): string { + const path = Path.resolve( rootDirectory, 'target/junit', process.env.JOB || '.', - `TEST-${CI_PARALLEL_PROCESS_PREFIX}${reportName}.xml` + `TEST-${CI_PARALLEL_PROCESS_PREFIX}${reportName}${counter ? `-${counter}` : ''}.xml` ); + + return Fs.existsSync(path) + ? getUniqueJunitReportPath(rootDirectory, reportName, (counter ?? 0) + 1) + : path; } diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index 21d25311420ca..e56c5adeb4fe1 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@babel/core": "^7.11.6", - "@elastic/eui": "29.3.0", + "@elastic/eui": "29.3.1", "@kbn/babel-preset": "1.0.0", "@kbn/optimizer": "1.0.0", "babel-loader": "^8.0.6", diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 966fb65406ac6..4700479941eed 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -22,6 +22,7 @@ require('./polyfills'); // must load before angular export const Jquery = require('jquery'); window.$ = window.jQuery = Jquery; +require('./flot_charts'); // stateful deps export const KbnI18n = require('@kbn/i18n'); @@ -50,8 +51,9 @@ export const ElasticEui = require('@elastic/eui'); export const ElasticEuiLibServices = require('@elastic/eui/lib/services'); export const ElasticEuiLibServicesFormat = require('@elastic/eui/lib/services/format'); export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); +export const Theme = require('./theme.ts'); export const Lodash = require('lodash'); export const LodashFp = require('lodash/fp'); -import * as Theme from './theme.ts'; -export { Theme }; +// runtime deps which don't need to be copied across all bundles +export const TsLib = require('tslib'); diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/API.md b/packages/kbn-ui-shared-deps/flot_charts/API.md similarity index 100% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/API.md rename to packages/kbn-ui-shared-deps/flot_charts/API.md diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/index.js b/packages/kbn-ui-shared-deps/flot_charts/index.js similarity index 52% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/index.js rename to packages/kbn-ui-shared-deps/flot_charts/index.js index 613939256cfc9..6d9872d3ec524 100644 --- a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/index.js +++ b/packages/kbn-ui-shared-deps/flot_charts/index.js @@ -1,7 +1,20 @@ /* - * 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. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ /* @notice @@ -32,17 +45,15 @@ * THE SOFTWARE. */ -import $ from 'jquery'; -if (window) window.jQuery = $; -require('./jquery.flot'); -require('./jquery.flot.time'); -require('./jquery.flot.canvas'); -require('./jquery.flot.symbol'); -require('./jquery.flot.crosshair'); -require('./jquery.flot.selection'); -require('./jquery.flot.pie'); -require('./jquery.flot.stack'); -require('./jquery.flot.threshold'); -require('./jquery.flot.fillbetween'); -require('./jquery.flot.log'); -module.exports = $; +import './jquery_flot'; +import './jquery_flot_canvas'; +import './jquery_flot_time'; +import './jquery_flot_symbol'; +import './jquery_flot_crosshair'; +import './jquery_flot_selection'; +import './jquery_flot_pie'; +import './jquery_flot_stack'; +import './jquery_flot_threshold'; +import './jquery_flot_fillbetween'; +import './jquery_flot_log'; +import './jquery_flot_axislabels'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.colorhelpers.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_colorhelpers.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.colorhelpers.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_colorhelpers.js diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot.js similarity index 100% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot.js diff --git a/src/plugins/timelion/public/flot/jquery.flot.axislabels.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_axislabels.js similarity index 100% rename from src/plugins/timelion/public/flot/jquery.flot.axislabels.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_axislabels.js diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.canvas.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_canvas.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.canvas.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_canvas.js diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.categories.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_categories.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.categories.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_categories.js diff --git a/src/plugins/timelion/public/flot/jquery.flot.crosshair.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_crosshair.js similarity index 100% rename from src/plugins/timelion/public/flot/jquery.flot.crosshair.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_crosshair.js diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.errorbars.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_errorbars.js similarity index 100% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.errorbars.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_errorbars.js diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.fillbetween.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_fillbetween.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.fillbetween.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_fillbetween.js diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.image.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_image.js similarity index 100% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.image.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_image.js diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.log.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_log.js similarity index 100% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.log.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_log.js diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.navigate.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_navigate.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.navigate.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_navigate.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js new file mode 100644 index 0000000000000..c1301a0659bda --- /dev/null +++ b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js @@ -0,0 +1,896 @@ +/* Flot plugin for rendering pie charts. + +Copyright (c) 2007-2014 IOLA and Ole Laursen. +Licensed under the MIT license. + +The plugin assumes that each series has a single data value, and that each +value is a positive integer or zero. Negative numbers don't make sense for a +pie chart, and have unpredictable results. The values do NOT need to be +passed in as percentages; the plugin will calculate the total and per-slice +percentages internally. + +* Created by Brian Medendorp + +* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars + +The plugin supports these options: + + series: { + pie: { + show: true/false + radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto' + innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect + startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result + tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show) + offset: { + top: integer value to move the pie up or down + left: integer value to move the pie left or right, or 'auto' + }, + stroke: { + color: any hexadecimal color value (other formats may or may not work, so best to stick with something like '#FFF') + width: integer pixel width of the stroke + }, + label: { + show: true/false, or 'auto' + formatter: a user-defined function that modifies the text/style of the label text + radius: 0-1 for percentage of fullsize, or a specified pixel length + background: { + color: any hexadecimal color value (other formats may or may not work, so best to stick with something like '#000') + opacity: 0-1 + }, + threshold: 0-1 for the percentage value at which to hide labels (if they're too small) + }, + combine: { + threshold: 0-1 for the percentage value at which to combine slices (if they're too small) + color: any hexadecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined + label: any text value of what the combined slice should be labeled + } + highlight: { + opacity: 0-1 + } + } + } + +More detail and specific examples can be found in the included HTML file. + +*/ + +import { i18n } from '@kbn/i18n'; + +(function($) { + // Maximum redraw attempts when fitting labels within the plot + + var REDRAW_ATTEMPTS = 10; + + // Factor by which to shrink the pie when fitting labels within the plot + + var REDRAW_SHRINK = 0.95; + + function init(plot) { + let canvas = null; + let target = null; + let options = null; + let maxRadius = null; + let centerLeft = null; + let centerTop = null; + let processed = false; + let ctx = null; + + // interactive variables + + let highlights = []; + + // add hook to determine if pie plugin in enabled, and then perform necessary operations + + plot.hooks.processOptions.push(function (plot, options) { + if (options.series.pie.show) { + options.grid.show = false; + + // set labels.show + + if (options.series.pie.label.show === 'auto') { + if (options.legend.show) { + options.series.pie.label.show = false; + } else { + options.series.pie.label.show = true; + } + } + + // set radius + + if (options.series.pie.radius === 'auto') { + if (options.series.pie.label.show) { + options.series.pie.radius = 3 / 4; + } else { + options.series.pie.radius = 1; + } + } + + // ensure sane tilt + + if (options.series.pie.tilt > 1) { + options.series.pie.tilt = 1; + } else if (options.series.pie.tilt < 0) { + options.series.pie.tilt = 0; + } + } + }); + + plot.hooks.bindEvents.push(function (plot, eventHolder) { + const options = plot.getOptions(); + if (options.series.pie.show) { + if (options.grid.hoverable) { + eventHolder.unbind('mousemove').mousemove(onMouseMove); + } + + if (options.grid.clickable) { + eventHolder.unbind('click').click(onClick); + } + } + }); + + plot.hooks.processDatapoints.push(function (plot, series, data, datapoints) { + const options = plot.getOptions(); + if (options.series.pie.show) { + processDatapoints(plot, series, data, datapoints); + } + }); + + plot.hooks.drawOverlay.push(function (plot, octx) { + const options = plot.getOptions(); + if (options.series.pie.show) { + drawOverlay(plot, octx); + } + }); + + plot.hooks.draw.push(function (plot, newCtx) { + const options = plot.getOptions(); + if (options.series.pie.show) { + draw(plot, newCtx); + } + }); + + function processDatapoints(plot) { + if (!processed) { + processed = true; + canvas = plot.getCanvas(); + target = $(canvas).parent(); + options = plot.getOptions(); + plot.setData(combine(plot.getData())); + } + } + + function combine(data) { + let total = 0; + let combined = 0; + let numCombined = 0; + let color = options.series.pie.combine.color; + const newdata = []; + + // Fix up the raw data from Flot, ensuring the data is numeric + + for (let i = 0; i < data.length; ++i) { + let value = data[i].data; + + // If the data is an array, we'll assume that it's a standard + // Flot x-y pair, and are concerned only with the second value. + + // Note how we use the original array, rather than creating a + // new one; this is more efficient and preserves any extra data + // that the user may have stored in higher indexes. + + if (Array.isArray(value) && value.length === 1) { + value = value[0]; + } + + if (Array.isArray(value)) { + // Equivalent to $.isNumeric() but compatible with jQuery < 1.7 + if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) { + value[1] = +value[1]; + } else { + value[1] = 0; + } + } else if (!isNaN(parseFloat(value)) && isFinite(value)) { + value = [1, +value]; + } else { + value = [1, 0]; + } + + data[i].data = [value]; + } + + // Sum up all the slices, so we can calculate percentages for each + + for (let i = 0; i < data.length; ++i) { + total += data[i].data[0][1]; + } + + // Count the number of slices with percentages below the combine + // threshold; if it turns out to be just one, we won't combine. + + for (let i = 0; i < data.length; ++i) { + const value = data[i].data[0][1]; + if (value / total <= options.series.pie.combine.threshold) { + combined += value; + numCombined++; + if (!color) { + color = data[i].color; + } + } + } + + for (let i = 0; i < data.length; ++i) { + const value = data[i].data[0][1]; + if (numCombined < 2 || value / total > options.series.pie.combine.threshold) { + newdata.push( + $.extend(data[i], { + /* extend to allow keeping all other original data values + and using them e.g. in labelFormatter. */ + data: [[1, value]], + color: data[i].color, + label: data[i].label, + angle: (value * Math.PI * 2) / total, + percent: value / (total / 100), + }) + ); + } + } + + if (numCombined > 1) { + newdata.push({ + data: [[1, combined]], + color: color, + label: options.series.pie.combine.label, + angle: (combined * Math.PI * 2) / total, + percent: combined / (total / 100), + }); + } + + return newdata; + } + + function draw(plot, newCtx) { + if (!target) { + return; + } // if no series were passed + + const canvasWidth = plot.getPlaceholder().width(); + const canvasHeight = plot.getPlaceholder().height(); + const legendWidth = target.children().filter('.legend').children().width() || 0; + + ctx = newCtx; + + // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE! + + // When combining smaller slices into an 'other' slice, we need to + // add a new series. Since Flot gives plugins no way to modify the + // list of series, the pie plugin uses a hack where the first call + // to processDatapoints results in a call to setData with the new + // list of series, then subsequent processDatapoints do nothing. + + // The plugin-global 'processed' flag is used to control this hack; + // it starts out false, and is set to true after the first call to + // processDatapoints. + + // Unfortunately this turns future setData calls into no-ops; they + // call processDatapoints, the flag is true, and nothing happens. + + // To fix this we'll set the flag back to false here in draw, when + // all series have been processed, so the next sequence of calls to + // processDatapoints once again starts out with a slice-combine. + // This is really a hack; in 0.9 we need to give plugins a proper + // way to modify series before any processing begins. + + processed = false; + + // calculate maximum radius and center point + + maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2; + centerTop = canvasHeight / 2 + options.series.pie.offset.top; + centerLeft = canvasWidth / 2; + + if (options.series.pie.offset.left === 'auto') { + if (options.legend.position.match('w')) { + centerLeft += legendWidth / 2; + } else { + centerLeft -= legendWidth / 2; + } + + if (centerLeft < maxRadius) { + centerLeft = maxRadius; + } else if (centerLeft > canvasWidth - maxRadius) { + centerLeft = canvasWidth - maxRadius; + } + } else { + centerLeft += options.series.pie.offset.left; + } + + const slices = plot.getData(); + let attempts = 0; + + // Keep shrinking the pie's radius until drawPie returns true, + // indicating that all the labels fit, or we try too many times. + + do { + if (attempts > 0) { + maxRadius *= REDRAW_SHRINK; + } + + attempts += 1; + clear(); + if (options.series.pie.tilt <= 0.8) { + drawShadow(); + } + } while (!drawPie() && attempts < REDRAW_ATTEMPTS); + + if (attempts >= REDRAW_ATTEMPTS) { + clear(); + const errorMessage = i18n.translate('flot.pie.unableToDrawLabelsInsideCanvasErrorMessage', { + defaultMessage: 'Could not draw pie with labels contained inside canvas', + }); + target.prepend( + `
${errorMessage}
` + ); + } + + if (plot.setSeries && plot.insertLegend) { + plot.setSeries(slices); + plot.insertLegend(); + } + + // we're actually done at this point, just defining internal functions at this point + + function clear() { + ctx.clearRect(0, 0, canvasWidth, canvasHeight); + target.children().filter('.pieLabel, .pieLabelBackground').remove(); + } + + function drawShadow() { + const shadowLeft = options.series.pie.shadow.left; + const shadowTop = options.series.pie.shadow.top; + const edge = 10; + const alpha = options.series.pie.shadow.alpha; + let radius = + options.series.pie.radius > 1 + ? options.series.pie.radius + : maxRadius * options.series.pie.radius; + + if ( + radius >= canvasWidth / 2 - shadowLeft || + radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || + radius <= edge + ) { + return; + } // shadow would be outside canvas, so don't draw it + + ctx.save(); + ctx.translate(shadowLeft, shadowTop); + ctx.globalAlpha = alpha; + ctx.fillStyle = '#000'; + + // center and rotate to starting position + + ctx.translate(centerLeft, centerTop); + ctx.scale(1, options.series.pie.tilt); + + //radius -= edge; + + for (let i = 1; i <= edge; i++) { + ctx.beginPath(); + ctx.arc(0, 0, radius, 0, Math.PI * 2, false); + ctx.fill(); + radius -= i; + } + + ctx.restore(); + } + + function drawPie() { + const startAngle = Math.PI * options.series.pie.startAngle; + const radius = + options.series.pie.radius > 1 + ? options.series.pie.radius + : maxRadius * options.series.pie.radius; + + // center and rotate to starting position + + ctx.save(); + ctx.translate(centerLeft, centerTop); + ctx.scale(1, options.series.pie.tilt); + //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera + + // draw slices + + ctx.save(); + let currentAngle = startAngle; + for (let i = 0; i < slices.length; ++i) { + slices[i].startAngle = currentAngle; + drawSlice(slices[i].angle, slices[i].color, true); + } + ctx.restore(); + + // draw slice outlines + + if (options.series.pie.stroke.width > 0) { + ctx.save(); + ctx.lineWidth = options.series.pie.stroke.width; + currentAngle = startAngle; + for (let i = 0; i < slices.length; ++i) { + drawSlice(slices[i].angle, options.series.pie.stroke.color, false); + } + + ctx.restore(); + } + + // draw donut hole + + drawDonutHole(ctx); + + ctx.restore(); + + // Draw the labels, returning true if they fit within the plot + + if (options.series.pie.label.show) { + return drawLabels(); + } else { + return true; + } + + function drawSlice(angle, color, fill) { + if (angle <= 0 || isNaN(angle)) { + return; + } + + if (fill) { + ctx.fillStyle = color; + } else { + ctx.strokeStyle = color; + ctx.lineJoin = 'round'; + } + + ctx.beginPath(); + if (Math.abs(angle - Math.PI * 2) > 0.000000001) { + ctx.moveTo(0, 0); + } // Center of the pie + + //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera + ctx.arc(0, 0, radius, currentAngle, currentAngle + angle / 2, false); + ctx.arc(0, 0, radius, currentAngle + angle / 2, currentAngle + angle, false); + ctx.closePath(); + //ctx.rotate(angle); // This doesn't work properly in Opera + currentAngle += angle; + + if (fill) { + ctx.fill(); + } else { + ctx.stroke(); + } + } + + function drawLabels() { + let currentAngle = startAngle; + const radius = + options.series.pie.label.radius > 1 + ? options.series.pie.label.radius + : maxRadius * options.series.pie.label.radius; + + for (let i = 0; i < slices.length; ++i) { + if (slices[i].percent >= options.series.pie.label.threshold * 100) { + if (!drawLabel(slices[i], currentAngle, i)) { + return false; + } + } + + currentAngle += slices[i].angle; + } + + return true; + + function drawLabel(slice, startAngle, index) { + if (slice.data[0][1] === 0) { + return true; + } + + // format label text + + const lf = options.legend.labelFormatter; + let text; + const plf = options.series.pie.label.formatter; + + if (lf) { + text = lf(slice.label, slice); + } else { + text = slice.label; + } + + if (plf) { + text = plf(text, slice); + } + + const halfAngle = (startAngle + slice.angle + startAngle) / 2; + const x = centerLeft + Math.round(Math.cos(halfAngle) * radius); + const y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt; + + const html = + "" + + text + + ''; + target.append(html); + + const label = target.children('#pieLabel' + index); + const labelTop = y - label.height() / 2; + const labelLeft = x - label.width() / 2; + + label.css('top', labelTop); + label.css('left', labelLeft); + + // check to make sure that the label is not outside the canvas + + if ( + 0 - labelTop > 0 || + 0 - labelLeft > 0 || + canvasHeight - (labelTop + label.height()) < 0 || + canvasWidth - (labelLeft + label.width()) < 0 + ) { + return false; + } + + if (options.series.pie.label.background.opacity !== 0) { + // put in the transparent background separately to avoid blended labels and label boxes + + let c = options.series.pie.label.background.color; + + if (c == null) { + c = slice.color; + } + + const pos = 'top:' + labelTop + 'px;left:' + labelLeft + 'px;'; + $( + "
" + ) + .css('opacity', options.series.pie.label.background.opacity) + .insertBefore(label); + } + + return true; + } // end individual label function + } // end drawLabels function + } // end drawPie function + } // end draw function + + // Placed here because it needs to be accessed from multiple locations + + function drawDonutHole(layer) { + if (options.series.pie.innerRadius > 0) { + // subtract the center + + layer.save(); + const innerRadius = + options.series.pie.innerRadius > 1 + ? options.series.pie.innerRadius + : maxRadius * options.series.pie.innerRadius; + layer.globalCompositeOperation = 'destination-out'; // this does not work with excanvas, but it will fall back to using the stroke color + layer.beginPath(); + layer.fillStyle = options.series.pie.stroke.color; + layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); + layer.fill(); + layer.closePath(); + layer.restore(); + + // add inner stroke + // TODO: Canvas forked flot here! + if (options.series.pie.stroke.width > 0) { + layer.save(); + layer.beginPath(); + layer.strokeStyle = options.series.pie.stroke.color; + layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false); + layer.stroke(); + layer.closePath(); + layer.restore(); + } + + // TODO: add extra shadow inside hole (with a mask) if the pie is tilted. + } + } + + //-- Additional Interactive related functions -- + + function isPointInPoly(poly, pt) { + let c = false; + const l = poly.length; + let j = l - 1; + for (let i = -1; ++i < l; j = i) { + ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || + (poly[j][1] <= pt[1] && pt[1] < poly[i][1])) && + pt[0] < + ((poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1])) / (poly[j][1] - poly[i][1]) + + poly[i][0] && + (c = !c); + } + return c; + } + + function findNearbySlice(mouseX, mouseY) { + const slices = plot.getData(); + const options = plot.getOptions(); + const radius = + options.series.pie.radius > 1 + ? options.series.pie.radius + : maxRadius * options.series.pie.radius; + let x; + let y; + + for (let i = 0; i < slices.length; ++i) { + const s = slices[i]; + + if (s.pie.show) { + ctx.save(); + ctx.beginPath(); + ctx.moveTo(0, 0); // Center of the pie + //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here. + ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false); + ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false); + ctx.closePath(); + x = mouseX - centerLeft; + y = mouseY - centerTop; + + if (ctx.isPointInPath) { + if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) { + ctx.restore(); + return { + datapoint: [s.percent, s.data], + dataIndex: 0, + series: s, + seriesIndex: i, + }; + } + } else { + // excanvas for IE doesn;t support isPointInPath, this is a workaround. + + const p1X = radius * Math.cos(s.startAngle); + const p1Y = radius * Math.sin(s.startAngle); + const p2X = radius * Math.cos(s.startAngle + s.angle / 4); + const p2Y = radius * Math.sin(s.startAngle + s.angle / 4); + const p3X = radius * Math.cos(s.startAngle + s.angle / 2); + const p3Y = radius * Math.sin(s.startAngle + s.angle / 2); + const p4X = radius * Math.cos(s.startAngle + s.angle / 1.5); + const p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5); + const p5X = radius * Math.cos(s.startAngle + s.angle); + const p5Y = radius * Math.sin(s.startAngle + s.angle); + const arrPoly = [ + [0, 0], + [p1X, p1Y], + [p2X, p2Y], + [p3X, p3Y], + [p4X, p4Y], + [p5X, p5Y], + ]; + const arrPoint = [x, y]; + + // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt? + + if (isPointInPoly(arrPoly, arrPoint)) { + ctx.restore(); + return { + datapoint: [s.percent, s.data], + dataIndex: 0, + series: s, + seriesIndex: i, + }; + } + } + + ctx.restore(); + } + } + + return null; + } + + function onMouseMove(e) { + triggerClickHoverEvent('plothover', e); + } + + function onClick(e) { + triggerClickHoverEvent('plotclick', e); + } + + // trigger click or hover event (they send the same parameters so we share their code) + + function triggerClickHoverEvent(eventname, e) { + const offset = plot.offset(); + const canvasX = parseInt(e.pageX - offset.left, 10); + const canvasY = parseInt(e.pageY - offset.top, 10); + const item = findNearbySlice(canvasX, canvasY); + + if (options.grid.autoHighlight) { + // clear auto-highlights + + for (let i = 0; i < highlights.length; ++i) { + const h = highlights[i]; + if (h.auto === eventname && !(item && h.series === item.series)) { + unhighlight(h.series); + } + } + } + + // highlight the slice + + if (item) { + highlight(item.series, eventname); + } + + // trigger any hover bind events + + const pos = { pageX: e.pageX, pageY: e.pageY }; + target.trigger(eventname, [pos, item]); + } + + function highlight(s, auto) { + //if (typeof s == "number") { + // s = series[s]; + //} + + const i = indexOfHighlight(s); + + if (i === -1) { + highlights.push({ series: s, auto: auto }); + plot.triggerRedrawOverlay(); + } else if (!auto) { + highlights[i].auto = false; + } + } + + function unhighlight(s) { + if (s == null) { + highlights = []; + plot.triggerRedrawOverlay(); + } + + //if (typeof s == "number") { + // s = series[s]; + //} + + const i = indexOfHighlight(s); + + if (i !== -1) { + highlights.splice(i, 1); + plot.triggerRedrawOverlay(); + } + } + + function indexOfHighlight(s) { + for (let i = 0; i < highlights.length; ++i) { + const h = highlights[i]; + if (h.series === s) { + return i; + } + } + return -1; + } + + function drawOverlay(plot, octx) { + const options = plot.getOptions(); + + const radius = + options.series.pie.radius > 1 + ? options.series.pie.radius + : maxRadius * options.series.pie.radius; + + octx.save(); + octx.translate(centerLeft, centerTop); + octx.scale(1, options.series.pie.tilt); + + for (let i = 0; i < highlights.length; ++i) { + drawHighlight(highlights[i].series); + } + + drawDonutHole(octx); + + octx.restore(); + + function drawHighlight(series) { + if (series.angle <= 0 || isNaN(series.angle)) { + return; + } + + //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString(); + octx.fillStyle = 'rgba(255, 255, 255, ' + options.series.pie.highlight.opacity + ')'; // this is temporary until we have access to parseColor + octx.beginPath(); + if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) { + octx.moveTo(0, 0); + } // Center of the pie + + octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false); + octx.arc( + 0, + 0, + radius, + series.startAngle + series.angle / 2, + series.startAngle + series.angle, + false + ); + octx.closePath(); + octx.fill(); + } + } + } // end init (plugin body) + + // define pie specific options and their default values + + const options = { + series: { + pie: { + show: false, + radius: 'auto', // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value) + innerRadius: 0 /* for donut */, + startAngle: 3 / 2, + tilt: 1, + shadow: { + left: 5, // shadow left offset + top: 15, // shadow top offset + alpha: 0.02, // shadow alpha + }, + offset: { + top: 0, + left: 'auto', + }, + stroke: { + color: '#fff', + width: 1, + }, + label: { + show: 'auto', + formatter: function (label, slice) { + return ( + "
" + + label + + '
' + + Math.round(slice.percent) + + '%
' + ); + }, // formatter function + radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value) + background: { + color: null, + opacity: 0, + }, + threshold: 0, // percentage at which to hide the label (i.e. the slice is too narrow) + }, + combine: { + threshold: -1, // percentage at which to combine little slices into one larger slice + color: null, // color to give the new slice (auto-generated if null) + label: 'Other', // label to give the new slice + }, + highlight: { + //color: "#fff", // will add this functionality once parseColor is available + opacity: 0.5, + }, + }, + }, + }; + + $.plot.plugins.push({ + init: init, + options: options, + name: "pie", + version: "1.1" + }); + +})(jQuery); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.resize.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_resize.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.resize.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_resize.js diff --git a/src/plugins/timelion/public/flot/jquery.flot.selection.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_selection.js similarity index 100% rename from src/plugins/timelion/public/flot/jquery.flot.selection.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_selection.js diff --git a/src/plugins/timelion/public/flot/jquery.flot.stack.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_stack.js similarity index 100% rename from src/plugins/timelion/public/flot/jquery.flot.stack.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_stack.js diff --git a/src/plugins/timelion/public/flot/jquery.flot.symbol.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_symbol.js similarity index 100% rename from src/plugins/timelion/public/flot/jquery.flot.symbol.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_symbol.js diff --git a/x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.threshold.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_threshold.js similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts/jquery.flot.threshold.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_threshold.js diff --git a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.time.js b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js similarity index 92% rename from x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.time.js rename to packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js index 991e87d364e8a..767088d1410e2 100644 --- a/x-pack/plugins/monitoring/public/lib/jquery_flot/flot-charts/jquery.flot.time.js +++ b/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js @@ -49,47 +49,47 @@ import { i18n } from '@kbn/i18n'; if (monthNames == null) { monthNames = [ - i18n.translate('xpack.monitoring.janLabel', { + i18n.translate('flot.time.janLabel', { defaultMessage: 'Jan', - }), i18n.translate('xpack.monitoring.febLabel', { + }), i18n.translate('flot.time.febLabel', { defaultMessage: 'Feb', - }), i18n.translate('xpack.monitoring.marLabel', { + }), i18n.translate('flot.time.marLabel', { defaultMessage: 'Mar', - }), i18n.translate('xpack.monitoring.aprLabel', { + }), i18n.translate('flot.time.aprLabel', { defaultMessage: 'Apr', - }), i18n.translate('xpack.monitoring.mayLabel', { + }), i18n.translate('flot.time.mayLabel', { defaultMessage: 'May', - }), i18n.translate('xpack.monitoring.junLabel', { + }), i18n.translate('flot.time.junLabel', { defaultMessage: 'Jun', - }), i18n.translate('xpack.monitoring.julLabel', { + }), i18n.translate('flot.time.julLabel', { defaultMessage: 'Jul', - }), i18n.translate('xpack.monitoring.augLabel', { + }), i18n.translate('flot.time.augLabel', { defaultMessage: 'Aug', - }), i18n.translate('xpack.monitoring.sepLabel', { + }), i18n.translate('flot.time.sepLabel', { defaultMessage: 'Sep', - }), i18n.translate('xpack.monitoring.octLabel', { + }), i18n.translate('flot.time.octLabel', { defaultMessage: 'Oct', - }), i18n.translate('xpack.monitoring.novLabel', { + }), i18n.translate('flot.time.novLabel', { defaultMessage: 'Nov', - }), i18n.translate('xpack.monitoring.decLabel', { + }), i18n.translate('flot.time.decLabel', { defaultMessage: 'Dec', })]; } if (dayNames == null) { - dayNames = [i18n.translate('xpack.monitoring.sunLabel', { + dayNames = [i18n.translate('flot.time.sunLabel', { defaultMessage: 'Sun', - }), i18n.translate('xpack.monitoring.monLabel', { + }), i18n.translate('flot.time.monLabel', { defaultMessage: 'Mon', - }), i18n.translate('xpack.monitoring.tueLabel', { + }), i18n.translate('flot.time.tueLabel', { defaultMessage: 'Tue', - }), i18n.translate('xpack.monitoring.wedLabel', { + }), i18n.translate('flot.time.wedLabel', { defaultMessage: 'Wed', - }), i18n.translate('xpack.monitoring.thuLabel', { + }), i18n.translate('flot.time.thuLabel', { defaultMessage: 'Thu', - }), i18n.translate('xpack.monitoring.friLabel', { + }), i18n.translate('flot.time.friLabel', { defaultMessage: 'Fri', - }), i18n.translate('xpack.monitoring.satLabel', { + }), i18n.translate('flot.time.satLabel', { defaultMessage: 'Sat', })]; } diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index a403ae63a8f70..8f931fae4f337 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -63,5 +63,10 @@ exports.externals = { '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', lodash: '__kbnSharedDeps__.Lodash', 'lodash/fp': '__kbnSharedDeps__.LodashFp', + + /** + * runtime deps which don't need to be copied across all bundles + */ + tslib: '__kbnSharedDeps__.TsLib', }; exports.publicPathLoader = require.resolve('./public_path_loader'); diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index e5ebb874e58aa..de9e8a654456a 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "23.2.1", - "@elastic/eui": "29.3.0", + "@elastic/eui": "29.3.1", "@elastic/numeral": "^2.5.0", "@kbn/i18n": "1.0.0", "@kbn/monaco": "1.0.0", @@ -33,6 +33,7 @@ "rxjs": "^6.5.5", "styled-components": "^5.1.0", "symbol-observable": "^1.2.0", + "tslib": "^2.0.0", "whatwg-fetch": "^3.0.0" }, "devDependencies": { diff --git a/packages/kbn-utility-types/package.json b/packages/kbn-utility-types/package.json index d1d7a1c0397cf..6b531efcebace 100644 --- a/packages/kbn-utility-types/package.json +++ b/packages/kbn-utility-types/package.json @@ -5,6 +5,9 @@ "license": "Apache-2.0", "main": "target", "types": "target/index.d.ts", + "kibana": { + "devOnly": true + }, "scripts": { "build": "tsc", "kbn:bootstrap": "tsc", diff --git a/src/cli_keystore/add.js b/src/cli_keystore/add.js index 232392f34c63b..d88256da1aa59 100644 --- a/src/cli_keystore/add.js +++ b/src/cli_keystore/add.js @@ -59,7 +59,15 @@ export async function add(keystore, key, options = {}) { value = await question(`Enter value for ${key}`, { mask: '*' }); } - keystore.add(key, value.trim()); + const parsedValue = value.trim(); + let parsedJsonValue; + try { + parsedJsonValue = JSON.parse(parsedValue); + } catch { + // noop, only treat value as json if it parses as JSON + } + + keystore.add(key, parsedJsonValue ?? parsedValue); keystore.save(); } diff --git a/src/cli_keystore/add.test.js b/src/cli_keystore/add.test.js index f1adee8879bc2..ba381ca2f3e14 100644 --- a/src/cli_keystore/add.test.js +++ b/src/cli_keystore/add.test.js @@ -129,6 +129,17 @@ describe('Kibana keystore', () => { expect(keystore.data.foo).toEqual('bar'); }); + it('parses JSON values', async () => { + prompt.question.returns(Promise.resolve('["bar"]\n')); + + const keystore = new Keystore('/data/test.keystore'); + sandbox.stub(keystore, 'save'); + + await add(keystore, 'foo'); + + expect(keystore.data.foo).toEqual(['bar']); + }); + it('persists updated keystore', async () => { prompt.question.returns(Promise.resolve('bar\n')); diff --git a/src/core/public/apm_system.test.ts b/src/core/public/apm_system.test.ts new file mode 100644 index 0000000000000..f88cdd899ef81 --- /dev/null +++ b/src/core/public/apm_system.test.ts @@ -0,0 +1,176 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +jest.mock('@elastic/apm-rum'); +import { init, apm } from '@elastic/apm-rum'; +import { ApmSystem } from './apm_system'; + +const initMock = init as jest.Mocked; +const apmMock = apm as DeeplyMockedKeys; + +describe('ApmSystem', () => { + afterEach(() => { + jest.resetAllMocks(); + jest.resetAllMocks(); + }); + + describe('setup', () => { + it('does not init apm if no config provided', async () => { + const apmSystem = new ApmSystem(undefined); + await apmSystem.setup(); + expect(initMock).not.toHaveBeenCalled(); + }); + + it('calls init with configuration', async () => { + const apmSystem = new ApmSystem({ active: true }); + await apmSystem.setup(); + expect(initMock).toHaveBeenCalledWith({ active: true }); + }); + + it('adds globalLabels if provided', async () => { + const apmSystem = new ApmSystem({ active: true, globalLabels: { alpha: 'one' } }); + await apmSystem.setup(); + expect(apm.addLabels).toHaveBeenCalledWith({ alpha: 'one' }); + }); + + describe('http request normalization', () => { + let windowSpy: any; + + beforeEach(() => { + windowSpy = jest.spyOn(global as any, 'window', 'get').mockImplementation(() => ({ + location: { + protocol: 'http:', + hostname: 'mykibanadomain.com', + port: '5601', + }, + })); + }); + + afterEach(() => { + windowSpy.mockRestore(); + }); + + it('adds an observe function', async () => { + const apmSystem = new ApmSystem({ active: true }); + await apmSystem.setup(); + expect(apm.observe).toHaveBeenCalledWith('transaction:end', expect.any(Function)); + }); + + /** + * Utility function to wrap functions that mutate their input but don't return the mutated value. + * Makes expects easier below. + */ + const returnArg = (func: (input: T) => any): ((input: T) => T) => { + return (input) => { + func(input); + return input; + }; + }; + + it('removes the hostname, port, and protocol only when all match window.location', async () => { + const apmSystem = new ApmSystem({ active: true }); + await apmSystem.setup(); + const observer = apmMock.observe.mock.calls[0][1]; + const wrappedObserver = returnArg(observer); + + // Strips the hostname, protocol, and port from URLs that are on the same origin + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:5601/asdf/qwerty', + } as Transaction) + ).toEqual({ type: 'http-request', name: 'GET /asdf/qwerty' }); + + // Does not modify URLs that are not on the same origin + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET https://mykibanadomain.com:5601/asdf/qwerty', + } as Transaction) + ).toEqual({ + type: 'http-request', + name: 'GET https://mykibanadomain.com:5601/asdf/qwerty', + }); + + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:9200/asdf/qwerty', + } as Transaction) + ).toEqual({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:9200/asdf/qwerty', + }); + + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://myotherdomain.com:5601/asdf/qwerty', + } as Transaction) + ).toEqual({ + type: 'http-request', + name: 'GET http://myotherdomain.com:5601/asdf/qwerty', + }); + }); + + it('strips the basePath', async () => { + const apmSystem = new ApmSystem({ active: true }, '/alpha'); + await apmSystem.setup(); + const observer = apmMock.observe.mock.calls[0][1]; + const wrappedObserver = returnArg(observer); + + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:5601/alpha', + } as Transaction) + ).toEqual({ type: 'http-request', name: 'GET /' }); + + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:5601/alpha/', + } as Transaction) + ).toEqual({ type: 'http-request', name: 'GET /' }); + + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:5601/alpha/beta', + } as Transaction) + ).toEqual({ type: 'http-request', name: 'GET /beta' }); + + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET http://mykibanadomain.com:5601/alpha/beta/', + } as Transaction) + ).toEqual({ type: 'http-request', name: 'GET /beta/' }); + + // Works with relative URLs as well + expect( + wrappedObserver({ + type: 'http-request', + name: 'GET /alpha/beta/', + } as Transaction) + ).toEqual({ type: 'http-request', name: 'GET /beta/' }); + }); + }); + }); +}); diff --git a/src/core/public/apm_system.ts b/src/core/public/apm_system.ts index 5e4953b96dc5b..3b3c1da01a925 100644 --- a/src/core/public/apm_system.ts +++ b/src/core/public/apm_system.ts @@ -17,14 +17,17 @@ * under the License. */ +import type { ApmBase } from '@elastic/apm-rum'; +import { modifyUrl } from '@kbn/std'; +import type { InternalApplicationStart } from './application'; + +/** "GET protocol://hostname:port/pathname" */ +const HTTP_REQUEST_TRANSACTION_NAME_REGEX = /^(GET|POST|PUT|HEAD|PATCH|DELETE|OPTIONS|CONNECT|TRACE)\s(.*)$/; + /** * This is the entry point used to boot the frontend when serving a application * that lives in the Kibana Platform. - * - * Any changes to this file should be kept in sync with - * src/legacy/ui/ui_bundles/app_entry_template.js */ -import type { InternalApplicationStart } from './application'; interface ApmConfig { // AgentConfigOptions is not exported from @elastic/apm-rum @@ -42,7 +45,7 @@ export class ApmSystem { * `apmConfig` would be populated with relevant APM RUM agent * configuration if server is started with elastic.apm.* config. */ - constructor(private readonly apmConfig?: ApmConfig) { + constructor(private readonly apmConfig?: ApmConfig, private readonly basePath = '') { this.enabled = apmConfig != null && !!apmConfig.active; } @@ -54,6 +57,8 @@ export class ApmSystem { apm.addLabels(globalLabels); } + this.addHttpRequestNormalization(apm); + init(apmConfig); } @@ -73,4 +78,52 @@ export class ApmSystem { } }); } + + /** + * Adds an observer to the APM configuration for normalizing transactions of the 'http-request' type to remove the + * hostname, protocol, port, and base path. Allows for coorelating data cross different deployments. + */ + private addHttpRequestNormalization(apm: ApmBase) { + apm.observe('transaction:end', (t) => { + if (t.type !== 'http-request') { + return; + } + + /** Split URLs of the from "GET protocol://hostname:port/pathname" into method & hostname */ + const matches = t.name.match(HTTP_REQUEST_TRANSACTION_NAME_REGEX); + if (!matches) { + return; + } + + const [, method, originalUrl] = matches; + // Normalize the URL + const normalizedUrl = modifyUrl(originalUrl, (parts) => { + const isAbsolute = parts.hostname && parts.protocol && parts.port; + // If the request was to a different host, port, or protocol then don't change anything + if ( + isAbsolute && + (parts.hostname !== window.location.hostname || + parts.protocol !== window.location.protocol || + parts.port !== window.location.port) + ) { + return; + } + + // Strip the protocol, hostnname, port, and protocol slashes to normalize + parts.protocol = null; + parts.hostname = null; + parts.port = null; + parts.slashes = false; + + // Replace the basePath if present in the pathname + if (parts.pathname === this.basePath) { + parts.pathname = '/'; + } else if (parts.pathname?.startsWith(`${this.basePath}/`)) { + parts.pathname = parts.pathname?.slice(this.basePath.length); + } + }); + + t.name = `${method} ${normalizedUrl}`; + }); + } } diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 0d08f6f3007b0..4d54d4831698b 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -242,11 +242,17 @@ export class ApplicationService { appId, { path, state, replace = false }: NavigateToAppOptions = {} ) => { - if (await this.shouldNavigate(overlays)) { + const currentAppId = this.currentAppId$.value; + const navigatingToSameApp = currentAppId === appId; + const shouldNavigate = navigatingToSameApp ? true : await this.shouldNavigate(overlays); + + if (shouldNavigate) { if (path === undefined) { path = applications$.value.get(appId)?.defaultPath; } - this.appInternalStates.delete(this.currentAppId$.value!); + if (!navigatingToSameApp) { + this.appInternalStates.delete(this.currentAppId$.value!); + } this.navigate!(getAppUrl(availableMounters, appId, path), state, replace); this.currentAppId$.next(appId); } diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx index d28486928b7e2..82933576bc493 100644 --- a/src/core/public/application/integration_tests/application_service.test.tsx +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -258,6 +258,34 @@ describe('ApplicationService', () => { expect(history.entries.length).toEqual(2); expect(history.entries[1].pathname).toEqual('/app/app1'); }); + + it('does not trigger navigation check if navigating to the current app', async () => { + startDeps.overlays.openConfirm.mockResolvedValue(false); + + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: ({ onAppLeave }: AppMountParameters) => { + onAppLeave((actions) => actions.confirm('confirmation-message', 'confirmation-title')); + return () => undefined; + }, + }); + + const { navigateToApp, getComponent } = await service.start(startDeps); + + update = createRenderer(getComponent()); + + await act(async () => { + await navigate('/app/app1'); + await navigateToApp('app1', { path: '/internal-path' }); + }); + + expect(startDeps.overlays.openConfirm).not.toHaveBeenCalled(); + expect(history.entries.length).toEqual(3); + expect(history.entries[2].pathname).toEqual('/app/app1/internal-path'); + }); }); describe('registering action menus', () => { @@ -331,6 +359,48 @@ describe('ApplicationService', () => { expect(await getValue(currentActionMenu$)).toBe(mounter2); }); + it('does not update the observable value when navigating to the current app', async () => { + const { register } = service.setup(setupDeps); + + let initialMount = true; + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: async ({ setHeaderActionMenu }: AppMountParameters) => { + if (initialMount) { + setHeaderActionMenu(mounter1); + initialMount = false; + } + return () => undefined; + }, + }); + + const { navigateToApp, getComponent, currentActionMenu$ } = await service.start(startDeps); + update = createRenderer(getComponent()); + + let mountedMenuCount = 0; + currentActionMenu$.subscribe(() => { + mountedMenuCount++; + }); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + + await act(async () => { + await navigateToApp('app1'); + await flushPromises(); + }); + + expect(await getValue(currentActionMenu$)).toBe(mounter1); + + // there is an initial 'undefined' emission + expect(mountedMenuCount).toBe(2); + }); + it('updates the observable value to undefined when switching to an application without action menu', async () => { const { register } = service.setup(setupDeps); diff --git a/src/core/public/kbn_bootstrap.ts b/src/core/public/kbn_bootstrap.ts index a083196004cf4..4536826a4a267 100644 --- a/src/core/public/kbn_bootstrap.ts +++ b/src/core/public/kbn_bootstrap.ts @@ -28,7 +28,7 @@ export async function __kbnBootstrap__() { ); let i18nError: Error | undefined; - const apmSystem = new ApmSystem(injectedMetadata.vars.apmConfig); + const apmSystem = new ApmSystem(injectedMetadata.vars.apmConfig, injectedMetadata.basePath); await Promise.all([ // eslint-disable-next-line no-console diff --git a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts index 340f45a0a2c18..b7ffefe7005e1 100644 --- a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts +++ b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts @@ -24,7 +24,8 @@ const { startES } = kbnTestServer.createTestServers({ }); let esServer: kbnTestServer.TestElasticsearchUtils; -describe('default route provider', () => { +// FLAKY: https://github.com/elastic/kibana/issues/81072 +describe.skip('default route provider', () => { let root: Root; beforeAll(async () => { diff --git a/src/core/server/elasticsearch/client/client_config.test.ts b/src/core/server/elasticsearch/client/client_config.test.ts index e8083836d3c1e..2beb07e6da5bc 100644 --- a/src/core/server/elasticsearch/client/client_config.test.ts +++ b/src/core/server/elasticsearch/client/client_config.test.ts @@ -216,28 +216,14 @@ describe('parseClientOptions', () => { ); }); - it('adds auth to the nodes if both `username` and `password` are set', () => { - let options = parseClientOptions( + it('does not add auth to the nodes', () => { + const options = parseClientOptions( createConfig({ username: 'user', - hosts: ['http://node-A:9200'], - }), - false - ); - expect(options.nodes).toMatchInlineSnapshot(` - Array [ - Object { - "url": "http://node-a:9200/", - }, - ] - `); - - options = parseClientOptions( - createConfig({ password: 'pass', hosts: ['http://node-A:9200'], }), - false + true ); expect(options.nodes).toMatchInlineSnapshot(` Array [ @@ -246,22 +232,6 @@ describe('parseClientOptions', () => { }, ] `); - - options = parseClientOptions( - createConfig({ - username: 'user', - password: 'pass', - hosts: ['http://node-A:9200'], - }), - false - ); - expect(options.nodes).toMatchInlineSnapshot(` - Array [ - Object { - "url": "http://user:pass@node-a:9200/", - }, - ] - `); }); }); describe('when `scoped` is true', () => { diff --git a/src/core/server/elasticsearch/client/client_config.ts b/src/core/server/elasticsearch/client/client_config.ts index f24c0d86550b8..dab20edfc61ff 100644 --- a/src/core/server/elasticsearch/client/client_config.ts +++ b/src/core/server/elasticsearch/client/client_config.ts @@ -93,7 +93,7 @@ export function parseClientOptions( }; } - clientOptions.nodes = config.hosts.map((host) => convertHost(host, !scoped, config)); + clientOptions.nodes = config.hosts.map((host) => convertHost(host)); if (config.ssl) { clientOptions.ssl = generateSslConfig( @@ -140,18 +140,10 @@ const generateSslConfig = ( return ssl; }; -const convertHost = ( - host: string, - needAuth: boolean, - { username, password }: ElasticsearchClientConfig -): NodeOptions => { +const convertHost = (host: string): NodeOptions => { const url = new URL(host); const isHTTPS = url.protocol === 'https:'; url.port = url.port || (isHTTPS ? '443' : '80'); - if (needAuth && username && password) { - url.username = username; - url.password = password; - } return { url, diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 71900ab982f3d..b70680594151b 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -103,6 +103,10 @@ interface ListenerOptions { export function createServer(serverOptions: ServerOptions, listenerOptions: ListenerOptions) { const server = new Server(serverOptions); + // remove fix + test as soon as update node.js to v12.19 https://github.com/elastic/kibana/pull/61587 + server.listener.headersTimeout = + listenerOptions.keepaliveTimeout + 2 * server.listener.headersTimeout; + server.listener.keepAliveTimeout = listenerOptions.keepaliveTimeout; server.listener.setTimeout(listenerOptions.socketTimeout); server.listener.on('timeout', (socket) => { diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index 0170e94867c06..3d0eba6de632e 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -313,7 +313,6 @@ describe('KibanaRequest', () => { expect(resp3.body).toEqual({ requestId: 'gamma' }); }); }); - describe('request uuid', () => { it('generates a UUID', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index b4d91926f13f4..412396644648e 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -50,6 +50,8 @@ configService.atPath.mockReturnValue( allowFromAnyIp: true, ipAllowlist: [], }, + keepaliveTimeout: 120_000, + socketTimeout: 120_000, } as any) ); diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 3cf56c30faeda..af2b5c54c0c9a 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -134,6 +134,7 @@ kibana_vars=( tilemap.url timelion.enabled vega.enableExternalUrls + xpack.actions.proxyUrl xpack.apm.enabled xpack.apm.serviceMapEnabled xpack.apm.ui.enabled diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts index 86a02d74dea15..d6a4224d9fab0 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts @@ -36,7 +36,33 @@ function generator({ # set -euo pipefail - docker pull ${baseOSImage} + retry_docker_pull() { + image=$1 + attempt=0 + max_retries=5 + + while true + do + attempt=$((attempt+1)) + + if [ $attempt -gt $max_retries ] + then + echo "Docker pull retries exceeded, aborting." + exit 1 + fi + + if docker pull "$image" + then + echo "Docker pull successful." + break + else + echo "Docker pull unsuccessful, attempt '$attempt'." + fi + + done + } + + retry_docker_pull ${baseOSImage} echo "Building: kibana${imageFlavor}${ubiImageFlavor}-docker"; \\ docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} -f Dockerfile . || exit 1; diff --git a/src/dev/jest/integration_tests/junit_reporter.test.js b/src/dev/jest/integration_tests/junit_reporter.test.js index 3280482a54203..65a955e17bb18 100644 --- a/src/dev/jest/integration_tests/junit_reporter.test.js +++ b/src/dev/jest/integration_tests/junit_reporter.test.js @@ -24,13 +24,13 @@ import { readFileSync } from 'fs'; import del from 'del'; import execa from 'execa'; import xml2js from 'xml2js'; -import { makeJunitReportPath } from '@kbn/test'; +import { getUniqueJunitReportPath } from '@kbn/test'; const MINUTE = 1000 * 60; const ROOT_DIR = resolve(__dirname, '../../../../'); const FIXTURE_DIR = resolve(__dirname, '__fixtures__'); const TARGET_DIR = resolve(FIXTURE_DIR, 'target'); -const XML_PATH = makeJunitReportPath(FIXTURE_DIR, 'Jest Tests'); +const XML_PATH = getUniqueJunitReportPath(FIXTURE_DIR, 'Jest Tests'); afterAll(async () => { await del(TARGET_DIR); diff --git a/src/dev/jest/junit_reporter.js b/src/dev/jest/junit_reporter.js index 7d43a224452af..ba8eca424cee0 100644 --- a/src/dev/jest/junit_reporter.js +++ b/src/dev/jest/junit_reporter.js @@ -23,7 +23,7 @@ import { writeFileSync, mkdirSync } from 'fs'; import xmlBuilder from 'xmlbuilder'; import { escapeCdata } from '../xml'; -import { makeJunitReportPath } from '@kbn/test'; +import { getUniqueJunitReportPath } from '@kbn/test'; const ROOT_DIR = dirname(require.resolve('../../../package.json')); @@ -103,7 +103,7 @@ export default class JestJUnitReporter { }); }); - const reportPath = makeJunitReportPath(rootDirectory, reportName); + const reportPath = getUniqueJunitReportPath(rootDirectory, reportName); const reportXML = root.end(); mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); diff --git a/src/dev/mocha/__tests__/junit_report_generation.js b/src/dev/mocha/__tests__/junit_report_generation.js index 00a11432dd9e8..dc7d161eca5a3 100644 --- a/src/dev/mocha/__tests__/junit_report_generation.js +++ b/src/dev/mocha/__tests__/junit_report_generation.js @@ -25,13 +25,14 @@ import { parseString } from 'xml2js'; import del from 'del'; import Mocha from 'mocha'; import expect from '@kbn/expect'; -import { makeJunitReportPath } from '@kbn/test'; +import { getUniqueJunitReportPath } from '@kbn/test'; import { setupJUnitReportGeneration } from '../junit_report_generation'; const PROJECT_DIR = resolve(__dirname, 'fixtures/project'); const DURATION_REGEX = /^\d+\.\d{3}$/; const ISO_DATE_SEC_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/; +const XML_PATH = getUniqueJunitReportPath(PROJECT_DIR, 'test'); describe('dev/mocha/junit report generation', () => { afterEach(() => { @@ -50,9 +51,7 @@ describe('dev/mocha/junit report generation', () => { mocha.addFile(resolve(PROJECT_DIR, 'test.js')); await new Promise((resolve) => mocha.run(resolve)); - const report = await fcb((cb) => - parseString(readFileSync(makeJunitReportPath(PROJECT_DIR, 'test')), cb) - ); + const report = await fcb((cb) => parseString(readFileSync(XML_PATH), cb)); // test case results are wrapped in expect(report).to.eql({ diff --git a/src/dev/mocha/junit_report_generation.js b/src/dev/mocha/junit_report_generation.js index a2e4e7d570ba1..096d82778ca42 100644 --- a/src/dev/mocha/junit_report_generation.js +++ b/src/dev/mocha/junit_report_generation.js @@ -22,7 +22,7 @@ import { writeFileSync, mkdirSync } from 'fs'; import { inspect } from 'util'; import xmlBuilder from 'xmlbuilder'; -import { makeJunitReportPath } from '@kbn/test'; +import { getUniqueJunitReportPath } from '@kbn/test'; import { getSnapshotOfRunnableLogs } from './log_cache'; import { escapeCdata } from '../xml'; @@ -140,7 +140,7 @@ export function setupJUnitReportGeneration(runner, options = {}) { } }); - const reportPath = makeJunitReportPath(rootDirectory, reportName); + const reportPath = getUniqueJunitReportPath(rootDirectory, reportName); const reportXML = builder.end(); mkdirSync(dirname(reportPath), { recursive: true }); writeFileSync(reportPath, reportXML, 'utf8'); diff --git a/src/dev/notice/generate_notice_from_source.ts b/src/dev/notice/generate_notice_from_source.ts index 9f7eb9d9e1aa4..e362427682ec0 100644 --- a/src/dev/notice/generate_notice_from_source.ts +++ b/src/dev/notice/generate_notice_from_source.ts @@ -52,7 +52,7 @@ export async function generateNoticeFromSource({ productName, directory, log }: 'src/plugins/*/{node_modules,build,dist}/**', 'x-pack/{node_modules,build,dist,data}/**', 'x-pack/packages/*/{node_modules,build,dist}/**', - 'x-pack/plugins/*/{node_modules,build,dist}/**', + 'x-pack/plugins/**/{node_modules,build,dist}/**', '**/target/**', ], }; diff --git a/src/legacy/server/i18n/index.ts b/src/legacy/server/i18n/index.ts index cb86c3220bec1..61caefb2fb599 100644 --- a/src/legacy/server/i18n/index.ts +++ b/src/legacy/server/i18n/index.ts @@ -33,7 +33,7 @@ export async function i18nMixin(kbnServer: KbnServer, server: Server, config: Ki const translationPaths = await Promise.all([ getTranslationPaths({ cwd: fromRoot('.'), - glob: I18N_RC, + glob: `*/${I18N_RC}`, }), ...(config.get('plugins.paths') as string[]).map((cwd) => getTranslationPaths({ cwd, glob: I18N_RC }) diff --git a/src/legacy/server/keystore/keystore.test.js b/src/legacy/server/keystore/keystore.test.js index 0897ce55d086b..e35edd1859484 100644 --- a/src/legacy/server/keystore/keystore.test.js +++ b/src/legacy/server/keystore/keystore.test.js @@ -157,11 +157,13 @@ describe('Keystore', () => { it('adds a key/value pair', () => { const keystore = new Keystore('/data/unprotected.keystore'); keystore.add('a3', 'baz'); + keystore.add('a4', [1, 'a', 2, 'b']); expect(keystore.data).toEqual({ 'a1.b2.c3': 'foo', a2: 'bar', a3: 'baz', + a4: [1, 'a', 2, 'b'], }); }); }); diff --git a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx index 794168132abb2..e9fa2833c3db5 100644 --- a/src/plugins/advanced_settings/public/management_app/components/field/field.tsx +++ b/src/plugins/advanced_settings/public/management_app/components/field/field.tsx @@ -22,6 +22,7 @@ import classNames from 'classnames'; import 'brace/theme/textmate'; import 'brace/mode/markdown'; +import 'brace/mode/json'; import { EuiBadge, diff --git a/src/plugins/charts/public/services/colors/color_palette.ts b/src/plugins/charts/public/services/colors/color_palette.ts index e1c32fe68da12..df76edb1e30ed 100644 --- a/src/plugins/charts/public/services/colors/color_palette.ts +++ b/src/plugins/charts/public/services/colors/color_palette.ts @@ -58,7 +58,7 @@ export function createColorPalette(num: number): string[] { const seedLength = seedColors.length; _.times(num - seedLength, function (i) { - colors.push(hsl((fraction(i + seedLength + 1) * 360 + offset) % 360, 0.5, 0.5).hex()); + colors.push(hsl((fraction(i + seedLength + 1) * 360 + offset) % 360, 50, 50).hex()); }); return colors; diff --git a/src/plugins/charts/public/services/colors/colors_palette.test.ts b/src/plugins/charts/public/services/colors/colors_palette.test.ts index 02ff5a6056d54..273a36f6a43a6 100644 --- a/src/plugins/charts/public/services/colors/colors_palette.test.ts +++ b/src/plugins/charts/public/services/colors/colors_palette.test.ts @@ -90,4 +90,8 @@ describe('Color Palette', () => { it('should create new darker colors when input is greater than 72', () => { expect(createColorPalette(num3)[72]).not.toEqual(seedColors[0]); }); + + it('should create new colors and convert them correctly', () => { + expect(createColorPalette(num3)[72]).toEqual('#404ABF'); + }); }); diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index f0f8b981bcf8c..3325d193e56ed 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -300,7 +300,7 @@ export class DashboardPlugin const app: App = { id: DashboardConstants.DASHBOARDS_ID, title: 'Dashboard', - order: -1001, + order: 2500, euiIconType: 'logoKibana', defaultPath: `#${DashboardConstants.LANDING_PAGE_PATH}`, updater$: this.appStateUpdater, diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard.ts index 850b2470dd475..a85f67f5ba56a 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard.ts @@ -46,10 +46,10 @@ export const dashboardSavedObjectType: SavedObjectsType = { description: { type: 'text' }, hits: { type: 'integer', index: false, doc_values: false }, kibanaSavedObjectMeta: { - properties: { searchSourceJSON: { type: 'text', index: false, doc_values: false } }, + properties: { searchSourceJSON: { type: 'text', index: false } }, }, - optionsJSON: { type: 'text', index: false, doc_values: false }, - panelsJSON: { type: 'text', index: false, doc_values: false }, + optionsJSON: { type: 'text', index: false }, + panelsJSON: { type: 'text', index: false }, refreshInterval: { properties: { display: { type: 'keyword', index: false, doc_values: false }, diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts index 3c4fac81c2c7c..be7836de31246 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.test.ts @@ -91,6 +91,17 @@ describe('Field', function () { expect(fieldC.searchable).toEqual(false); }); + it('calculates visualizable', () => { + const field = getField({ type: 'unknown' }); + expect(field.visualizable).toEqual(false); + + const fieldB = getField({ type: 'conflict' }); + expect(fieldB.visualizable).toEqual(false); + + const fieldC = getField({ aggregatable: false, scripted: false }); + expect(fieldC.visualizable).toEqual(false); + }); + it('calculates aggregatable', () => { const field = getField({ aggregatable: true, scripted: false }); expect(field.aggregatable).toEqual(true); diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 808afc3449c2a..4a22508f7fef3 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -18,6 +18,7 @@ */ import { KbnFieldType, getKbnFieldType } from '../../kbn_field_types'; +import { KBN_FIELD_TYPES } from '../../kbn_field_types/types'; import { IFieldType } from './types'; import { FieldSpec, IndexPattern } from '../..'; @@ -129,7 +130,8 @@ export class IndexPatternField implements IFieldType { } public get visualizable() { - return this.aggregatable; + const notVisualizableFieldTypes: string[] = [KBN_FIELD_TYPES.UNKNOWN, KBN_FIELD_TYPES.CONFLICT]; + return this.aggregatable && !notVisualizableFieldTypes.includes(this.spec.type); } public toJSON() { diff --git a/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts b/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts index 6a2d6edd04692..dd1a9a7f689a9 100644 --- a/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts +++ b/src/plugins/data/common/kbn_field_types/kbn_field_types.test.ts @@ -66,6 +66,7 @@ describe('utils/kbn_field_types', () => { test('returns the kbnFieldType name that matches the esType', () => { expect(castEsToKbnFieldTypeName(ES_FIELD_TYPES.KEYWORD)).toBe('string'); expect(castEsToKbnFieldTypeName(ES_FIELD_TYPES.FLOAT)).toBe('number'); + expect(castEsToKbnFieldTypeName(ES_FIELD_TYPES.UNSIGNED_LONG)).toBe('number'); }); test('returns unknown for unknown es types', () => { diff --git a/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts b/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts index b93ebcbbca9c8..373cdfda30607 100644 --- a/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts +++ b/src/plugins/data/common/kbn_field_types/kbn_field_types_factory.ts @@ -48,6 +48,7 @@ export const createKbnFieldTypes = (): KbnFieldType[] => [ ES_FIELD_TYPES.DOUBLE, ES_FIELD_TYPES.INTEGER, ES_FIELD_TYPES.LONG, + ES_FIELD_TYPES.UNSIGNED_LONG, ES_FIELD_TYPES.SHORT, ES_FIELD_TYPES.BYTE, ES_FIELD_TYPES.TOKEN_COUNT, diff --git a/src/plugins/data/common/kbn_field_types/types.ts b/src/plugins/data/common/kbn_field_types/types.ts index acd7a36b01fb3..ba9fd3e70b315 100644 --- a/src/plugins/data/common/kbn_field_types/types.ts +++ b/src/plugins/data/common/kbn_field_types/types.ts @@ -52,6 +52,7 @@ export enum ES_FIELD_TYPES { INTEGER = 'integer', LONG = 'long', SHORT = 'short', + UNSIGNED_LONG = 'unsigned_long', NESTED = 'nested', BYTE = 'byte', diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index edfd10c017ca9..cdf3fe8a8072f 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -513,7 +513,9 @@ export enum ES_FIELD_TYPES { // (undocumented) TOKEN_COUNT = "token_count", // (undocumented) - _TYPE = "_type" + _TYPE = "_type", + // (undocumented) + UNSIGNED_LONG = "unsigned_long" } // Warning: (ae-missing-release-tag) "ES_SEARCH_STRATEGY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/data/public/query/query_string/query_string_manager.test.ts b/src/plugins/data/public/query/query_string/query_string_manager.test.ts new file mode 100644 index 0000000000000..aa1556480452a --- /dev/null +++ b/src/plugins/data/public/query/query_string/query_string_manager.test.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { QueryStringManager } from './query_string_manager'; +import { Storage } from '../../../../kibana_utils/public/storage'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { coreMock } from '../../../../../core/public/mocks'; +import { Query } from '../../../common/query'; + +describe('QueryStringManager', () => { + let service: QueryStringManager; + + beforeEach(() => { + service = new QueryStringManager( + new Storage(new StubBrowserStorage()), + coreMock.createSetup().uiSettings + ); + }); + + test('getUpdates$ is a cold emits only after query changes', () => { + const obs$ = service.getUpdates$(); + const emittedValues: Query[] = []; + obs$.subscribe((v) => { + emittedValues.push(v); + }); + expect(emittedValues).toHaveLength(0); + + const newQuery = { query: 'new query', language: 'kquery' }; + service.setQuery(newQuery); + expect(emittedValues).toHaveLength(1); + expect(emittedValues[0]).toEqual(newQuery); + + service.setQuery({ ...newQuery }); + expect(emittedValues).toHaveLength(1); + }); +}); diff --git a/src/plugins/data/public/query/query_string/query_string_manager.ts b/src/plugins/data/public/query/query_string/query_string_manager.ts index bd02830f4aed8..50732c99a62d9 100644 --- a/src/plugins/data/public/query/query_string/query_string_manager.ts +++ b/src/plugins/data/public/query/query_string/query_string_manager.ts @@ -17,8 +17,8 @@ * under the License. */ -import _ from 'lodash'; import { BehaviorSubject } from 'rxjs'; +import { skip } from 'rxjs/operators'; import { CoreStart } from 'kibana/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { Query, UI_SETTINGS } from '../../../common'; @@ -61,7 +61,7 @@ export class QueryStringManager { } public getUpdates$ = () => { - return this.query$.asObservable(); + return this.query$.asObservable().pipe(skip(1)); }; public getQuery = (): Query => { diff --git a/src/plugins/data/public/search/errors/timeout_error.tsx b/src/plugins/data/public/search/errors/timeout_error.tsx index a9ff0c3b38ae6..007689dd0269d 100644 --- a/src/plugins/data/public/search/errors/timeout_error.tsx +++ b/src/plugins/data/public/search/errors/timeout_error.tsx @@ -97,7 +97,12 @@ export class SearchTimeoutError extends KbnError { <> - this.onClick(application)} size="s"> + this.onClick(application)} + size="s" + data-test-subj="searchTimeoutError" + > {actionText} diff --git a/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx b/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx index 80e1a26163b72..19606cafc5c8a 100644 --- a/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx +++ b/src/plugins/data/public/ui/apply_filters/apply_filters_popover.tsx @@ -18,17 +18,12 @@ */ import React from 'react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import { IIndexPattern, Filter } from '../..'; type CancelFnType = () => void; type SubmitFnType = (filters: Filter[]) => void; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazyApplyFiltersPopoverContent = React.lazy(() => import('./apply_filter_popover_content')); diff --git a/src/plugins/data/public/ui/filter_bar/index.tsx b/src/plugins/data/public/ui/filter_bar/index.tsx index b4296bb6615d4..4d9ba69afd48e 100644 --- a/src/plugins/data/public/ui/filter_bar/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/index.tsx @@ -18,14 +18,9 @@ */ import React from 'react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import type { FilterLabelProps } from './filter_editor/lib/filter_label'; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazyFilterLabel = React.lazy(() => import('./filter_editor/lib/filter_label')); export const FilterLabel = (props: FilterLabelProps) => ( diff --git a/src/plugins/data/public/ui/index_pattern_select/index.tsx b/src/plugins/data/public/ui/index_pattern_select/index.tsx index 2912ec401b8b6..de4d6c9a75bfe 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index.tsx @@ -18,14 +18,9 @@ */ import React from 'react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import type { IndexPatternSelectProps } from './index_pattern_select'; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazyIndexPatternSelect = React.lazy(() => import('./index_pattern_select')); export const IndexPatternSelect = (props: IndexPatternSelectProps) => ( diff --git a/src/plugins/data/public/ui/query_string_input/index.tsx b/src/plugins/data/public/ui/query_string_input/index.tsx index 5bc5bd5097969..eb6641bf3661e 100644 --- a/src/plugins/data/public/ui/query_string_input/index.tsx +++ b/src/plugins/data/public/ui/query_string_input/index.tsx @@ -18,16 +18,11 @@ */ import React from 'react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import { withKibana } from '../../../../kibana_react/public'; import type { QueryBarTopRowProps } from './query_bar_top_row'; import type { QueryStringInputProps } from './query_string_input'; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazyQueryBarTopRow = React.lazy(() => import('./query_bar_top_row')); export const QueryBarTopRow = (props: QueryBarTopRowProps) => ( diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index e01fbedbe38de..7a44b924870f0 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -36,12 +36,14 @@ import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { Toast } from 'src/core/public'; import { IDataPluginServices, IIndexPattern, TimeRange, TimeHistoryContract, Query } from '../..'; -import { useKibana, toMountPoint } from '../../../../kibana_react/public'; -import { QueryStringInput } from './'; +import { useKibana, toMountPoint, withKibana } from '../../../../kibana_react/public'; +import QueryStringInputUI from './query_string_input'; import { doesKueryExpressionHaveLuceneSyntaxError, UI_SETTINGS } from '../../../common'; import { PersistedLog, getQueryLog } from '../../query'; import { NoDataPopover } from './no_data_popover'; +const QueryStringInput = withKibana(QueryStringInputUI); + // @internal export interface QueryBarTopRowProps { query?: Query; diff --git a/src/plugins/data/public/ui/search_bar/index.tsx b/src/plugins/data/public/ui/search_bar/index.tsx index d81ed7333655d..310542f4b12bd 100644 --- a/src/plugins/data/public/ui/search_bar/index.tsx +++ b/src/plugins/data/public/ui/search_bar/index.tsx @@ -19,15 +19,10 @@ import React from 'react'; import { injectI18n } from '@kbn/i18n/react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import { withKibana } from '../../../../kibana_react/public'; import type { SearchBarProps } from './search_bar'; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazySearchBar = React.lazy(() => import('./search_bar')); const WrappedSearchBar = (props: SearchBarProps) => ( diff --git a/src/plugins/data/public/ui/search_bar/search_bar.test.tsx b/src/plugins/data/public/ui/search_bar/search_bar.test.tsx index a2f6c7e82d1a4..74992f35fffc8 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.test.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.test.tsx @@ -18,10 +18,7 @@ */ import React from 'react'; -import { waitFor } from '@testing-library/dom'; -import { render } from '@testing-library/react'; - -import { SearchBar } from './'; +import SearchBar from './search_bar'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; @@ -29,6 +26,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { coreMock } from '../../../../../core/public/mocks'; const startMock = coreMock.createStart(); +import { mount } from 'enzyme'; import { IIndexPattern } from '../..'; const mockTimeHistory = { @@ -37,16 +35,14 @@ const mockTimeHistory = { }, }; -jest.mock('..', () => { +jest.mock('../filter_bar/filter_bar', () => { return { FilterBar: () =>
, }; }); -jest.mock('../query_string_input', () => { - return { - QueryBarTopRow: () =>
, - }; +jest.mock('../query_string_input/query_bar_top_row', () => { + return () =>
; }); const noop = jest.fn(); @@ -118,46 +114,41 @@ function wrapSearchBarInContext(testProps: any) { } describe('SearchBar', () => { - const SEARCH_BAR_TEST_ID = 'globalQueryBar'; const SEARCH_BAR_ROOT = '.globalQueryBar'; - const FILTER_BAR = '.globalFilterBar'; + const FILTER_BAR = '.filterBar'; const QUERY_BAR = '.queryBar'; beforeEach(() => { jest.clearAllMocks(); }); - it('Should render query bar when no options provided (in reality - timepicker)', async () => { - const { container, getByTestId } = render( + it('Should render query bar when no options provided (in reality - timepicker)', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(0); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(1); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(1); }); - it('Should render empty when timepicker is off and no options provided', async () => { - const { container, getByTestId } = render( + it('Should render empty when timepicker is off and no options provided', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], showDatePicker: false, }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(0); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(0); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(0); }); - it('Should render filter bar, when required fields are provided', async () => { - const { container, getByTestId } = render( + it('Should render filter bar, when required fields are provided', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], showDatePicker: false, @@ -166,15 +157,13 @@ describe('SearchBar', () => { }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(1); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(0); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(1); + expect(component.find(QUERY_BAR).length).toBe(0); }); - it('Should NOT render filter bar, if disabled', async () => { - const { container, getByTestId } = render( + it('Should NOT render filter bar, if disabled', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], showFilterBar: false, @@ -184,15 +173,13 @@ describe('SearchBar', () => { }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(0); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(0); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(0); }); - it('Should render query bar, when required fields are provided', async () => { - const { container, getByTestId } = render( + it('Should render query bar, when required fields are provided', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], screenTitle: 'test screen', @@ -201,15 +188,13 @@ describe('SearchBar', () => { }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(0); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(1); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(1); }); - it('Should NOT render query bar, if disabled', async () => { - const { container, getByTestId } = render( + it('Should NOT render query bar, if disabled', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], screenTitle: 'test screen', @@ -219,15 +204,13 @@ describe('SearchBar', () => { }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(0); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(0); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(0); + expect(component.find(QUERY_BAR).length).toBe(0); }); - it('Should render query bar and filter bar', async () => { - const { container, getByTestId } = render( + it('Should render query bar and filter bar', () => { + const component = mount( wrapSearchBarInContext({ indexPatterns: [mockIndexPattern], screenTitle: 'test screen', @@ -238,10 +221,8 @@ describe('SearchBar', () => { }) ); - await waitFor(() => getByTestId(SEARCH_BAR_TEST_ID)); - - expect(container.querySelectorAll(SEARCH_BAR_ROOT).length).toBe(1); - expect(container.querySelectorAll(FILTER_BAR).length).toBe(1); - expect(container.querySelectorAll(QUERY_BAR).length).toBe(1); + expect(component.find(SEARCH_BAR_ROOT).length).toBe(1); + expect(component.find(FILTER_BAR).length).toBe(1); + expect(component.find(QUERY_BAR).length).toBe(1); }); }); diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 95651ac9ed8b3..daa6fa0dd80ab 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -26,7 +26,7 @@ import { get, isEqual } from 'lodash'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; -import { QueryBarTopRow } from '../query_string_input'; +import QueryBarTopRow from '../query_string_input/query_bar_top_row'; import { SavedQueryAttributes, TimeHistoryContract, SavedQuery } from '../../query'; import { IDataPluginServices } from '../../types'; import { TimeRange, Query, Filter, IIndexPattern } from '../../../common'; diff --git a/src/plugins/data/public/ui/shard_failure_modal/index.tsx b/src/plugins/data/public/ui/shard_failure_modal/index.tsx index cea882deff365..2ac470573c422 100644 --- a/src/plugins/data/public/ui/shard_failure_modal/index.tsx +++ b/src/plugins/data/public/ui/shard_failure_modal/index.tsx @@ -18,14 +18,9 @@ */ import React from 'react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import type { ShardFailureOpenModalButtonProps } from './shard_failure_open_modal_button'; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazyShardFailureOpenModalButton = React.lazy( () => import('./shard_failure_open_modal_button') diff --git a/src/plugins/data/public/ui/typeahead/index.tsx b/src/plugins/data/public/ui/typeahead/index.tsx index aa3c2d71300df..58547cd2ccbec 100644 --- a/src/plugins/data/public/ui/typeahead/index.tsx +++ b/src/plugins/data/public/ui/typeahead/index.tsx @@ -18,14 +18,9 @@ */ import React from 'react'; -import { EuiLoadingContent, EuiDelayRender } from '@elastic/eui'; import type { SuggestionsComponentProps } from './suggestions_component'; -const Fallback = () => ( - - - -); +const Fallback = () =>
; const LazySuggestionsComponent = React.lazy(() => import('./suggestions_component')); export const SuggestionsComponent = (props: SuggestionsComponentProps) => ( diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 4c56aa70638d0..e4518ed9fd36e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -210,7 +210,9 @@ export enum ES_FIELD_TYPES { // (undocumented) TOKEN_COUNT = "token_count", // (undocumented) - _TYPE = "_type" + _TYPE = "_type", + // (undocumented) + UNSIGNED_LONG = "unsigned_long" } // Warning: (ae-missing-release-tag) "ES_SEARCH_STRATEGY" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 078a047324113..ce2b33774d06a 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -193,7 +193,7 @@ app.directive('discoverApp', function () { function discoverController($element, $route, $scope, $timeout, $window, Promise, uiCapabilities) { const { isDefault: isDefaultType } = indexPatternsUtils; const subscriptions = new Subscription(); - const $fetchObservable = new Subject(); + const refetch$ = new Subject(); let inspectorRequest; const savedSearch = $route.current.locals.savedObjects.savedSearch; $scope.searchSource = savedSearch.searchSource; @@ -267,7 +267,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise ); if (changes.length) { - $fetchObservable.next(); + refetch$.next(); } }); } @@ -633,12 +633,18 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise const init = _.once(() => { $scope.updateDataSource().then(async () => { - const searchBarChanges = merge(data.query.state$, $fetchObservable).pipe(debounceTime(100)); + const fetch$ = merge( + refetch$, + filterManager.getFetches$(), + timefilter.getFetch$(), + timefilter.getAutoRefreshFetch$(), + data.query.queryString.getUpdates$() + ).pipe(debounceTime(100)); subscriptions.add( subscribeWithScope( $scope, - searchBarChanges, + fetch$, { next: $scope.fetch, }, @@ -717,7 +723,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise init.complete = true; if (shouldSearchOnPageLoad()) { - $fetchObservable.next(); + refetch$.next(); } }); }); @@ -812,7 +818,7 @@ function discoverController($element, $route, $scope, $timeout, $window, Promise $scope.handleRefresh = function (_payload, isUpdate) { if (isUpdate === false) { - $fetchObservable.next(); + refetch$.next(); } }; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx new file mode 100644 index 0000000000000..2cf626d182eeb --- /dev/null +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.test.tsx @@ -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 React from 'react'; +import { findTestSubject } from '@elastic/eui/lib/test'; +// @ts-ignore +import stubbedLogstashFields from 'fixtures/logstash_fields'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { DiscoverFieldDetails } from './discover_field_details'; +import { coreMock } from '../../../../../../core/public/mocks'; +import { IndexPatternField } from '../../../../../data/public'; +import { getStubIndexPattern } from '../../../../../data/public/test_utils'; + +const indexPattern = getStubIndexPattern( + 'logstash-*', + (cfg: any) => cfg, + 'time', + stubbedLogstashFields(), + coreMock.createSetup() +); + +describe('discover sidebar field details', function () { + const defaultProps = { + indexPattern, + details: { buckets: [], error: '', exists: 1, total: true, columns: [] }, + onAddFilter: jest.fn(), + }; + + function mountComponent(field: IndexPatternField) { + const compProps = { ...defaultProps, field }; + return mountWithIntl(); + } + + it('should enable the visualize link for a number field', function () { + const visualizableField = new IndexPatternField( + { + name: 'bytes', + type: 'number', + esTypes: ['long'], + count: 10, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'bytes' + ); + const comp = mountComponent(visualizableField); + expect(findTestSubject(comp, 'fieldVisualize-bytes')).toBeTruthy(); + }); + + it('should disable the visualize link for an _id field', function () { + const conflictField = new IndexPatternField( + { + name: '_id', + type: 'string', + esTypes: ['_id'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'test' + ); + const comp = mountComponent(conflictField); + expect(findTestSubject(comp, 'fieldVisualize-_id')).toEqual({}); + }); + + it('should disable the visualize link for an unknown field', function () { + const unknownField = new IndexPatternField( + { + name: 'test', + type: 'unknown', + esTypes: ['double'], + count: 0, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + 'test' + ); + const comp = mountComponent(unknownField); + expect(findTestSubject(comp, 'fieldVisualize-test')).toEqual({}); + }); +}); diff --git a/src/plugins/discover/public/plugin.ts b/src/plugins/discover/public/plugin.ts index b81aa1f819a2f..b1bbc89b62d9d 100644 --- a/src/plugins/discover/public/plugin.ts +++ b/src/plugins/discover/public/plugin.ts @@ -240,7 +240,7 @@ export class DiscoverPlugin id: 'discover', title: 'Discover', updater$: this.appStateUpdater.asObservable(), - order: -1004, + order: 1000, euiIconType: 'logoKibana', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts index c13550e543ab6..a6e42f956a025 100644 --- a/src/plugins/discover/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -48,7 +48,7 @@ export const searchSavedObjectType: SavedObjectsType = { hits: { type: 'integer', index: false, doc_values: false }, kibanaSavedObjectMeta: { properties: { - searchSourceJSON: { type: 'text', index: false, doc_values: false }, + searchSourceJSON: { type: 'text', index: false }, }, }, sort: { type: 'keyword', index: false, doc_values: false }, diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_error_label.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_error_label.tsx index 1e4604af9dc09..fa4d7f466caee 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_error_label.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_error_label.tsx @@ -40,8 +40,10 @@ export function EmbeddableErrorLabel(props: Props) { return (
- - {labelText} + + + {labelText} +
diff --git a/src/plugins/expressions/common/util/create_error.ts b/src/plugins/expressions/common/util/create_error.ts index 9bdab74efd6f9..bdb3ce8e34e01 100644 --- a/src/plugins/expressions/common/util/create_error.ts +++ b/src/plugins/expressions/common/util/create_error.ts @@ -21,7 +21,9 @@ import { ExpressionValueError } from '../../common'; type ErrorLike = Partial>; -export const createError = (err: string | Error | ErrorLike): ExpressionValueError => ({ +export const createError = ( + err: string | Error | (ErrorLike & { original?: Error }) +): ExpressionValueError => ({ type: 'error', error: { stack: @@ -32,6 +34,11 @@ export const createError = (err: string | Error | ErrorLike): ExpressionValueErr : undefined, message: typeof err === 'string' ? err : String(err.message), name: typeof err === 'object' ? err.name || 'Error' : 'Error', - original: err instanceof Error ? err : undefined, + original: + err instanceof Error + ? err + : typeof err === 'object' && 'original' in err && err.original instanceof Error + ? err.original + : undefined, }, }); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 12476c70044b5..99d170c96666d 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -35,7 +35,10 @@ export interface ReactExpressionRendererProps extends IExpressionLoaderParams { className?: string; dataAttrs?: string[]; expression: string | ExpressionAstExpression; - renderError?: (error?: string | null) => React.ReactElement | React.ReactElement[]; + renderError?: ( + message?: string | null, + error?: ExpressionRenderError | null + ) => React.ReactElement | React.ReactElement[]; padding?: 'xs' | 's' | 'm' | 'l' | 'xl'; onEvent?: (event: ExpressionRendererEvent) => void; /** @@ -186,7 +189,10 @@ export const ReactExpressionRenderer = ({
{state.isEmpty && } {state.isLoading && } - {!state.isLoading && state.error && renderError && renderError(state.error.message)} + {!state.isLoading && + state.error && + renderError && + renderError(state.error.message, state.error)}
orderA - orderB; interface Props { @@ -38,7 +38,9 @@ interface Props { export const SolutionsSection: FC = ({ addBasePath, solutions, directories }) => { // Separate Kibana from other solutions const kibana = solutions.find(({ id }) => id === 'kibana'); - const kibanaApps = directories.filter(({ solutionId }) => solutionId === 'kibana'); + const kibanaApps = directories + .filter(({ solutionId }) => solutionId === 'kibana') + .sort(sortByOrder); solutions = solutions.sort(sortByOrder).filter(({ id }) => id !== 'kibana'); return ( diff --git a/src/plugins/home/public/assets/auditd_logs/screenshot.png b/src/plugins/home/public/assets/auditd_logs/screenshot.png new file mode 100644 index 0000000000000..732afa18dc11c Binary files /dev/null and b/src/plugins/home/public/assets/auditd_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/coredns_logs/screenshot.png b/src/plugins/home/public/assets/coredns_logs/screenshot.png new file mode 100644 index 0000000000000..70921fa9bafb2 Binary files /dev/null and b/src/plugins/home/public/assets/coredns_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/crowdstrike_logs/screenshot.png b/src/plugins/home/public/assets/crowdstrike_logs/screenshot.png new file mode 100644 index 0000000000000..b74edfe2293f9 Binary files /dev/null and b/src/plugins/home/public/assets/crowdstrike_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/googlecloud_logs/screenshot.png b/src/plugins/home/public/assets/googlecloud_logs/screenshot.png new file mode 100644 index 0000000000000..4f68932e9f709 Binary files /dev/null and b/src/plugins/home/public/assets/googlecloud_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/haproxy_logs/screenshot.png b/src/plugins/home/public/assets/haproxy_logs/screenshot.png new file mode 100644 index 0000000000000..85a24bf01f3aa Binary files /dev/null and b/src/plugins/home/public/assets/haproxy_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/icinga_logs/screenshot.png b/src/plugins/home/public/assets/icinga_logs/screenshot.png new file mode 100644 index 0000000000000..013b20fcf166e Binary files /dev/null and b/src/plugins/home/public/assets/icinga_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/logos/barracuda.svg b/src/plugins/home/public/assets/logos/barracuda.svg new file mode 100644 index 0000000000000..555cdd6f8a32b --- /dev/null +++ b/src/plugins/home/public/assets/logos/barracuda.svg @@ -0,0 +1,100 @@ + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/checkpoint.svg b/src/plugins/home/public/assets/logos/checkpoint.svg new file mode 100644 index 0000000000000..e71866e78c293 --- /dev/null +++ b/src/plugins/home/public/assets/logos/checkpoint.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/crowdstrike.svg b/src/plugins/home/public/assets/logos/crowdstrike.svg new file mode 100644 index 0000000000000..1b2195a2244f9 --- /dev/null +++ b/src/plugins/home/public/assets/logos/crowdstrike.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/cylance.svg b/src/plugins/home/public/assets/logos/cylance.svg new file mode 100644 index 0000000000000..ccd6004d19e76 --- /dev/null +++ b/src/plugins/home/public/assets/logos/cylance.svg @@ -0,0 +1,82 @@ + + + + +Cylance_BB_Logo_RGB_Vert_Black + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/f5.svg b/src/plugins/home/public/assets/logos/f5.svg new file mode 100644 index 0000000000000..d985bde96291f --- /dev/null +++ b/src/plugins/home/public/assets/logos/f5.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/fortinet.svg b/src/plugins/home/public/assets/logos/fortinet.svg new file mode 100644 index 0000000000000..d6a8448f320bc --- /dev/null +++ b/src/plugins/home/public/assets/logos/fortinet.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/icinga.svg b/src/plugins/home/public/assets/logos/icinga.svg new file mode 100644 index 0000000000000..88161d61ca75e --- /dev/null +++ b/src/plugins/home/public/assets/logos/icinga.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/infoblox.svg b/src/plugins/home/public/assets/logos/infoblox.svg new file mode 100644 index 0000000000000..57b4d23b16812 --- /dev/null +++ b/src/plugins/home/public/assets/logos/infoblox.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/juniper.svg b/src/plugins/home/public/assets/logos/juniper.svg new file mode 100644 index 0000000000000..8802414a5aafe --- /dev/null +++ b/src/plugins/home/public/assets/logos/juniper.svg @@ -0,0 +1,72 @@ + +image/svg+xml \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/linux.svg b/src/plugins/home/public/assets/logos/linux.svg new file mode 100644 index 0000000000000..c0a92e0c0f404 --- /dev/null +++ b/src/plugins/home/public/assets/logos/linux.svg @@ -0,0 +1,1532 @@ + + + + Tux + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Tux + 20 June 2012 + + + Garrett LeSage + + + + + + Larry Ewing, the creator of the original Tux graphic + + + + + tux + Linux + penguin + logo + + + + + Larry Ewing, Garrett LeSage + + + https://github.com/garrett/Tux + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/microsoft.svg b/src/plugins/home/public/assets/logos/microsoft.svg new file mode 100644 index 0000000000000..5334aa7ca6864 --- /dev/null +++ b/src/plugins/home/public/assets/logos/microsoft.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/misp.svg b/src/plugins/home/public/assets/logos/misp.svg new file mode 100644 index 0000000000000..1cc61eda0b608 --- /dev/null +++ b/src/plugins/home/public/assets/logos/misp.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + Threat Sharing + + + + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/netscout.svg b/src/plugins/home/public/assets/logos/netscout.svg new file mode 100644 index 0000000000000..cbd25cd92594a --- /dev/null +++ b/src/plugins/home/public/assets/logos/netscout.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/plugins/home/public/assets/logos/o365.svg b/src/plugins/home/public/assets/logos/o365.svg new file mode 100644 index 0000000000000..3763f267ffc7a --- /dev/null +++ b/src/plugins/home/public/assets/logos/o365.svg @@ -0,0 +1,16 @@ + + + logo-integrations-Desktop HD Copy 2 + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/okta.svg b/src/plugins/home/public/assets/logos/okta.svg new file mode 100644 index 0000000000000..d806cb7dc6451 --- /dev/null +++ b/src/plugins/home/public/assets/logos/okta.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/osquery.svg b/src/plugins/home/public/assets/logos/osquery.svg new file mode 100755 index 0000000000000..c2bf733d35931 --- /dev/null +++ b/src/plugins/home/public/assets/logos/osquery.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/paloalto.svg b/src/plugins/home/public/assets/logos/paloalto.svg new file mode 100644 index 0000000000000..8c8e71ae0d9fc --- /dev/null +++ b/src/plugins/home/public/assets/logos/paloalto.svg @@ -0,0 +1,29 @@ + + + logo-integrations-Desktop HD + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/rabbitmq.svg b/src/plugins/home/public/assets/logos/rabbitmq.svg new file mode 100644 index 0000000000000..dabd2a5744cb4 --- /dev/null +++ b/src/plugins/home/public/assets/logos/rabbitmq.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/plugins/home/public/assets/logos/radware.svg b/src/plugins/home/public/assets/logos/radware.svg new file mode 100644 index 0000000000000..6252efef77624 --- /dev/null +++ b/src/plugins/home/public/assets/logos/radware.svg @@ -0,0 +1,66 @@ + +image/svg+xml \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/sonicwall.svg b/src/plugins/home/public/assets/logos/sonicwall.svg new file mode 100644 index 0000000000000..fb1aded68a29e --- /dev/null +++ b/src/plugins/home/public/assets/logos/sonicwall.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/sophos.svg b/src/plugins/home/public/assets/logos/sophos.svg new file mode 100644 index 0000000000000..1f2cfc3a7f036 --- /dev/null +++ b/src/plugins/home/public/assets/logos/sophos.svg @@ -0,0 +1,69 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/plugins/home/public/assets/logos/tomcat.svg b/src/plugins/home/public/assets/logos/tomcat.svg new file mode 100644 index 0000000000000..410a468872e17 --- /dev/null +++ b/src/plugins/home/public/assets/logos/tomcat.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/plugins/home/public/assets/logos/zscaler.svg b/src/plugins/home/public/assets/logos/zscaler.svg new file mode 100644 index 0000000000000..b8a21a2fa6eed --- /dev/null +++ b/src/plugins/home/public/assets/logos/zscaler.svg @@ -0,0 +1 @@ +Zscaler-Logo-TM-Blue-RGB-May2019 \ No newline at end of file diff --git a/src/plugins/home/public/assets/microsoft_logs/screenshot.png b/src/plugins/home/public/assets/microsoft_logs/screenshot.png new file mode 100644 index 0000000000000..7df250e2ae885 Binary files /dev/null and b/src/plugins/home/public/assets/microsoft_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/misp_logs/screenshot.png b/src/plugins/home/public/assets/misp_logs/screenshot.png new file mode 100644 index 0000000000000..a02068ddf3038 Binary files /dev/null and b/src/plugins/home/public/assets/misp_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/mongodb_logs/screenshot.png b/src/plugins/home/public/assets/mongodb_logs/screenshot.png new file mode 100644 index 0000000000000..c77c37d5ce05b Binary files /dev/null and b/src/plugins/home/public/assets/mongodb_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/o365_logs/screenshot.png b/src/plugins/home/public/assets/o365_logs/screenshot.png new file mode 100644 index 0000000000000..a2413e7b909bc Binary files /dev/null and b/src/plugins/home/public/assets/o365_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/okta_logs/screenshot.png b/src/plugins/home/public/assets/okta_logs/screenshot.png new file mode 100644 index 0000000000000..6a28b4363b05b Binary files /dev/null and b/src/plugins/home/public/assets/okta_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/panw_logs/screenshot.png b/src/plugins/home/public/assets/panw_logs/screenshot.png new file mode 100644 index 0000000000000..ef9d5f706eca6 Binary files /dev/null and b/src/plugins/home/public/assets/panw_logs/screenshot.png differ diff --git a/src/plugins/home/public/assets/santa_logs/screenshot.png b/src/plugins/home/public/assets/santa_logs/screenshot.png new file mode 100644 index 0000000000000..31abdeb270a35 Binary files /dev/null and b/src/plugins/home/public/assets/santa_logs/screenshot.png differ diff --git a/src/plugins/home/server/tutorials/activemq_logs/index.ts b/src/plugins/home/server/tutorials/activemq_logs/index.ts index c11c070637ae1..b0214f7a6422b 100644 --- a/src/plugins/home/server/tutorials/activemq_logs/index.ts +++ b/src/plugins/home/server/tutorials/activemq_logs/index.ts @@ -53,11 +53,11 @@ export function activemqLogsSpecProvider(context: TutorialContext): TutorialSche artifacts: { dashboards: [ { - id: '26434790-1464-11ea-8fd8-030a13064883', + id: 'ffe86390-145f-11ea-8fd8-030a13064883', linkLabel: i18n.translate('home.tutorials.activemqLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'ActiveMQ Application Events', + defaultMessage: 'ActiveMQ Audit Events', }), - isOverview: true, + isOverview: false, }, ], exportedFields: { diff --git a/src/plugins/home/server/tutorials/auditd_logs/index.ts b/src/plugins/home/server/tutorials/auditd_logs/index.ts new file mode 100644 index 0000000000000..365a7dcca44fe --- /dev/null +++ b/src/plugins/home/server/tutorials/auditd_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function auditdLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'auditd'; + const platforms = ['DEB', 'RPM'] as const; + return { + id: 'auditdLogs', + name: i18n.translate('home.tutorials.auditdLogs.nameTitle', { + defaultMessage: 'Auditd logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.auditdLogs.shortDescription', { + defaultMessage: 'Collect logs from the Linux auditd daemon.', + }), + longDescription: i18n.translate('home.tutorials.auditdLogs.longDescription', { + defaultMessage: + 'The module collects and parses logs from the audit daemon ( `auditd`). \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-auditd.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/linux.svg', + artifacts: { + dashboards: [ + { + id: 'dfbb49f0-0a0f-11e7-8a62-2d05eaaac5cb-ecs', + linkLabel: i18n.translate('home.tutorials.auditdLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Audit Events', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-auditd.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/auditd_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/barracuda_logs/index.ts b/src/plugins/home/server/tutorials/barracuda_logs/index.ts new file mode 100644 index 0000000000000..b5792b7535cc3 --- /dev/null +++ b/src/plugins/home/server/tutorials/barracuda_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function barracudaLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'barracuda'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'barracudaLogs', + name: i18n.translate('home.tutorials.barracudaLogs.nameTitle', { + defaultMessage: 'Barracuda logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.barracudaLogs.shortDescription', { + defaultMessage: 'Collect Barracuda Web Application Firewall logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.barracudaLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Barracuda Web Application Firewall logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-barracuda.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/barracuda.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.barracudaLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-barracuda.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/bluecoat_logs/index.ts b/src/plugins/home/server/tutorials/bluecoat_logs/index.ts new file mode 100644 index 0000000000000..5e55b6b2c6455 --- /dev/null +++ b/src/plugins/home/server/tutorials/bluecoat_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function bluecoatLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'bluecoat'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'bluecoatLogs', + name: i18n.translate('home.tutorials.bluecoatLogs.nameTitle', { + defaultMessage: 'Bluecoat logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.bluecoatLogs.shortDescription', { + defaultMessage: 'Collect Blue Coat Director logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.bluecoatLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Blue Coat Director logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-bluecoat.html', + }, + }), + euiIconType: 'logoLogging', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.bluecoatLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-bluecoat.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/cef_logs/index.ts b/src/plugins/home/server/tutorials/cef_logs/index.ts new file mode 100644 index 0000000000000..6395f0ae76d5e --- /dev/null +++ b/src/plugins/home/server/tutorials/cef_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function cefLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'cef'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'cefLogs', + name: i18n.translate('home.tutorials.cefLogs.nameTitle', { + defaultMessage: 'CEF logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.cefLogs.shortDescription', { + defaultMessage: 'Collect Common Event Format (CEF) log data over syslog.', + }), + longDescription: i18n.translate('home.tutorials.cefLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Common Event Format (CEF) data over \ + Syslog. When messages are received over the syslog protocol the syslog \ + input will parse the header and set the timestamp value. Then the \ + processor is applied to parse the CEF encoded data. The decoded data \ + is written into a `cef` object field. Lastly any Elastic Common Schema \ + (ECS) fields that can be populated with the CEF data are populated. \ + [Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-cef.html', + }, + }), + euiIconType: 'logoLogging', + artifacts: { + dashboards: [ + { + id: 'dd0bc9af-2e89-4150-9b42-62517ea56b71', + linkLabel: i18n.translate('home.tutorials.cefLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'CEF Network Overview Dashboard', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-cef.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/checkpoint_logs/index.ts b/src/plugins/home/server/tutorials/checkpoint_logs/index.ts new file mode 100644 index 0000000000000..ed7051f63a32f --- /dev/null +++ b/src/plugins/home/server/tutorials/checkpoint_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function checkpointLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'checkpoint'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'checkpointLogs', + name: i18n.translate('home.tutorials.checkpointLogs.nameTitle', { + defaultMessage: 'Check Point logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.checkpointLogs.shortDescription', { + defaultMessage: 'Collect Check Point firewall logs.', + }), + longDescription: i18n.translate('home.tutorials.checkpointLogs.longDescription', { + defaultMessage: + 'This is a module for Check Point firewall logs. It supports logs from the Log Exporter in the Syslog format. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-checkpoint.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/checkpoint.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.checkpointLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-checkpoint.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/cisco_logs/index.ts b/src/plugins/home/server/tutorials/cisco_logs/index.ts index b771744a069c3..b60a2137d680e 100644 --- a/src/plugins/home/server/tutorials/cisco_logs/index.ts +++ b/src/plugins/home/server/tutorials/cisco_logs/index.ts @@ -35,17 +35,16 @@ export function ciscoLogsSpecProvider(context: TutorialContext): TutorialSchema return { id: 'ciscoLogs', name: i18n.translate('home.tutorials.ciscoLogs.nameTitle', { - defaultMessage: 'Cisco', + defaultMessage: 'Cisco logs', }), moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.ciscoLogs.shortDescription', { - defaultMessage: 'Collect and parse logs received from Cisco ASA firewalls.', + defaultMessage: 'Collect Cisco network device logs over syslog or from a file.', }), longDescription: i18n.translate('home.tutorials.ciscoLogs.longDescription', { defaultMessage: - 'This is a module for Cisco network device’s logs. Currently \ -supports the "asa" fileset for Cisco ASA firewall logs received over syslog or read from a file. \ + 'This is a module for Cisco network devices logs (ASA, FTD, IOS, Nexus). It includes the following filesets for receiving logs over syslog or read from a file: \ [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-cisco.html', @@ -53,13 +52,15 @@ supports the "asa" fileset for Cisco ASA firewall logs received over syslog or r }), euiIconType: '/plugins/home/assets/logos/cisco.svg', artifacts: { - dashboards: [], - application: { - path: '/app/security', - label: i18n.translate('home.tutorials.ciscoLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Security App', - }), - }, + dashboards: [ + { + id: 'a555b160-4987-11e9-b8ce-ed898b5ef295', + linkLabel: i18n.translate('home.tutorials.ciscoLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'ASA Firewall Dashboard', + }), + isOverview: true, + }, + ], exportedFields: { documentationUrl: '{config.docs.beats.filebeat}/exported-fields-cisco.html', }, diff --git a/src/plugins/home/server/tutorials/coredns_logs/index.ts b/src/plugins/home/server/tutorials/coredns_logs/index.ts index 7fc8a2402d216..13467e628c7fb 100644 --- a/src/plugins/home/server/tutorials/coredns_logs/index.ts +++ b/src/plugins/home/server/tutorials/coredns_logs/index.ts @@ -31,7 +31,7 @@ import { export function corednsLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'coredns'; - const platforms = ['OSX', 'DEB', 'RPM'] as const; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'corednsLogs', name: i18n.translate('home.tutorials.corednsLogs.nameTitle', { @@ -40,12 +40,11 @@ export function corednsLogsSpecProvider(context: TutorialContext): TutorialSchem moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.corednsLogs.shortDescription', { - defaultMessage: 'Collect the logs created by Coredns.', + defaultMessage: 'Collect CoreDNS logs.', }), longDescription: i18n.translate('home.tutorials.corednsLogs.longDescription', { defaultMessage: - 'The `coredns` Filebeat module collects the logs from \ -[CoreDNS](https://coredns.io/manual/toc/). \ + 'This is a filebeat module for CoreDNS. It supports both standalone CoreDNS deployment and CoreDNS deployment in Kubernetes. \ [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-coredns.html', @@ -57,7 +56,7 @@ export function corednsLogsSpecProvider(context: TutorialContext): TutorialSchem { id: '53aa1f70-443e-11e9-8548-ab7fbe04f038', linkLabel: i18n.translate('home.tutorials.corednsLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'CoreDNS logs dashboard', + defaultMessage: '[Filebeat CoreDNS] Overview', }), isOverview: true, }, @@ -67,7 +66,7 @@ export function corednsLogsSpecProvider(context: TutorialContext): TutorialSchem }, }, completionTimeMinutes: 10, - previewImagePath: '/plugins/home/assets/coredns_logs/screenshot.jpg', + previewImagePath: '/plugins/home/assets/coredns_logs/screenshot.png', onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), diff --git a/src/plugins/home/server/tutorials/crowdstrike_logs/index.ts b/src/plugins/home/server/tutorials/crowdstrike_logs/index.ts new file mode 100644 index 0000000000000..e4dd2bc6bb9c2 --- /dev/null +++ b/src/plugins/home/server/tutorials/crowdstrike_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function crowdstrikeLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'crowdstrike'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'crowdstrikeLogs', + name: i18n.translate('home.tutorials.crowdstrikeLogs.nameTitle', { + defaultMessage: 'CrowdStrike logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.crowdstrikeLogs.shortDescription', { + defaultMessage: 'Collect CrowdStrike Falcon logs using the Falcon SIEM Connector.', + }), + longDescription: i18n.translate('home.tutorials.crowdstrikeLogs.longDescription', { + defaultMessage: + 'This is the Filebeat module for CrowdStrike Falcon using the Falcon \ + [SIEM Connector](https://www.crowdstrike.com/blog/tech-center/integrate-with-your-siem). \ + This module collects this data, converts it to ECS, and ingests it to view in the SIEM. \ + By default, the Falcon SIEM connector outputs JSON formatted Falcon Streaming API event data. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-crowdstrike.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/crowdstrike.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.crowdstrikeLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-crowdstrike.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/cylance_logs/index.ts b/src/plugins/home/server/tutorials/cylance_logs/index.ts new file mode 100644 index 0000000000000..387a56febb83b --- /dev/null +++ b/src/plugins/home/server/tutorials/cylance_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function cylanceLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'cylance'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'cylanceLogs', + name: i18n.translate('home.tutorials.cylanceLogs.nameTitle', { + defaultMessage: 'CylancePROTECT logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.cylanceLogs.shortDescription', { + defaultMessage: 'Collect CylancePROTECT logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.cylanceLogs.longDescription', { + defaultMessage: + 'This is a module for receiving CylancePROTECT logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-cylance.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/cylance.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.cylanceLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-cylance.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/elasticsearch_logs/index.ts b/src/plugins/home/server/tutorials/elasticsearch_logs/index.ts index f6c280d29f67f..b30457ea7848b 100644 --- a/src/plugins/home/server/tutorials/elasticsearch_logs/index.ts +++ b/src/plugins/home/server/tutorials/elasticsearch_logs/index.ts @@ -65,6 +65,7 @@ export function elasticsearchLogsSpecProvider(context: TutorialContext): Tutoria }, }, completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/elasticsearch_logs/screenshot.png', onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), diff --git a/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts b/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts index 0cf032e6b90c1..94b5af143447f 100644 --- a/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts +++ b/src/plugins/home/server/tutorials/envoyproxy_logs/index.ts @@ -35,17 +35,16 @@ export function envoyproxyLogsSpecProvider(context: TutorialContext): TutorialSc return { id: 'envoyproxyLogs', name: i18n.translate('home.tutorials.envoyproxyLogs.nameTitle', { - defaultMessage: 'Envoyproxy', + defaultMessage: 'Envoy Proxy logs', }), moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.envoyproxyLogs.shortDescription', { - defaultMessage: 'Collect and parse logs received from the Envoy proxy.', + defaultMessage: 'Collect Envoy Proxy logs.', }), longDescription: i18n.translate('home.tutorials.envoyproxyLogs.longDescription', { defaultMessage: - 'This is a filebeat module for [Envoy proxy access log](https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/access_log). \ -It supports both standalone deployment and Envoy proxy deployment in Kubernetes. \ + 'This is a Filebeat module for Envoy proxy access log ( https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/access_log). It supports both standalone deployment and Envoy proxy deployment in Kubernetes. \ [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-envoyproxy.html', @@ -53,13 +52,18 @@ It supports both standalone deployment and Envoy proxy deployment in Kubernetes. }), euiIconType: '/plugins/home/assets/logos/envoyproxy.svg', artifacts: { - dashboards: [], - application: { - path: '/app/security', - label: i18n.translate('home.tutorials.envoyproxyLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Security App', - }), - }, + dashboards: [ + { + id: '0c610510-5cbd-11e9-8477-077ec9664dbd', + linkLabel: i18n.translate( + 'home.tutorials.envoyproxyLogs.artifacts.dashboards.linkLabel', + { + defaultMessage: 'Envoy Proxy Overview', + } + ), + isOverview: true, + }, + ], exportedFields: { documentationUrl: '{config.docs.beats.filebeat}/exported-fields-envoyproxy.html', }, diff --git a/src/plugins/home/server/tutorials/f5_logs/index.ts b/src/plugins/home/server/tutorials/f5_logs/index.ts new file mode 100644 index 0000000000000..4b4ae367ab46e --- /dev/null +++ b/src/plugins/home/server/tutorials/f5_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function f5LogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'f5'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'f5Logs', + name: i18n.translate('home.tutorials.f5Logs.nameTitle', { + defaultMessage: 'F5 logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.f5Logs.shortDescription', { + defaultMessage: 'Collect F5 Big-IP Access Policy Manager logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.f5Logs.longDescription', { + defaultMessage: + 'This is a module for receiving Big-IP Access Policy Manager logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-f5.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/f5.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.f5Logs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-f5.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/f5_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/fortinet_logs/index.ts b/src/plugins/home/server/tutorials/fortinet_logs/index.ts new file mode 100644 index 0000000000000..d60d383016295 --- /dev/null +++ b/src/plugins/home/server/tutorials/fortinet_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function fortinetLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'fortinet'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'fortinetLogs', + name: i18n.translate('home.tutorials.fortinetLogs.nameTitle', { + defaultMessage: 'Fortinet logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.fortinetLogs.shortDescription', { + defaultMessage: 'Collect Fortinet FortiOS logs over syslog.', + }), + longDescription: i18n.translate('home.tutorials.fortinetLogs.longDescription', { + defaultMessage: + 'This is a module for Fortinet FortiOS logs sent in the syslog format. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-fortinet.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/fortinet.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.fortinetLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-fortinet.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/googlecloud_logs/index.ts b/src/plugins/home/server/tutorials/googlecloud_logs/index.ts new file mode 100644 index 0000000000000..482ad38ddfbcd --- /dev/null +++ b/src/plugins/home/server/tutorials/googlecloud_logs/index.ts @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function googlecloudLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'googlecloud'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'googlecloudLogs', + name: i18n.translate('home.tutorials.googlecloudLogs.nameTitle', { + defaultMessage: 'Google Cloud logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.googlecloudLogs.shortDescription', { + defaultMessage: 'Collect Google Cloud audit, firewall, and VPC flow logs.', + }), + longDescription: i18n.translate('home.tutorials.googlecloudLogs.longDescription', { + defaultMessage: + 'This is a module for Google Cloud logs. It supports reading audit, VPC flow, \ + and firewall logs that have been exported from Stackdriver to a Google Pub/Sub \ + topic sink. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-googlecloud.html', + }, + }), + euiIconType: 'logoGoogleG', + artifacts: { + dashboards: [ + { + id: '6576c480-73a2-11ea-a345-f985c61fe654', + linkLabel: i18n.translate( + 'home.tutorials.googlecloudLogs.artifacts.dashboards.linkLabel', + { + defaultMessage: 'Audit Logs Dashbaord', + } + ), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-googlecloud.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/googlecloud_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/gsuite_logs/index.ts b/src/plugins/home/server/tutorials/gsuite_logs/index.ts new file mode 100644 index 0000000000000..0f7c0d7077d39 --- /dev/null +++ b/src/plugins/home/server/tutorials/gsuite_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function gsuiteLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'gsuite'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'gsuiteLogs', + name: i18n.translate('home.tutorials.gsuiteLogs.nameTitle', { + defaultMessage: 'GSuite logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.gsuiteLogs.shortDescription', { + defaultMessage: 'Collect GSuite activity reports.', + }), + longDescription: i18n.translate('home.tutorials.gsuiteLogs.longDescription', { + defaultMessage: + 'This is a module for ingesting data from the different GSuite audit reports APIs. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-gsuite.html', + }, + }), + euiIconType: 'logoGoogleG', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.gsuiteLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-gsuite.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/haproxy_logs/index.ts b/src/plugins/home/server/tutorials/haproxy_logs/index.ts new file mode 100644 index 0000000000000..82ef405ffe839 --- /dev/null +++ b/src/plugins/home/server/tutorials/haproxy_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function haproxyLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'haproxy'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'haproxyLogs', + name: i18n.translate('home.tutorials.haproxyLogs.nameTitle', { + defaultMessage: 'HAProxy logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.haproxyLogs.shortDescription', { + defaultMessage: 'Collect HAProxy logs.', + }), + longDescription: i18n.translate('home.tutorials.haproxyLogs.longDescription', { + defaultMessage: + 'The module collects and parses logs from a ( `haproxy`) process. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-haproxy.html', + }, + }), + euiIconType: 'logoHAproxy', + artifacts: { + dashboards: [ + { + id: '3560d580-aa34-11e8-9c06-877f0445e3e0-ecs', + linkLabel: i18n.translate('home.tutorials.haproxyLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'HAProxy Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-haproxy.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/haproxy_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/icinga_logs/index.ts b/src/plugins/home/server/tutorials/icinga_logs/index.ts new file mode 100644 index 0000000000000..de494e5a15046 --- /dev/null +++ b/src/plugins/home/server/tutorials/icinga_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function icingaLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'icinga'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'icingaLogs', + name: i18n.translate('home.tutorials.icingaLogs.nameTitle', { + defaultMessage: 'Icinga logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.icingaLogs.shortDescription', { + defaultMessage: 'Collect Icinga main, debug, and startup logs.', + }), + longDescription: i18n.translate('home.tutorials.icingaLogs.longDescription', { + defaultMessage: + 'The module parses the main, debug, and startup logs of [Icinga](https://www.icinga.com/products/icinga-2/). \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-icinga.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/icinga.svg', + artifacts: { + dashboards: [ + { + id: 'f693d260-2417-11e7-a83b-d5f4cebac9ff-ecs', + linkLabel: i18n.translate('home.tutorials.icingaLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Icinga Main Log', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-icinga.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/icinga_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/imperva_logs/index.ts b/src/plugins/home/server/tutorials/imperva_logs/index.ts new file mode 100644 index 0000000000000..d723a4c2f49ef --- /dev/null +++ b/src/plugins/home/server/tutorials/imperva_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function impervaLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'imperva'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'impervaLogs', + name: i18n.translate('home.tutorials.impervaLogs.nameTitle', { + defaultMessage: 'Imperva logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.impervaLogs.shortDescription', { + defaultMessage: 'Collect Imperva SecureSphere logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.impervaLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Imperva SecureSphere logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-imperva.html', + }, + }), + euiIconType: 'logoLogging', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.impervaLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-imperva.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/infoblox_logs/index.ts b/src/plugins/home/server/tutorials/infoblox_logs/index.ts new file mode 100644 index 0000000000000..811f3110e7871 --- /dev/null +++ b/src/plugins/home/server/tutorials/infoblox_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function infobloxLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'infoblox'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'infobloxLogs', + name: i18n.translate('home.tutorials.infobloxLogs.nameTitle', { + defaultMessage: 'Infoblox logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.infobloxLogs.shortDescription', { + defaultMessage: 'Collect Infoblox NIOS logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.infobloxLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Infoblox NIOS logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-infoblox.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/infoblox.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.infobloxLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-infoblox.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/iptables_logs/index.ts b/src/plugins/home/server/tutorials/iptables_logs/index.ts index b3be133767447..dc44ae6107b1e 100644 --- a/src/plugins/home/server/tutorials/iptables_logs/index.ts +++ b/src/plugins/home/server/tutorials/iptables_logs/index.ts @@ -31,37 +31,39 @@ import { export function iptablesLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'iptables'; - const platforms = ['DEB', 'RPM'] as const; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'iptablesLogs', name: i18n.translate('home.tutorials.iptablesLogs.nameTitle', { - defaultMessage: 'Iptables / Ubiquiti', + defaultMessage: 'Iptables logs', }), moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.iptablesLogs.shortDescription', { - defaultMessage: 'Collect and parse iptables and ip6tables logs or from Ubiqiti firewalls.', + defaultMessage: 'Collect iptables and ip6tables logs.', }), longDescription: i18n.translate('home.tutorials.iptablesLogs.longDescription', { defaultMessage: - 'This is a module for iptables and ip6tables logs. It parses logs \ -received over the network via syslog or from a file. Also, it understands the \ -prefix added by some Ubiquiti firewalls, which includes the rule set name, rule \ -number and the action performed on the traffic (allow/deny).. \ -[Learn more]({learnMoreLink}).', + 'This is a module for iptables and ip6tables logs. It parses logs received \ + over the network via syslog or from a file. Also, it understands the prefix \ + added by some Ubiquiti firewalls, which includes the rule set name, rule \ + number and the action performed on the traffic (allow/deny). \ + [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-iptables.html', }, }), - euiIconType: '/plugins/home/assets/logos/ubiquiti.svg', + euiIconType: '/plugins/home/assets/logos/linux.svg', artifacts: { - dashboards: [], - application: { - path: '/app/security', - label: i18n.translate('home.tutorials.iptablesLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Security App', - }), - }, + dashboards: [ + { + id: 'ceefb9e0-1f51-11e9-93ed-f7e068f4aebb-ecs', + linkLabel: i18n.translate('home.tutorials.iptablesLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Iptables Overview', + }), + isOverview: true, + }, + ], exportedFields: { documentationUrl: '{config.docs.beats.filebeat}/exported-fields-iptables.html', }, diff --git a/src/plugins/home/server/tutorials/juniper_logs/index.ts b/src/plugins/home/server/tutorials/juniper_logs/index.ts new file mode 100644 index 0000000000000..45688baa5bcdf --- /dev/null +++ b/src/plugins/home/server/tutorials/juniper_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function juniperLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'juniper'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'juniperLogs', + name: i18n.translate('home.tutorials.juniperLogs.nameTitle', { + defaultMessage: 'Juniper Logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.juniperLogs.shortDescription', { + defaultMessage: 'Collect Juniper JUNOS logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.juniperLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Juniper JUNOS logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-juniper.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/juniper.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.juniperLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-juniper.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/kibana_logs/index.ts b/src/plugins/home/server/tutorials/kibana_logs/index.ts new file mode 100644 index 0000000000000..88286a230f771 --- /dev/null +++ b/src/plugins/home/server/tutorials/kibana_logs/index.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function kibanaLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'kibana'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'kibanaLogs', + name: i18n.translate('home.tutorials.kibanaLogs.nameTitle', { + defaultMessage: 'Kibana Logs', + }), + moduleName, + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.kibanaLogs.shortDescription', { + defaultMessage: 'Collect Kibana logs.', + }), + longDescription: i18n.translate('home.tutorials.kibanaLogs.longDescription', { + defaultMessage: 'This is the Kibana module. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-kibana.html', + }, + }), + euiIconType: 'logoKibana', + artifacts: { + dashboards: [], + application: { + label: i18n.translate('home.tutorials.kibanaLogs.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/discover#/', + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-kibana.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/logstash_logs/index.ts b/src/plugins/home/server/tutorials/logstash_logs/index.ts index 32982cd1055a4..6998aaf9001e6 100644 --- a/src/plugins/home/server/tutorials/logstash_logs/index.ts +++ b/src/plugins/home/server/tutorials/logstash_logs/index.ts @@ -38,13 +38,13 @@ export function logstashLogsSpecProvider(context: TutorialContext): TutorialSche defaultMessage: 'Logstash logs', }), moduleName, - category: TutorialsCategory.LOGGING, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.logstashLogs.shortDescription', { - defaultMessage: 'Collect and parse debug and slow logs created by Logstash itself.', + defaultMessage: 'Collect Logstash main and slow logs.', }), longDescription: i18n.translate('home.tutorials.logstashLogs.longDescription', { defaultMessage: - 'The `logstash` Filebeat module parses debug and slow logs created by Logstash itself. \ + 'The modules parse Logstash regular logs and the slow log, it will support the plain text format and the JSON format. \ [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-logstash.html', @@ -56,7 +56,7 @@ export function logstashLogsSpecProvider(context: TutorialContext): TutorialSche { id: 'Filebeat-Logstash-Log-Dashboard-ecs', linkLabel: i18n.translate('home.tutorials.logstashLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Logstash logs dashboard', + defaultMessage: 'Logstash Logs', }), isOverview: true, }, @@ -66,7 +66,6 @@ export function logstashLogsSpecProvider(context: TutorialContext): TutorialSche }, }, completionTimeMinutes: 10, - previewImagePath: '/plugins/home/assets/logstash_logs/screenshot.png', onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), diff --git a/src/plugins/home/server/tutorials/microsoft_logs/index.ts b/src/plugins/home/server/tutorials/microsoft_logs/index.ts new file mode 100644 index 0000000000000..28739a5817ae7 --- /dev/null +++ b/src/plugins/home/server/tutorials/microsoft_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function microsoftLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'microsoft'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'microsoftLogs', + name: i18n.translate('home.tutorials.microsoftLogs.nameTitle', { + defaultMessage: 'Microsoft Defender ATP logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.microsoftLogs.shortDescription', { + defaultMessage: 'Collect Microsoft Defender ATP alerts.', + }), + longDescription: i18n.translate('home.tutorials.microsoftLogs.longDescription', { + defaultMessage: + 'Collect Microsoft Defender ATP alerts for use with Elastic Security. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-microsoft.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/microsoft.svg', + artifacts: { + dashboards: [ + { + id: '65402c30-ca6a-11ea-9d4d-9737a63aaa55', + linkLabel: i18n.translate('home.tutorials.microsoftLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Microsoft ATP Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-microsoft.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/microsoft_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/misp_logs/index.ts b/src/plugins/home/server/tutorials/misp_logs/index.ts new file mode 100644 index 0000000000000..050c22fb39523 --- /dev/null +++ b/src/plugins/home/server/tutorials/misp_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function mispLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'misp'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'mispLogs', + name: i18n.translate('home.tutorials.mispLogs.nameTitle', { + defaultMessage: 'MISP threat intel logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.mispLogs.shortDescription', { + defaultMessage: 'Collect MISP threat intelligence data with Filebeat.', + }), + longDescription: i18n.translate('home.tutorials.mispLogs.longDescription', { + defaultMessage: + 'This is a filebeat module for reading threat intel information from the MISP platform ( https://www.circl.lu/doc/misp/). It uses the httpjson input to access the MISP REST API interface. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-misp.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/misp.svg', + artifacts: { + dashboards: [ + { + id: 'c6cac9e0-f105-11e9-9a88-690b10c8ee99', + linkLabel: i18n.translate('home.tutorials.mispLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'MISP Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-misp.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/misp_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/mongodb_logs/index.ts b/src/plugins/home/server/tutorials/mongodb_logs/index.ts new file mode 100644 index 0000000000000..1c8db19a3873b --- /dev/null +++ b/src/plugins/home/server/tutorials/mongodb_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function mongodbLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'mongodb'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'mongodbLogs', + name: i18n.translate('home.tutorials.mongodbLogs.nameTitle', { + defaultMessage: 'MongoDB logs', + }), + moduleName, + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.mongodbLogs.shortDescription', { + defaultMessage: 'Collect MongoDB logs.', + }), + longDescription: i18n.translate('home.tutorials.mongodbLogs.longDescription', { + defaultMessage: + 'The module collects and parses logs created by [MongoDB](https://www.mongodb.com/). \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-mongodb.html', + }, + }), + euiIconType: 'logoMongodb', + artifacts: { + dashboards: [ + { + id: 'abcf35b0-0a82-11e8-bffe-ff7d4f68cf94-ecs', + linkLabel: i18n.translate('home.tutorials.mongodbLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'MongoDB Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-mongodb.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/mongodb_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/mssql_logs/index.ts b/src/plugins/home/server/tutorials/mssql_logs/index.ts new file mode 100644 index 0000000000000..8a18da20f2e6a --- /dev/null +++ b/src/plugins/home/server/tutorials/mssql_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function mssqlLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'mssql'; + const platforms = ['DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'mssqlLogs', + name: i18n.translate('home.tutorials.mssqlLogs.nameTitle', { + defaultMessage: 'MSSQL logs', + }), + moduleName, + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.mssqlLogs.shortDescription', { + defaultMessage: 'Collect MSSQL logs.', + }), + longDescription: i18n.translate('home.tutorials.mssqlLogs.longDescription', { + defaultMessage: + 'The module parses error logs created by MSSQL. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-mssql.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/microsoft.svg', + artifacts: { + dashboards: [], + application: { + label: i18n.translate('home.tutorials.mssqlLogs.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/discover#/', + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-mssql.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/netflow/common_instructions.ts b/src/plugins/home/server/tutorials/netflow/common_instructions.ts deleted file mode 100644 index 8fe24ba9c7994..0000000000000 --- a/src/plugins/home/server/tutorials/netflow/common_instructions.ts +++ /dev/null @@ -1,281 +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 { i18n } from '@kbn/i18n'; - -export function createCommonNetflowInstructions() { - return { - CONFIG: { - ON_PREM: { - OSX: [ - { - title: i18n.translate('home.tutorials.netflow.common.config.onPrem.osxTitle', { - defaultMessage: 'Edit the configuration', - }), - textPre: i18n.translate('home.tutorials.netflow.common.config.onPrem.osxTextPre', { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config/logstash.yml`', - }, - }), - commands: ['modules:', ' - name: netflow', ' var.input.udp.port: '], - textPost: i18n.translate('home.tutorials.netflow.common.config.onPrem.osxTextPost', { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data.', - values: { - udpPort: '``', - }, - }), - }, - ], - WINDOWS: [ - { - title: i18n.translate('home.tutorials.netflow.common.config.onPrem.windowsTitle', { - defaultMessage: 'Edit the configuration', - }), - textPre: i18n.translate('home.tutorials.netflow.common.config.onPrem.windowsTextPre', { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config\\logstash.yml`', - }, - }), - commands: ['modules:', ' - name: netflow', ' var.input.udp.port: '], - textPost: i18n.translate( - 'home.tutorials.netflow.common.config.onPrem.windowsTextPost', - { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data.', - values: { - udpPort: '``', - }, - } - ), - }, - ], - }, - ON_PREM_ELASTIC_CLOUD: { - OSX: [ - { - title: i18n.translate( - 'home.tutorials.netflow.common.config.onPremElasticCloud.osxTitle', - { - defaultMessage: 'Edit the configuration', - } - ), - textPre: i18n.translate( - 'home.tutorials.netflow.common.config.onPremElasticCloud.osxTextPre', - { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config/logstash.yml`', - }, - } - ), - commands: [ - 'modules:', - ' - name: netflow', - ' var.input.udp.port: ', - ' var.elasticsearch.hosts: [ "" ]', - ' var.elasticsearch.username: elastic', - ' var.elasticsearch.password: ', - ], - textPost: i18n.translate( - 'home.tutorials.netflow.common.config.onPremElasticCloud.osxTextPost', - { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data, \ - {esUrl} is the URL of Elasticsearch running on Elastic Cloud, and \ - {password} is the password of the {elastic} user.', - values: { - elastic: '`elastic`', - esUrl: '``', - password: '``', - udpPort: '``', - }, - } - ), - }, - ], - WINDOWS: [ - { - title: i18n.translate( - 'home.tutorials.netflow.common.config.onPremElasticCloud.windowsTitle', - { - defaultMessage: 'Edit the configuration', - } - ), - textPre: i18n.translate( - 'home.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPre', - { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config\\logstash.yml`', - }, - } - ), - commands: [ - 'modules:', - ' - name: netflow', - ' var.input.udp.port: ', - ' var.elasticsearch.hosts: [ "" ]', - ' var.elasticsearch.username: elastic', - ' var.elasticsearch.password: ', - ], - textPost: i18n.translate( - 'home.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPost', - { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data, \ - {esUrl} is the URL of Elasticsearch running on Elastic Cloud, and \ - {password} is the password of the {elastic} user.', - values: { - elastic: '`elastic`', - esUrl: '``', - password: '``', - udpPort: '``', - }, - } - ), - }, - ], - }, - ELASTIC_CLOUD: { - OSX: [ - { - title: i18n.translate('home.tutorials.netflow.common.config.elasticCloud.osxTitle', { - defaultMessage: 'Edit the configuration', - }), - textPre: i18n.translate( - 'home.tutorials.netflow.common.config.elasticCloud.osxTextPre', - { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config/logstash.yml`', - }, - } - ), - commands: [ - 'cloud.id: "{config.cloud.id}"', - 'cloud.auth: "elastic:"', - ' ', - 'modules:', - ' - name: netflow', - ' var.input.udp.port: ', - ], - textPost: i18n.translate( - 'home.tutorials.netflow.common.config.elasticCloud.osxTextPost', - { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data and \ - {password} is the password of the {elastic} user.', - values: { - elastic: '`elastic`', - password: '``', - udpPort: '``', - }, - } - ), - }, - ], - WINDOWS: [ - { - title: i18n.translate( - 'home.tutorials.netflow.common.config.elasticCloud.windowsTitle', - { - defaultMessage: 'Edit the configuration', - } - ), - textPre: i18n.translate( - 'home.tutorials.netflow.common.config.elasticCloud.windowsTextPre', - { - defaultMessage: 'Modify {logstashConfigPath} to set the configuration parameters:', - values: { - logstashConfigPath: '`config\\logstash.yml`', - }, - } - ), - commands: [ - 'cloud.id: "{config.cloud.id}"', - 'cloud.auth: "elastic:"', - ' ', - 'modules:', - ' - name: netflow', - ' var.input.udp.port: ', - ], - textPost: i18n.translate( - 'home.tutorials.netflow.common.config.elasticCloud.windowsTextPost', - { - defaultMessage: - 'Where {udpPort} is the UDP port on which Logstash will receive Netflow data and \ - {password} is the password of the {elastic} user.', - values: { - elastic: '`elastic`', - password: '``', - udpPort: '``', - }, - } - ), - }, - ], - }, - }, - SETUP: { - OSX: [ - { - title: i18n.translate('home.tutorials.netflow.common.setup.osxTitle', { - defaultMessage: 'Run the Netflow module', - }), - textPre: i18n.translate('home.tutorials.netflow.common.setup.osxTextPre', { - defaultMessage: 'Run:', - }), - commands: ['./bin/logstash --modules netflow --setup'], - textPost: i18n.translate('home.tutorials.netflow.common.setup.osxTextPost', { - defaultMessage: - 'The {setupOption} option creates a {netflowPrefix} index pattern in Elasticsearch and imports \ - Kibana dashboards and visualizations. Omit this option for subsequent runs to avoid overwriting existing dashboards.', - values: { - setupOption: '`--setup`', - netflowPrefix: '`netflow-*`', - }, - }), - }, - ], - WINDOWS: [ - { - title: i18n.translate('home.tutorials.netflow.common.setup.windowsTitle', { - defaultMessage: 'Run the Netflow module', - }), - textPre: i18n.translate('home.tutorials.netflow.common.setup.windowsTextPre', { - defaultMessage: 'Run:', - }), - commands: ['bin\\logstash --modules netflow --setup'], - textPost: i18n.translate('home.tutorials.netflow.common.setup.windowsTextPost', { - defaultMessage: - 'The {setupOption} option creates a {netflowPrefix} index pattern in Elasticsearch and imports \ - Kibana dashboards and visualizations. Omit this option for subsequent runs to avoid overwriting existing dashboards.', - values: { - setupOption: '`--setup`', - netflowPrefix: '`netflow-*`', - }, - }), - }, - ], - }, - }; -} diff --git a/src/plugins/home/server/tutorials/netflow/elastic_cloud.ts b/src/plugins/home/server/tutorials/netflow/elastic_cloud.ts deleted file mode 100644 index fbedc6abfbb8a..0000000000000 --- a/src/plugins/home/server/tutorials/netflow/elastic_cloud.ts +++ /dev/null @@ -1,58 +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 { i18n } from '@kbn/i18n'; - -import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; -import { createLogstashInstructions } from '../instructions/logstash_instructions'; -import { createCommonNetflowInstructions } from './common_instructions'; - -// TODO: compare with onPremElasticCloud and onPrem scenarios and extract out common bits -export function createElasticCloudInstructions() { - const COMMON_NETFLOW_INSTRUCTIONS = createCommonNetflowInstructions(); - const LOGSTASH_INSTRUCTIONS = createLogstashInstructions(); - - return { - instructionSets: [ - { - title: i18n.translate('home.tutorials.netflow.elasticCloudInstructions.title', { - defaultMessage: 'Getting Started', - }), - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.OSX, - instructions: [ - ...LOGSTASH_INSTRUCTIONS.INSTALL.OSX, - ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ELASTIC_CLOUD.OSX, - ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.OSX, - ], - }, - { - id: INSTRUCTION_VARIANT.WINDOWS, - instructions: [ - ...LOGSTASH_INSTRUCTIONS.INSTALL.WINDOWS, - ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ELASTIC_CLOUD.WINDOWS, - ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.WINDOWS, - ], - }, - ], - }, - ], - }; -} diff --git a/src/plugins/home/server/tutorials/netflow/index.ts b/src/plugins/home/server/tutorials/netflow/index.ts deleted file mode 100644 index 5be30bbb152b7..0000000000000 --- a/src/plugins/home/server/tutorials/netflow/index.ts +++ /dev/null @@ -1,52 +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 { i18n } from '@kbn/i18n'; - -import { TutorialsCategory } from '../../services/tutorials'; -import { createOnPremInstructions } from './on_prem'; -import { createElasticCloudInstructions } from './elastic_cloud'; -import { createOnPremElasticCloudInstructions } from './on_prem_elastic_cloud'; - -export function netflowSpecProvider() { - const moduleName = 'netflow'; - return { - id: 'netflow', - name: 'Netflow', - moduleName, - category: TutorialsCategory.SECURITY_SOLUTION, - shortDescription: i18n.translate('home.tutorials.netflow.tutorialShortDescription', { - defaultMessage: 'Collect Netflow records sent by a Netflow exporter.', - }), - longDescription: i18n.translate('home.tutorials.netflow.tutorialLongDescription', { - defaultMessage: - 'The Logstash Netflow module collects and parses network flow data, \ -indexes the events into Elasticsearch, and installs a suite of Kibana dashboards. \ -This module support Netflow Version 5 and 9. [Learn more]({linkUrl}).', - values: { - linkUrl: '{config.docs.logstash}/netflow-module.html', - }, - }), - completionTimeMinutes: 10, - // previewImagePath: 'kibana-apache.png', TODO - onPrem: createOnPremInstructions(), - elasticCloud: createElasticCloudInstructions(), - onPremElasticCloud: createOnPremElasticCloudInstructions(), - }; -} diff --git a/src/plugins/home/server/tutorials/netflow/on_prem.ts b/src/plugins/home/server/tutorials/netflow/on_prem.ts deleted file mode 100644 index ef8c3e172af87..0000000000000 --- a/src/plugins/home/server/tutorials/netflow/on_prem.ts +++ /dev/null @@ -1,58 +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 { i18n } from '@kbn/i18n'; - -import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; -import { createLogstashInstructions } from '../instructions/logstash_instructions'; -import { createCommonNetflowInstructions } from './common_instructions'; - -// TODO: compare with onPremElasticCloud and elasticCloud scenarios and extract out common bits -export function createOnPremInstructions() { - const COMMON_NETFLOW_INSTRUCTIONS = createCommonNetflowInstructions(); - const LOGSTASH_INSTRUCTIONS = createLogstashInstructions(); - - return { - instructionSets: [ - { - title: i18n.translate('home.tutorials.netflow.onPremInstructions.title', { - defaultMessage: 'Getting Started', - }), - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.OSX, - instructions: [ - ...LOGSTASH_INSTRUCTIONS.INSTALL.OSX, - ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM.OSX, - ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.OSX, - ], - }, - { - id: INSTRUCTION_VARIANT.WINDOWS, - instructions: [ - ...LOGSTASH_INSTRUCTIONS.INSTALL.WINDOWS, - ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM.WINDOWS, - ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.WINDOWS, - ], - }, - ], - }, - ], - }; -} diff --git a/src/plugins/home/server/tutorials/netflow/on_prem_elastic_cloud.ts b/src/plugins/home/server/tutorials/netflow/on_prem_elastic_cloud.ts deleted file mode 100644 index 85aa694970491..0000000000000 --- a/src/plugins/home/server/tutorials/netflow/on_prem_elastic_cloud.ts +++ /dev/null @@ -1,68 +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 { i18n } from '@kbn/i18n'; - -import { INSTRUCTION_VARIANT } from '../../../common/instruction_variant'; -import { createLogstashInstructions } from '../instructions/logstash_instructions'; -import { - createTrycloudOption1, - createTrycloudOption2, -} from '../instructions/onprem_cloud_instructions'; -import { createCommonNetflowInstructions } from './common_instructions'; - -// TODO: compare with onPrem and elasticCloud scenarios and extract out common bits -export function createOnPremElasticCloudInstructions() { - const COMMON_NETFLOW_INSTRUCTIONS = createCommonNetflowInstructions(); - const TRYCLOUD_OPTION1 = createTrycloudOption1(); - const TRYCLOUD_OPTION2 = createTrycloudOption2(); - const LOGSTASH_INSTRUCTIONS = createLogstashInstructions(); - - return { - instructionSets: [ - { - title: i18n.translate('home.tutorials.netflow.onPremElasticCloudInstructions.title', { - defaultMessage: 'Getting Started', - }), - instructionVariants: [ - { - id: INSTRUCTION_VARIANT.OSX, - instructions: [ - TRYCLOUD_OPTION1, - TRYCLOUD_OPTION2, - ...LOGSTASH_INSTRUCTIONS.INSTALL.OSX, - ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM_ELASTIC_CLOUD.OSX, - ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.OSX, - ], - }, - { - id: INSTRUCTION_VARIANT.WINDOWS, - instructions: [ - TRYCLOUD_OPTION1, - TRYCLOUD_OPTION2, - ...LOGSTASH_INSTRUCTIONS.INSTALL.WINDOWS, - ...COMMON_NETFLOW_INSTRUCTIONS.CONFIG.ON_PREM_ELASTIC_CLOUD.WINDOWS, - ...COMMON_NETFLOW_INSTRUCTIONS.SETUP.WINDOWS, - ], - }, - ], - }, - ], - }; -} diff --git a/src/plugins/home/server/tutorials/netflow_logs/index.ts b/src/plugins/home/server/tutorials/netflow_logs/index.ts new file mode 100644 index 0000000000000..0e36bcafae580 --- /dev/null +++ b/src/plugins/home/server/tutorials/netflow_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function netflowLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'netflow'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'netflowLogs', + name: i18n.translate('home.tutorials.netflowLogs.nameTitle', { + defaultMessage: 'NetFlow / IPFIX Collector', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.netflowLogs.shortDescription', { + defaultMessage: 'Collect NetFlow and IPFIX flow records.', + }), + longDescription: i18n.translate('home.tutorials.netflowLogs.longDescription', { + defaultMessage: + 'This is a module for receiving NetFlow and IPFIX flow records over UDP. This input supports NetFlow versions 1, 5, 6, 7, 8 and 9, as well as IPFIX. For NetFlow versions older than 9, fields are mapped automatically to NetFlow v9. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-netflow.html', + }, + }), + euiIconType: 'logoBeats', + artifacts: { + dashboards: [ + { + id: '34e26884-161a-4448-9556-43b5bf2f62a2', + linkLabel: i18n.translate('home.tutorials.netflowLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Netflow Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-netflow.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/netscout_logs/index.ts b/src/plugins/home/server/tutorials/netscout_logs/index.ts new file mode 100644 index 0000000000000..b94e0df0bf795 --- /dev/null +++ b/src/plugins/home/server/tutorials/netscout_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function netscoutLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'netscout'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'netscoutLogs', + name: i18n.translate('home.tutorials.netscoutLogs.nameTitle', { + defaultMessage: 'Arbor Peakflow logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.netscoutLogs.shortDescription', { + defaultMessage: 'Collect Netscout Arbor Peakflow SP logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.netscoutLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Arbor Peakflow SP logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-netscout.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/netscout.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.netscoutLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-netscout.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/o365_logs/index.ts b/src/plugins/home/server/tutorials/o365_logs/index.ts new file mode 100644 index 0000000000000..2ee3f3550a0d7 --- /dev/null +++ b/src/plugins/home/server/tutorials/o365_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function o365LogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'o365'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'o365Logs', + name: i18n.translate('home.tutorials.o365Logs.nameTitle', { + defaultMessage: 'Office 365 logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.o365Logs.shortDescription', { + defaultMessage: 'Collect Office 365 activity logs via the Office 365 API.', + }), + longDescription: i18n.translate('home.tutorials.o365Logs.longDescription', { + defaultMessage: + 'This is a module for Office 365 logs received via one of the Office 365 \ + API endpoints. It currently supports user, admin, system, and policy \ + actions and events from Office 365 and Azure AD activity logs exposed \ + by the Office 365 Management Activity API. \ + [Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-o365.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/o365.svg', + artifacts: { + dashboards: [ + { + id: '712e2c00-685d-11ea-8d6a-292ef5d68366', + linkLabel: i18n.translate('home.tutorials.o365Logs.artifacts.dashboards.linkLabel', { + defaultMessage: 'O365 Audit Dashboard', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-o365.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/o365_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/okta_logs/index.ts b/src/plugins/home/server/tutorials/okta_logs/index.ts new file mode 100644 index 0000000000000..6371d9848af2e --- /dev/null +++ b/src/plugins/home/server/tutorials/okta_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function oktaLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'okta'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'oktaLogs', + name: i18n.translate('home.tutorials.oktaLogs.nameTitle', { + defaultMessage: 'Okta logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.oktaLogs.shortDescription', { + defaultMessage: 'Collect the Okta system log via the Okta API.', + }), + longDescription: i18n.translate('home.tutorials.oktaLogs.longDescription', { + defaultMessage: + 'The Okta module collects events from the [Okta API](https://developer.okta.com/docs/reference/). \ + Specifically this supports reading from the [Okta System Log API](https://developer.okta.com/docs/reference/api/system-log/). \ + [Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-okta.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/okta.svg', + artifacts: { + dashboards: [ + { + id: '749203a0-67b1-11ea-a76f-bf44814e437d', + linkLabel: i18n.translate('home.tutorials.oktaLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Okta Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-okta.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/okta_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/oracle_metrics/index.ts b/src/plugins/home/server/tutorials/oracle_metrics/index.ts index ce6da26d5dcb6..a7d315a0259d4 100644 --- a/src/plugins/home/server/tutorials/oracle_metrics/index.ts +++ b/src/plugins/home/server/tutorials/oracle_metrics/index.ts @@ -51,6 +51,7 @@ export function oracleMetricsSpecProvider(context: TutorialContext): TutorialSch learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-' + moduleName + '.html', }, }), + euiIconType: '/plugins/home/assets/logos/oracle.svg', artifacts: { application: { label: i18n.translate('home.tutorials.oracleMetrics.artifacts.application.label', { diff --git a/src/plugins/home/server/tutorials/osquery_logs/index.ts b/src/plugins/home/server/tutorials/osquery_logs/index.ts index c4869a889a085..1d98b30b324ce 100644 --- a/src/plugins/home/server/tutorials/osquery_logs/index.ts +++ b/src/plugins/home/server/tutorials/osquery_logs/index.ts @@ -40,23 +40,27 @@ export function osqueryLogsSpecProvider(context: TutorialContext): TutorialSchem moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.osqueryLogs.shortDescription', { - defaultMessage: 'Collect the result logs created by osqueryd.', + defaultMessage: 'Collect osquery logs in JSON format.', }), longDescription: i18n.translate('home.tutorials.osqueryLogs.longDescription', { defaultMessage: - 'The `osquery` Filebeat module collects the JSON result logs collected by `osqueryd`. \ -[Learn more]({learnMoreLink}).', + 'The module collects and decodes the result logs written by \ + [osqueryd](https://osquery.readthedocs.io/en/latest/introduction/using-osqueryd/) in \ + the JSON format. To set up osqueryd follow the osquery installation instructions for \ + your operating system and configure the `filesystem` logging driver (the default). \ + Make sure UTC timestamps are enabled. \ + [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-osquery.html', }, }), - euiIconType: 'logoOsquery', + euiIconType: '/plugins/home/assets/logos/osquery.svg', artifacts: { dashboards: [ { id: '69f5ae20-eb02-11e7-8f04-51231daa5b05-ecs', linkLabel: i18n.translate('home.tutorials.osqueryLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Osquery logs dashboard', + defaultMessage: 'Osquery Compliance Pack', }), isOverview: true, }, @@ -66,7 +70,6 @@ export function osqueryLogsSpecProvider(context: TutorialContext): TutorialSchem }, }, completionTimeMinutes: 10, - previewImagePath: '/plugins/home/assets/osquery_logs/screenshot.png', onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), diff --git a/src/plugins/home/server/tutorials/panw_logs/index.ts b/src/plugins/home/server/tutorials/panw_logs/index.ts new file mode 100644 index 0000000000000..5a0da7538b4c0 --- /dev/null +++ b/src/plugins/home/server/tutorials/panw_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function panwLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'panw'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'panwLogs', + name: i18n.translate('home.tutorials.panwLogs.nameTitle', { + defaultMessage: 'Palo Alto Networks PAN-OS logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.panwLogs.shortDescription', { + defaultMessage: + 'Collect Palo Alto Networks PAN-OS threat and traffic logs over syslog or from a log file.', + }), + longDescription: i18n.translate('home.tutorials.panwLogs.longDescription', { + defaultMessage: + 'This is a module for Palo Alto Networks PAN-OS firewall monitoring \ + logs received over Syslog or read from a file. It currently supports \ + messages of Traffic and Threat types. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-panw.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/paloalto.svg', + artifacts: { + dashboards: [ + { + id: 'e40ba240-7572-11e9-976e-65a8f47cc4c1', + linkLabel: i18n.translate('home.tutorials.panwLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'PANW Network Flows', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-panw.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/panw_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/rabbitmq_logs/index.ts b/src/plugins/home/server/tutorials/rabbitmq_logs/index.ts new file mode 100644 index 0000000000000..2676f8b52306c --- /dev/null +++ b/src/plugins/home/server/tutorials/rabbitmq_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function rabbitmqLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'rabbitmq'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'rabbitmqLogs', + name: i18n.translate('home.tutorials.rabbitmqLogs.nameTitle', { + defaultMessage: 'RabbitMQ logs', + }), + moduleName, + category: TutorialsCategory.LOGGING, + shortDescription: i18n.translate('home.tutorials.rabbitmqLogs.shortDescription', { + defaultMessage: 'Collect RabbitMQ logs.', + }), + longDescription: i18n.translate('home.tutorials.rabbitmqLogs.longDescription', { + defaultMessage: + 'This is the module for parsing [RabbitMQ log files](https://www.rabbitmq.com/logging.html) \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-rabbitmq.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/rabbitmq.svg', + artifacts: { + dashboards: [], + application: { + label: i18n.translate('home.tutorials.rabbitmqLogs.artifacts.application.label', { + defaultMessage: 'Discover', + }), + path: '/app/discover#/', + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-rabbitmq.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/radware_logs/index.ts b/src/plugins/home/server/tutorials/radware_logs/index.ts new file mode 100644 index 0000000000000..10f32c7b767ab --- /dev/null +++ b/src/plugins/home/server/tutorials/radware_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function radwareLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'radware'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'radwareLogs', + name: i18n.translate('home.tutorials.radwareLogs.nameTitle', { + defaultMessage: 'Radware DefensePro logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.radwareLogs.shortDescription', { + defaultMessage: 'Collect Radware DefensePro logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.radwareLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Radware DefensePro logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-radware.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/radware.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.radwareLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-radware.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/register.ts b/src/plugins/home/server/tutorials/register.ts index c48423edb2a07..67e9f5a406803 100644 --- a/src/plugins/home/server/tutorials/register.ts +++ b/src/plugins/home/server/tutorials/register.ts @@ -16,82 +16,115 @@ * specific language governing permissions and limitations * under the License. */ -import { systemLogsSpecProvider } from './system_logs'; -import { systemMetricsSpecProvider } from './system_metrics'; +import { activemqLogsSpecProvider } from './activemq_logs'; +import { activemqMetricsSpecProvider } from './activemq_metrics'; +import { aerospikeMetricsSpecProvider } from './aerospike_metrics'; import { apacheLogsSpecProvider } from './apache_logs'; import { apacheMetricsSpecProvider } from './apache_metrics'; +import { auditbeatSpecProvider } from './auditbeat'; +import { auditdLogsSpecProvider } from './auditd_logs'; +import { awsLogsSpecProvider } from './aws_logs'; +import { awsMetricsSpecProvider } from './aws_metrics'; +import { azureLogsSpecProvider } from './azure_logs'; +import { azureMetricsSpecProvider } from './azure_metrics'; +import { barracudaLogsSpecProvider } from './barracuda_logs'; +import { bluecoatLogsSpecProvider } from './bluecoat_logs'; +import { cefLogsSpecProvider } from './cef_logs'; +import { cephMetricsSpecProvider } from './ceph_metrics'; +import { checkpointLogsSpecProvider } from './checkpoint_logs'; +import { ciscoLogsSpecProvider } from './cisco_logs'; +import { cloudwatchLogsSpecProvider } from './cloudwatch_logs'; +import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; +import { consulMetricsSpecProvider } from './consul_metrics'; +import { corednsLogsSpecProvider } from './coredns_logs'; +import { corednsMetricsSpecProvider } from './coredns_metrics'; +import { couchbaseMetricsSpecProvider } from './couchbase_metrics'; +import { couchdbMetricsSpecProvider } from './couchdb_metrics'; +import { crowdstrikeLogsSpecProvider } from './crowdstrike_logs'; +import { cylanceLogsSpecProvider } from './cylance_logs'; +import { dockerMetricsSpecProvider } from './docker_metrics'; +import { dropwizardMetricsSpecProvider } from './dropwizard_metrics'; import { elasticsearchLogsSpecProvider } from './elasticsearch_logs'; +import { elasticsearchMetricsSpecProvider } from './elasticsearch_metrics'; +import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; +import { envoyproxyMetricsSpecProvider } from './envoyproxy_metrics'; +import { etcdMetricsSpecProvider } from './etcd_metrics'; +import { f5LogsSpecProvider } from './f5_logs'; +import { fortinetLogsSpecProvider } from './fortinet_logs'; +import { golangMetricsSpecProvider } from './golang_metrics'; +import { googlecloudLogsSpecProvider } from './googlecloud_logs'; +import { googlecloudMetricsSpecProvider } from './googlecloud_metrics'; +import { gsuiteLogsSpecProvider } from './gsuite_logs'; +import { haproxyLogsSpecProvider } from './haproxy_logs'; +import { haproxyMetricsSpecProvider } from './haproxy_metrics'; +import { ibmmqLogsSpecProvider } from './ibmmq_logs'; +import { ibmmqMetricsSpecProvider } from './ibmmq_metrics'; +import { icingaLogsSpecProvider } from './icinga_logs'; import { iisLogsSpecProvider } from './iis_logs'; +import { iisMetricsSpecProvider } from './iis_metrics'; +import { impervaLogsSpecProvider } from './imperva_logs'; +import { infobloxLogsSpecProvider } from './infoblox_logs'; +import { iptablesLogsSpecProvider } from './iptables_logs'; +import { juniperLogsSpecProvider } from './juniper_logs'; import { kafkaLogsSpecProvider } from './kafka_logs'; +import { kafkaMetricsSpecProvider } from './kafka_metrics'; +import { kibanaLogsSpecProvider } from './kibana_logs'; +import { kibanaMetricsSpecProvider } from './kibana_metrics'; +import { kubernetesMetricsSpecProvider } from './kubernetes_metrics'; import { logstashLogsSpecProvider } from './logstash_logs'; -import { nginxLogsSpecProvider } from './nginx_logs'; -import { nginxMetricsSpecProvider } from './nginx_metrics'; +import { logstashMetricsSpecProvider } from './logstash_metrics'; +import { memcachedMetricsSpecProvider } from './memcached_metrics'; +import { microsoftLogsSpecProvider } from './microsoft_logs'; +import { mispLogsSpecProvider } from './misp_logs'; +import { mongodbLogsSpecProvider } from './mongodb_logs'; +import { mongodbMetricsSpecProvider } from './mongodb_metrics'; +import { mssqlLogsSpecProvider } from './mssql_logs'; +import { mssqlMetricsSpecProvider } from './mssql_metrics'; +import { muninMetricsSpecProvider } from './munin_metrics'; import { mysqlLogsSpecProvider } from './mysql_logs'; import { mysqlMetricsSpecProvider } from './mysql_metrics'; -import { mongodbMetricsSpecProvider } from './mongodb_metrics'; +import { natsLogsSpecProvider } from './nats_logs'; +import { natsMetricsSpecProvider } from './nats_metrics'; +import { netflowLogsSpecProvider } from './netflow_logs'; +import { netscoutLogsSpecProvider } from './netscout_logs'; +import { nginxLogsSpecProvider } from './nginx_logs'; +import { nginxMetricsSpecProvider } from './nginx_metrics'; +import { o365LogsSpecProvider } from './o365_logs'; +import { oktaLogsSpecProvider } from './okta_logs'; +import { openmetricsMetricsSpecProvider } from './openmetrics_metrics'; +import { oracleMetricsSpecProvider } from './oracle_metrics'; import { osqueryLogsSpecProvider } from './osquery_logs'; +import { panwLogsSpecProvider } from './panw_logs'; import { phpfpmMetricsSpecProvider } from './php_fpm_metrics'; -import { postgresqlMetricsSpecProvider } from './postgresql_metrics'; import { postgresqlLogsSpecProvider } from './postgresql_logs'; +import { postgresqlMetricsSpecProvider } from './postgresql_metrics'; +import { prometheusMetricsSpecProvider } from './prometheus_metrics'; +import { rabbitmqLogsSpecProvider } from './rabbitmq_logs'; import { rabbitmqMetricsSpecProvider } from './rabbitmq_metrics'; +import { radwareLogsSpecProvider } from './radware_logs'; import { redisLogsSpecProvider } from './redis_logs'; import { redisMetricsSpecProvider } from './redis_metrics'; +import { redisenterpriseMetricsSpecProvider } from './redisenterprise_metrics'; +import { santaLogsSpecProvider } from './santa_logs'; +import { sonicwallLogsSpecProvider } from './sonicwall_logs'; +import { sophosLogsSpecProvider } from './sophos_logs'; +import { squidLogsSpecProvider } from './squid_logs'; +import { stanMetricsSpecProvider } from './stan_metrics'; +import { statsdMetricsSpecProvider } from './statsd_metrics'; import { suricataLogsSpecProvider } from './suricata_logs'; -import { dockerMetricsSpecProvider } from './docker_metrics'; -import { kubernetesMetricsSpecProvider } from './kubernetes_metrics'; -import { uwsgiMetricsSpecProvider } from './uwsgi_metrics'; -import { netflowSpecProvider } from './netflow'; +import { systemLogsSpecProvider } from './system_logs'; +import { systemMetricsSpecProvider } from './system_metrics'; +import { tomcatLogsSpecProvider } from './tomcat_logs'; import { traefikLogsSpecProvider } from './traefik_logs'; -import { cephMetricsSpecProvider } from './ceph_metrics'; -import { aerospikeMetricsSpecProvider } from './aerospike_metrics'; -import { couchbaseMetricsSpecProvider } from './couchbase_metrics'; -import { dropwizardMetricsSpecProvider } from './dropwizard_metrics'; -import { elasticsearchMetricsSpecProvider } from './elasticsearch_metrics'; -import { etcdMetricsSpecProvider } from './etcd_metrics'; -import { haproxyMetricsSpecProvider } from './haproxy_metrics'; -import { kafkaMetricsSpecProvider } from './kafka_metrics'; -import { kibanaMetricsSpecProvider } from './kibana_metrics'; -import { memcachedMetricsSpecProvider } from './memcached_metrics'; -import { muninMetricsSpecProvider } from './munin_metrics'; +import { traefikMetricsSpecProvider } from './traefik_metrics'; +import { uptimeMonitorsSpecProvider } from './uptime_monitors'; +import { uwsgiMetricsSpecProvider } from './uwsgi_metrics'; import { vSphereMetricsSpecProvider } from './vsphere_metrics'; -import { windowsMetricsSpecProvider } from './windows_metrics'; import { windowsEventLogsSpecProvider } from './windows_event_logs'; -import { golangMetricsSpecProvider } from './golang_metrics'; -import { logstashMetricsSpecProvider } from './logstash_metrics'; -import { prometheusMetricsSpecProvider } from './prometheus_metrics'; -import { zookeeperMetricsSpecProvider } from './zookeeper_metrics'; -import { uptimeMonitorsSpecProvider } from './uptime_monitors'; -import { cloudwatchLogsSpecProvider } from './cloudwatch_logs'; -import { awsMetricsSpecProvider } from './aws_metrics'; -import { mssqlMetricsSpecProvider } from './mssql_metrics'; -import { natsMetricsSpecProvider } from './nats_metrics'; -import { natsLogsSpecProvider } from './nats_logs'; +import { windowsMetricsSpecProvider } from './windows_metrics'; import { zeekLogsSpecProvider } from './zeek_logs'; -import { corednsMetricsSpecProvider } from './coredns_metrics'; -import { corednsLogsSpecProvider } from './coredns_logs'; -import { auditbeatSpecProvider } from './auditbeat'; -import { iptablesLogsSpecProvider } from './iptables_logs'; -import { ciscoLogsSpecProvider } from './cisco_logs'; -import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; -import { couchdbMetricsSpecProvider } from './couchdb_metrics'; -import { consulMetricsSpecProvider } from './consul_metrics'; -import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; -import { traefikMetricsSpecProvider } from './traefik_metrics'; -import { awsLogsSpecProvider } from './aws_logs'; -import { activemqLogsSpecProvider } from './activemq_logs'; -import { activemqMetricsSpecProvider } from './activemq_metrics'; -import { azureMetricsSpecProvider } from './azure_metrics'; -import { ibmmqLogsSpecProvider } from './ibmmq_logs'; -import { stanMetricsSpecProvider } from './stan_metrics'; -import { envoyproxyMetricsSpecProvider } from './envoyproxy_metrics'; -import { ibmmqMetricsSpecProvider } from './ibmmq_metrics'; -import { statsdMetricsSpecProvider } from './statsd_metrics'; -import { redisenterpriseMetricsSpecProvider } from './redisenterprise_metrics'; -import { openmetricsMetricsSpecProvider } from './openmetrics_metrics'; -import { oracleMetricsSpecProvider } from './oracle_metrics'; -import { iisMetricsSpecProvider } from './iis_metrics'; -import { azureLogsSpecProvider } from './azure_logs'; -import { googlecloudMetricsSpecProvider } from './googlecloud_metrics'; +import { zookeeperMetricsSpecProvider } from './zookeeper_metrics'; +import { zscalerLogsSpecProvider } from './zscaler_logs'; export const builtInTutorials = [ systemLogsSpecProvider, @@ -118,7 +151,7 @@ export const builtInTutorials = [ dockerMetricsSpecProvider, kubernetesMetricsSpecProvider, uwsgiMetricsSpecProvider, - netflowSpecProvider, + netflowLogsSpecProvider, traefikLogsSpecProvider, cephMetricsSpecProvider, aerospikeMetricsSpecProvider, @@ -170,4 +203,37 @@ export const builtInTutorials = [ iisMetricsSpecProvider, azureLogsSpecProvider, googlecloudMetricsSpecProvider, + auditdLogsSpecProvider, + barracudaLogsSpecProvider, + bluecoatLogsSpecProvider, + cefLogsSpecProvider, + checkpointLogsSpecProvider, + crowdstrikeLogsSpecProvider, + cylanceLogsSpecProvider, + f5LogsSpecProvider, + fortinetLogsSpecProvider, + googlecloudLogsSpecProvider, + gsuiteLogsSpecProvider, + haproxyLogsSpecProvider, + icingaLogsSpecProvider, + impervaLogsSpecProvider, + infobloxLogsSpecProvider, + juniperLogsSpecProvider, + kibanaLogsSpecProvider, + microsoftLogsSpecProvider, + mispLogsSpecProvider, + mongodbLogsSpecProvider, + mssqlLogsSpecProvider, + netscoutLogsSpecProvider, + o365LogsSpecProvider, + oktaLogsSpecProvider, + panwLogsSpecProvider, + rabbitmqLogsSpecProvider, + radwareLogsSpecProvider, + santaLogsSpecProvider, + sonicwallLogsSpecProvider, + sophosLogsSpecProvider, + squidLogsSpecProvider, + tomcatLogsSpecProvider, + zscalerLogsSpecProvider, ]; diff --git a/src/plugins/home/server/tutorials/santa_logs/index.ts b/src/plugins/home/server/tutorials/santa_logs/index.ts new file mode 100644 index 0000000000000..3cdab67a08b57 --- /dev/null +++ b/src/plugins/home/server/tutorials/santa_logs/index.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 { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function santaLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'santa'; + const platforms = ['OSX'] as const; + return { + id: 'santaLogs', + name: i18n.translate('home.tutorials.santaLogs.nameTitle', { + defaultMessage: 'Google Santa logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.santaLogs.shortDescription', { + defaultMessage: 'Collect Google Santa logs about process executions on MacOS.', + }), + longDescription: i18n.translate('home.tutorials.santaLogs.longDescription', { + defaultMessage: + 'The module collects and parses logs from [Google Santa](https://github.com/google/santa), \ + a security tool for macOS that monitors process executions and can blacklist/whitelist binaries. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-santa.html', + }, + }), + euiIconType: 'logoLogging', + artifacts: { + dashboards: [ + { + id: '161855f0-ff6a-11e8-93c5-d5ecd1b3e307-ecs', + linkLabel: i18n.translate('home.tutorials.santaLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Santa Overview', + }), + isOverview: true, + }, + ], + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-santa.html', + }, + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/home/assets/santa_logs/screenshot.png', + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/sonicwall_logs/index.ts b/src/plugins/home/server/tutorials/sonicwall_logs/index.ts new file mode 100644 index 0000000000000..49cfa694f3888 --- /dev/null +++ b/src/plugins/home/server/tutorials/sonicwall_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function sonicwallLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'sonicwall'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'sonicwallLogs', + name: i18n.translate('home.tutorials.sonicwallLogs.nameTitle', { + defaultMessage: 'Sonicwall FW logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.sonicwallLogs.shortDescription', { + defaultMessage: 'Collect Sonicwall-FW logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.sonicwallLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Sonicwall-FW logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-sonicwall.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/sonicwall.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.radwareLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-sonicwall.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/sophos_logs/index.ts b/src/plugins/home/server/tutorials/sophos_logs/index.ts new file mode 100644 index 0000000000000..0c732fe954d3b --- /dev/null +++ b/src/plugins/home/server/tutorials/sophos_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function sophosLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'sophos'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'sophosLogs', + name: i18n.translate('home.tutorials.sophosLogs.nameTitle', { + defaultMessage: 'Sophos logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.sophosLogs.shortDescription', { + defaultMessage: 'Collect Sophos XG SFOS logs over syslog.', + }), + longDescription: i18n.translate('home.tutorials.sophosLogs.longDescription', { + defaultMessage: + 'This is a module for Sophos Products, currently it supports XG SFOS logs sent in the syslog format. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-sophos.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/sophos.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.sophosLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-sophos.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/squid_logs/index.ts b/src/plugins/home/server/tutorials/squid_logs/index.ts new file mode 100644 index 0000000000000..c697f728b8e0e --- /dev/null +++ b/src/plugins/home/server/tutorials/squid_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function squidLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'squid'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'squidLogs', + name: i18n.translate('home.tutorials.squidLogs.nameTitle', { + defaultMessage: 'Squid logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.squidLogs.shortDescription', { + defaultMessage: 'Collect Squid logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.squidLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Squid logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-squid.html', + }, + }), + euiIconType: 'logoLogging', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.squidLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-squid.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/suricata_logs/index.ts b/src/plugins/home/server/tutorials/suricata_logs/index.ts index eec81b9496647..85ea3062f8b6d 100644 --- a/src/plugins/home/server/tutorials/suricata_logs/index.ts +++ b/src/plugins/home/server/tutorials/suricata_logs/index.ts @@ -40,13 +40,13 @@ export function suricataLogsSpecProvider(context: TutorialContext): TutorialSche moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.suricataLogs.shortDescription', { - defaultMessage: 'Collect the result logs created by Suricata IDS/IPS/NSM.', + defaultMessage: 'Collect Suricata IDS/IPS/NSM logs.', }), longDescription: i18n.translate('home.tutorials.suricataLogs.longDescription', { defaultMessage: - 'The `suricata` Filebeat module collects the logs from the \ -[Suricata Eve JSON output](https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html). \ -[Learn more]({learnMoreLink}).', + 'This is a module to the Suricata IDS/IPS/NSM log. It parses logs that are \ + in the [Suricata Eve JSON format](https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html). \ + [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-suricata.html', }, @@ -55,9 +55,9 @@ export function suricataLogsSpecProvider(context: TutorialContext): TutorialSche artifacts: { dashboards: [ { - id: '69f5ae20-eb02-11e7-8f04-51231daa5b05', + id: '78289c40-86da-11e8-b59d-21efb914e65c-ecs', linkLabel: i18n.translate('home.tutorials.suricataLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Suricata logs dashboard', + defaultMessage: 'Suricata Events Overview', }), isOverview: true, }, diff --git a/src/plugins/home/server/tutorials/system_logs/index.ts b/src/plugins/home/server/tutorials/system_logs/index.ts index f39df25461a5f..f94098399938f 100644 --- a/src/plugins/home/server/tutorials/system_logs/index.ts +++ b/src/plugins/home/server/tutorials/system_logs/index.ts @@ -31,33 +31,32 @@ import { export function systemLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'system'; - const platforms = ['OSX', 'DEB', 'RPM'] as const; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'systemLogs', name: i18n.translate('home.tutorials.systemLogs.nameTitle', { defaultMessage: 'System logs', }), moduleName, - category: TutorialsCategory.LOGGING, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.systemLogs.shortDescription', { - defaultMessage: 'Collect and parse logs written by the local Syslog server.', + defaultMessage: 'Collect system logs of common Unix/Linux based distributions.', }), longDescription: i18n.translate('home.tutorials.systemLogs.longDescription', { defaultMessage: - 'The `system` Filebeat module collects and parses logs created by the system logging service of common \ -Unix/Linux based distributions. This module is not available on Windows. \ + 'The module collects and parses logs created by the system logging service of common Unix/Linux based distributions. \ [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-system.html', }, }), - euiIconType: '/plugins/home/assets/logos/system.svg', + euiIconType: 'logoLogging', artifacts: { dashboards: [ { id: 'Filebeat-syslog-dashboard-ecs', linkLabel: i18n.translate('home.tutorials.systemLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'System logs dashboard', + defaultMessage: 'System Syslog Dashboard', }), isOverview: true, }, @@ -67,7 +66,6 @@ Unix/Linux based distributions. This module is not available on Windows. \ }, }, completionTimeMinutes: 10, - previewImagePath: '/plugins/home/assets/system_logs/screenshot.png', onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), diff --git a/src/plugins/home/server/tutorials/tomcat_logs/index.ts b/src/plugins/home/server/tutorials/tomcat_logs/index.ts new file mode 100644 index 0000000000000..4b3c9f7357719 --- /dev/null +++ b/src/plugins/home/server/tutorials/tomcat_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function tomcatLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'tomcat'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'tomcatLogs', + name: i18n.translate('home.tutorials.tomcatLogs.nameTitle', { + defaultMessage: 'Tomcat logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.tomcatLogs.shortDescription', { + defaultMessage: 'Collect Apache Tomcat logs over syslog or from a file.', + }), + longDescription: i18n.translate('home.tutorials.tomcatLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Apache Tomcat logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-tomcat.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/tomcat.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.tomcatLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-tomcat.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/home/server/tutorials/traefik_logs/index.ts b/src/plugins/home/server/tutorials/traefik_logs/index.ts index 0a84dcb081883..8bcc806ad59a0 100644 --- a/src/plugins/home/server/tutorials/traefik_logs/index.ts +++ b/src/plugins/home/server/tutorials/traefik_logs/index.ts @@ -38,13 +38,13 @@ export function traefikLogsSpecProvider(context: TutorialContext): TutorialSchem defaultMessage: 'Traefik logs', }), moduleName, - category: TutorialsCategory.LOGGING, + category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.traefikLogs.shortDescription', { - defaultMessage: 'Collect and parse access logs created by the Traefik Proxy.', + defaultMessage: 'Collect Traefik access logs.', }), longDescription: i18n.translate('home.tutorials.traefikLogs.longDescription', { defaultMessage: - 'The `traefik` Filebeat module parses access logs created by Traefik. \ + 'The module parses access logs created by [Træfik](https://traefik.io/). \ [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-traefik.html', @@ -56,7 +56,7 @@ export function traefikLogsSpecProvider(context: TutorialContext): TutorialSchem { id: 'Filebeat-Traefik-Dashboard-ecs', linkLabel: i18n.translate('home.tutorials.traefikLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Traefik logs dashboard', + defaultMessage: 'Traefik Access Logs', }), isOverview: true, }, @@ -66,7 +66,6 @@ export function traefikLogsSpecProvider(context: TutorialContext): TutorialSchem }, }, completionTimeMinutes: 10, - previewImagePath: '/plugins/home/assets/traefik_logs/screenshot.png', onPrem: onPremInstructions(moduleName, platforms, context), elasticCloud: cloudInstructions(moduleName, platforms), onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), diff --git a/src/plugins/home/server/tutorials/zeek_logs/index.ts b/src/plugins/home/server/tutorials/zeek_logs/index.ts index e39dcd3409490..dbc07d27b4f8a 100644 --- a/src/plugins/home/server/tutorials/zeek_logs/index.ts +++ b/src/plugins/home/server/tutorials/zeek_logs/index.ts @@ -31,7 +31,7 @@ import { export function zeekLogsSpecProvider(context: TutorialContext): TutorialSchema { const moduleName = 'zeek'; - const platforms = ['OSX', 'DEB', 'RPM'] as const; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; return { id: 'zeekLogs', name: i18n.translate('home.tutorials.zeekLogs.nameTitle', { @@ -40,13 +40,13 @@ export function zeekLogsSpecProvider(context: TutorialContext): TutorialSchema { moduleName, category: TutorialsCategory.SECURITY_SOLUTION, shortDescription: i18n.translate('home.tutorials.zeekLogs.shortDescription', { - defaultMessage: 'Collect the logs created by Zeek/Bro.', + defaultMessage: 'Collect Zeek network security monitoring logs.', }), longDescription: i18n.translate('home.tutorials.zeekLogs.longDescription', { defaultMessage: - 'The `zeek` Filebeat module collects the logs from \ -[Zeek](https://www.zeek.org//documentation/index.html). \ -[Learn more]({learnMoreLink}).', + 'This is a module for Zeek, which used to be called Bro. It parses logs \ + that are in the [Zeek JSON format](https://www.zeek.org/manual/release/logs/index.html). \ + [Learn more]({learnMoreLink}).', values: { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-zeek.html', }, @@ -57,7 +57,7 @@ export function zeekLogsSpecProvider(context: TutorialContext): TutorialSchema { { id: '7cbb5410-3700-11e9-aa6d-ff445a78330c', linkLabel: i18n.translate('home.tutorials.zeekLogs.artifacts.dashboards.linkLabel', { - defaultMessage: 'Zeek logs dashboard', + defaultMessage: 'Zeek Overview', }), isOverview: true, }, diff --git a/src/plugins/home/server/tutorials/zscaler_logs/index.ts b/src/plugins/home/server/tutorials/zscaler_logs/index.ts new file mode 100644 index 0000000000000..800702fe7da96 --- /dev/null +++ b/src/plugins/home/server/tutorials/zscaler_logs/index.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { TutorialsCategory } from '../../services/tutorials'; +import { + onPremInstructions, + cloudInstructions, + onPremCloudInstructions, +} from '../instructions/filebeat_instructions'; +import { + TutorialContext, + TutorialSchema, +} from '../../services/tutorials/lib/tutorials_registry_types'; + +export function zscalerLogsSpecProvider(context: TutorialContext): TutorialSchema { + const moduleName = 'zscaler'; + const platforms = ['OSX', 'DEB', 'RPM', 'WINDOWS'] as const; + return { + id: 'zscalerLogs', + name: i18n.translate('home.tutorials.zscalerLogs.nameTitle', { + defaultMessage: 'Zscaler Logs', + }), + moduleName, + category: TutorialsCategory.SECURITY_SOLUTION, + shortDescription: i18n.translate('home.tutorials.zscalerLogs.shortDescription', { + defaultMessage: 'This is a module for receiving Zscaler NSS logs over Syslog or a file.', + }), + longDescription: i18n.translate('home.tutorials.zscalerLogs.longDescription', { + defaultMessage: + 'This is a module for receiving Zscaler NSS logs over Syslog or a file. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-zscaler.html', + }, + }), + euiIconType: '/plugins/home/assets/logos/zscaler.svg', + artifacts: { + dashboards: [], + application: { + path: '/app/security', + label: i18n.translate('home.tutorials.zscalerLogs.artifacts.dashboards.linkLabel', { + defaultMessage: 'Security App', + }), + }, + exportedFields: { + documentationUrl: '{config.docs.beats.filebeat}/exported-fields-zscaler.html', + }, + }, + completionTimeMinutes: 10, + onPrem: onPremInstructions(moduleName, platforms, context), + elasticCloud: cloudInstructions(moduleName, platforms), + onPremElasticCloud: onPremCloudInstructions(moduleName, platforms), + }; +} diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx index c88918041ca81..675f2dec86145 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx @@ -36,8 +36,8 @@ const mockIndexPatternCreationType = new IndexPatternCreationConfig({ }); jest.mock('../../lib/get_indices', () => ({ - getIndices: ({}, {}, query: string) => { - if (query.startsWith('e')) { + getIndices: ({ pattern }: { pattern: string }) => { + if (pattern.startsWith('e')) { return [{ name: 'es', item: {} }]; } diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index d8555d71d6ec0..386514de68d4d 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -147,6 +147,10 @@ export class StepIndexPattern extends Component { const { indexPatternCreationType } = this.props; const { existingIndexPatterns } = this.state; + const { http } = this.context.services; + const getIndexTags = (indexName: string) => indexPatternCreationType.getIndexTags(indexName); + const searchClient = this.context.services.data.search.search; + const showAllIndices = this.state.isIncludingSystemIndices; if ((existingIndexPatterns as string[]).includes(query)) { this.setState({ indexPatternExists: true }); @@ -157,12 +161,7 @@ export class StepIndexPattern extends Component indexPatternCreationType.getIndexTags(indexName), - query, - this.state.isIncludingSystemIndices - ) + getIndices({ http, getIndexTags, pattern: query, showAllIndices, searchClient }) ); // If the search changed, discard this state if (query !== this.lastQuery) { @@ -173,18 +172,8 @@ export class StepIndexPattern extends Component indexPatternCreationType.getIndexTags(indexName), - `${query}*`, - this.state.isIncludingSystemIndices - ), - getIndices( - this.context.services.http, - (indexName: string) => indexPatternCreationType.getIndexTags(indexName), - query, - this.state.isIncludingSystemIndices - ), + getIndices({ http, getIndexTags, pattern: `${query}*`, showAllIndices, searchClient }), + getIndices({ http, getIndexTags, pattern: query, showAllIndices, searchClient }), ]); // If the search changed, discard this state diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx index aa97c21d766b9..259bd5c1d007e 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/create_index_pattern_wizard.tsx @@ -108,6 +108,11 @@ export class CreateIndexPatternWizard extends Component< }; fetchData = async () => { + const { http } = this.context.services; + const getIndexTags = (indexName: string) => + this.state.indexPatternCreationType.getIndexTags(indexName); + const searchClient = this.context.services.data.search.search; + const indicesFailMsg = ( this.state.indexPatternCreationType.getIndexTags(indexName), - `*`, - false - ), + getIndices({ http, getIndexTags, pattern: '*', searchClient }), [], indicesFailMsg @@ -142,12 +142,7 @@ export class CreateIndexPatternWizard extends Component< this.catchAndWarn( // if we get an error from remote cluster query, supply fallback value that allows user entry. // ['a'] is fallback value - getIndices( - this.context.services.http, - (indexName: string) => this.state.indexPatternCreationType.getIndexTags(indexName), - `*:*`, - false - ), + getIndices({ http, getIndexTags, pattern: '*:*', searchClient }), ['a'], clustersFailMsg diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts index 44a2d1a3be0d0..f366085164631 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.test.ts @@ -17,11 +17,12 @@ * under the License. */ -import { getIndices, responseToItemArray } from './get_indices'; +import { getIndices, responseToItemArray, dedupeMatchedItems } from './get_indices'; import { httpServiceMock } from '../../../../../../core/public/mocks'; -import { ResolveIndexResponseItemIndexAttrs } from '../types'; +import { ResolveIndexResponseItemIndexAttrs, MatchedItem } from '../types'; +import { Observable } from 'rxjs'; -export const successfulResponse = { +export const successfulResolveResponse = { indices: [ { name: 'remoteCluster1:bar-01', @@ -43,27 +44,64 @@ export const successfulResponse = { ], }; -const mockGetTags = () => []; +const successfulSearchResponse = { + rawResponse: { + aggregations: { + indices: { + buckets: [{ key: 'kibana_sample_data_ecommerce' }, { key: '.kibana_1' }], + }, + }, + }, +}; + +const getIndexTags = () => []; +const searchClient = () => + new Observable((observer) => { + observer.next(successfulSearchResponse); + observer.complete(); + }) as any; const http = httpServiceMock.createStartContract(); -http.get.mockResolvedValue(successfulResponse); +http.get.mockResolvedValue(successfulResolveResponse); describe('getIndices', () => { it('should work in a basic case', async () => { - const result = await getIndices(http, mockGetTags, 'kibana', false); + const uncalledSearchClient = jest.fn(); + const result = await getIndices({ + http, + getIndexTags, + pattern: 'kibana', + searchClient: uncalledSearchClient, + }); + expect(http.get).toHaveBeenCalled(); + expect(uncalledSearchClient).not.toHaveBeenCalled(); expect(result.length).toBe(3); expect(result[0].name).toBe('f-alias'); expect(result[1].name).toBe('foo'); }); + it('should make two calls in cross cluser case', async () => { + http.get.mockResolvedValue(successfulResolveResponse); + const result = await getIndices({ http, getIndexTags, pattern: '*:kibana', searchClient }); + + expect(http.get).toHaveBeenCalled(); + expect(result.length).toBe(4); + expect(result[0].name).toBe('f-alias'); + expect(result[1].name).toBe('foo'); + expect(result[2].name).toBe('kibana_sample_data_ecommerce'); + expect(result[3].name).toBe('remoteCluster1:bar-01'); + }); + it('should ignore ccs query-all', async () => { - expect((await getIndices(http, mockGetTags, '*:', false)).length).toBe(0); + expect((await getIndices({ http, getIndexTags, pattern: '*:', searchClient })).length).toBe(0); }); it('should ignore a single comma', async () => { - expect((await getIndices(http, mockGetTags, ',', false)).length).toBe(0); - expect((await getIndices(http, mockGetTags, ',*', false)).length).toBe(0); - expect((await getIndices(http, mockGetTags, ',foobar', false)).length).toBe(0); + expect((await getIndices({ http, getIndexTags, pattern: ',', searchClient })).length).toBe(0); + expect((await getIndices({ http, getIndexTags, pattern: ',*', searchClient })).length).toBe(0); + expect( + (await getIndices({ http, getIndexTags, pattern: ',foobar', searchClient })).length + ).toBe(0); }); it('response object to item array', () => { @@ -91,8 +129,14 @@ describe('getIndices', () => { }, ], }; - expect(responseToItemArray(result, mockGetTags)).toMatchSnapshot(); - expect(responseToItemArray({}, mockGetTags)).toEqual([]); + expect(responseToItemArray(result, getIndexTags)).toMatchSnapshot(); + expect(responseToItemArray({}, getIndexTags)).toEqual([]); + }); + + it('matched items are deduped', () => { + const setA = [{ name: 'a' }, { name: 'b' }] as MatchedItem[]; + const setB = [{ name: 'b' }, { name: 'c' }] as MatchedItem[]; + expect(dedupeMatchedItems(setA, setB)).toHaveLength(3); }); describe('errors', () => { @@ -100,7 +144,7 @@ describe('getIndices', () => { http.get.mockImplementationOnce(() => { throw new Error('Test error'); }); - const result = await getIndices(http, mockGetTags, 'kibana', false); + const result = await getIndices({ http, getIndexTags, pattern: 'kibana', searchClient }); expect(result.length).toBe(0); }); }); diff --git a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts index 6844e90316e22..618eb8e80a5ff 100644 --- a/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts +++ b/src/plugins/index_pattern_management/public/components/create_index_pattern_wizard/lib/get_indices.ts @@ -20,8 +20,11 @@ import { sortBy } from 'lodash'; import { HttpStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; +import { map, scan } from 'rxjs/operators'; import { IndexPatternCreationConfig } from '../../../../../index_pattern_management/public'; import { MatchedItem, ResolveIndexResponse, ResolveIndexResponseItemIndexAttrs } from '../types'; +import { DataPublicPluginStart, IEsSearchResponse } from '../../../../../data/public'; +import { MAX_SEARCH_SIZE } from '../constants'; const aliasLabel = i18n.translate('indexPatternManagement.aliasLabel', { defaultMessage: 'Alias' }); const dataStreamLabel = i18n.translate('indexPatternManagement.dataStreamLabel', { @@ -36,13 +39,134 @@ const frozenLabel = i18n.translate('indexPatternManagement.frozenLabel', { defaultMessage: 'Frozen', }); -export async function getIndices( - http: HttpStart, +export const searchResponseToArray = ( getIndexTags: IndexPatternCreationConfig['getIndexTags'], - rawPattern: string, showAllIndices: boolean -): Promise { +) => (response: IEsSearchResponse) => { + const { rawResponse } = response; + if (!rawResponse.aggregations) { + return []; + } else { + return rawResponse.aggregations.indices.buckets + .map((bucket: { key: string }) => { + return bucket.key; + }) + .filter((indexName: string) => { + if (showAllIndices) { + return true; + } else { + return !indexName.startsWith('.'); + } + }) + .map((indexName: string) => { + return { + name: indexName, + tags: getIndexTags(indexName), + item: {}, + }; + }); + } +}; + +export const getIndicesViaSearch = async ({ + getIndexTags, + pattern, + searchClient, + showAllIndices, +}: { + getIndexTags: IndexPatternCreationConfig['getIndexTags']; + pattern: string; + searchClient: DataPublicPluginStart['search']['search']; + showAllIndices: boolean; +}): Promise => + searchClient({ + params: { + ignoreUnavailable: true, + expand_wildcards: showAllIndices ? 'all' : 'open', + index: pattern, + body: { + size: 0, // no hits + aggs: { + indices: { + terms: { + field: '_index', + size: MAX_SEARCH_SIZE, + }, + }, + }, + }, + }, + }) + .pipe(map(searchResponseToArray(getIndexTags, showAllIndices))) + .pipe(scan((accumulator = [], value) => accumulator.join(value))) + .toPromise() + .catch(() => []); + +export const getIndicesViaResolve = async ({ + http, + getIndexTags, + pattern, + showAllIndices, +}: { + http: HttpStart; + getIndexTags: IndexPatternCreationConfig['getIndexTags']; + pattern: string; + showAllIndices: boolean; +}) => + http + .get(`/internal/index-pattern-management/resolve_index/${pattern}`, { + query: showAllIndices ? { expand_wildcards: 'all' } : undefined, + }) + .then((response) => { + if (!response) { + return []; + } else { + return responseToItemArray(response, getIndexTags); + } + }); + +/** + * Takes two MatchedItem[]s and returns a merged set, with the second set prrioritized over the first based on name + * + * @param matchedA + * @param matchedB + */ + +export const dedupeMatchedItems = (matchedA: MatchedItem[], matchedB: MatchedItem[]) => { + const mergedMatchedItems = matchedA.reduce((col, item) => { + col[item.name] = item; + return col; + }, {} as Record); + + matchedB.reduce((col, item) => { + col[item.name] = item; + return col; + }, mergedMatchedItems); + + return Object.values(mergedMatchedItems).sort((a, b) => { + if (a.name > b.name) return 1; + if (b.name > a.name) return -1; + + return 0; + }); +}; + +export async function getIndices({ + http, + getIndexTags = () => [], + pattern: rawPattern, + showAllIndices = false, + searchClient, +}: { + http: HttpStart; + getIndexTags?: IndexPatternCreationConfig['getIndexTags']; + pattern: string; + showAllIndices?: boolean; + searchClient: DataPublicPluginStart['search']['search']; +}): Promise { const pattern = rawPattern.trim(); + const isCCS = pattern.indexOf(':') !== -1; + const requests: Array> = []; // Searching for `*:` fails for CCS environments. The search request // is worthless anyways as the we should only send a request @@ -62,20 +186,32 @@ export async function getIndices( return []; } - const query = showAllIndices ? { expand_wildcards: 'all' } : undefined; + const promiseResolve = getIndicesViaResolve({ + http, + getIndexTags, + pattern, + showAllIndices, + }).catch(() => []); + requests.push(promiseResolve); + + if (isCCS) { + // CCS supports ±1 major version. We won't be able to expect resolve endpoint to exist until v9 + const promiseSearch = getIndicesViaSearch({ + getIndexTags, + pattern, + searchClient, + showAllIndices, + }).catch(() => []); + requests.push(promiseSearch); + } - try { - const response = await http.get( - `/internal/index-pattern-management/resolve_index/${pattern}`, - { query } - ); - if (!response) { - return []; - } + const responses = await Promise.all(requests); - return responseToItemArray(response, getIndexTags); - } catch { - return []; + if (responses.length === 2) { + const [resolveResponse, searchResponse] = responses; + return dedupeMatchedItems(searchResponse, resolveResponse); + } else { + return responses[0]; } } diff --git a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx index 2768314a75860..6d14f787135be 100644 --- a/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx +++ b/src/plugins/index_pattern_management/public/components/index_pattern_table/index_pattern_table.tsx @@ -88,6 +88,7 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { application, http, getMlCardState, + data, } = useKibana().services; const [indexPatterns, setIndexPatterns] = useState([]); const [creationOptions, setCreationOptions] = useState([]); @@ -122,24 +123,26 @@ export const IndexPatternTable = ({ canSave, history }: Props) => { const removeAliases = (item: MatchedItem) => !((item as unknown) as ResolveIndexResponseItemAlias).indices; + const searchClient = data.search.search; + const loadSources = () => { - getIndices(http, () => [], '*', false).then((dataSources) => + getIndices({ http, pattern: '*', searchClient }).then((dataSources) => setSources(dataSources.filter(removeAliases)) ); - getIndices(http, () => [], '*:*', false).then((dataSources) => + getIndices({ http, pattern: '*:*', searchClient }).then((dataSources) => setRemoteClustersExist(!!dataSources.filter(removeAliases).length) ); }; useEffect(() => { - getIndices(http, () => [], '*', false).then((dataSources) => { + getIndices({ http, pattern: '*', searchClient }).then((dataSources) => { setSources(dataSources.filter(removeAliases)); setIsLoadingSources(false); }); - getIndices(http, () => [], '*:*', false).then((dataSources) => + getIndices({ http, pattern: '*:*', searchClient }).then((dataSources) => setRemoteClustersExist(!!dataSources.filter(removeAliases).length) ); - }, [http, creationOptions]); + }, [http, creationOptions, searchClient]); chrome.docTitle.change(title); diff --git a/src/plugins/kibana_overview/public/plugin.ts b/src/plugins/kibana_overview/public/plugin.ts index d2dec404fb4df..d37ac90e3926d 100644 --- a/src/plugins/kibana_overview/public/plugin.ts +++ b/src/plugins/kibana_overview/public/plugin.ts @@ -109,7 +109,7 @@ export class KibanaOverviewPlugin defaultMessage: 'Search and find insights.', }), i18n.translate('kibanaOverview.kibana.appDescription3', { - defaultMessage: 'Design pixel-perfect reports.', + defaultMessage: 'Design pixel-perfect presentations.', }), i18n.translate('kibanaOverview.kibana.appDescription4', { defaultMessage: 'Plot geographic data.', diff --git a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx index d788a25eb7d6f..fc4d111d2a049 100644 --- a/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx +++ b/src/plugins/saved_objects_management/public/management_section/saved_objects_table_page.tsx @@ -77,7 +77,9 @@ const SavedObjectsTablePage = ({ goInspectObject={(savedObject) => { const { editUrl } = savedObject.meta; if (editUrl) { - return coreStart.application.navigateToUrl('/app' + editUrl); + return coreStart.application.navigateToUrl( + coreStart.http.basePath.prepend(`/app${editUrl}`) + ); } }} canGoInApp={(savedObject) => { diff --git a/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx b/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx index f2eeedb5b7372..fff266bf964b0 100644 --- a/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx +++ b/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx @@ -32,7 +32,7 @@ import React, { useState } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; export const defaultAlertTitle = i18n.translate('security.checkup.insecureClusterTitle', { - defaultMessage: 'Please secure your installation', + defaultMessage: 'Your data is not secure', }); export const defaultAlertText: (onDismiss: (persist: boolean) => void) => MountPoint = ( @@ -47,7 +47,7 @@ export const defaultAlertText: (onDismiss: (persist: boolean) => void) => MountP @@ -66,7 +66,7 @@ export const defaultAlertText: (onDismiss: (persist: boolean) => void) => MountP size="s" color="primary" fill - href="https://www.elastic.co/what-is/elastic-stack-security" + href="https://www.elastic.co/what-is/elastic-stack-security?blade=kibanasecuritymessage" target="_blank" > {i18n.translate('security.checkup.learnMoreButtonText', { diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap index 7357598c8495f..21b579bce898a 100644 --- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap +++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap @@ -1,5 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`TelemetryManagementSectionComponent does not show the endpoint link when isSecurityExampleEnabled returns false 1`] = ` + +

+ + + , + } + } + /> +

+

+ + + , + } + } + /> +

+
+`; + exports[`TelemetryManagementSectionComponent renders as expected 1`] = ` , "endpointSecurityData": { it('renders as expected', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -51,6 +52,7 @@ describe('TelemetryManagementSectionComponent', () => { onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={true} enableSaving={true} + isSecurityExampleEnabled={isSecurityExampleEnabled} toasts={coreStart.notifications.toasts} /> ) @@ -59,6 +61,7 @@ describe('TelemetryManagementSectionComponent', () => { it('renders null because query does not match the SEARCH_TERMS', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -81,6 +84,7 @@ describe('TelemetryManagementSectionComponent', () => { onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={false} enableSaving={true} + isSecurityExampleEnabled={isSecurityExampleEnabled} toasts={coreStart.notifications.toasts} /> @@ -96,6 +100,7 @@ describe('TelemetryManagementSectionComponent', () => { showAppliesSettingMessage={false} enableSaving={true} toasts={coreStart.notifications.toasts} + isSecurityExampleEnabled={isSecurityExampleEnabled} /> ); @@ -108,6 +113,7 @@ describe('TelemetryManagementSectionComponent', () => { it('renders because query matches the SEARCH_TERMS', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -128,6 +134,7 @@ describe('TelemetryManagementSectionComponent', () => { telemetryService={telemetryService} onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={false} + isSecurityExampleEnabled={isSecurityExampleEnabled} enableSaving={true} toasts={coreStart.notifications.toasts} /> @@ -152,6 +159,7 @@ describe('TelemetryManagementSectionComponent', () => { it('renders null because allowChangingOptInStatus is false', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -173,6 +181,7 @@ describe('TelemetryManagementSectionComponent', () => { onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={true} enableSaving={true} + isSecurityExampleEnabled={isSecurityExampleEnabled} toasts={coreStart.notifications.toasts} /> ); @@ -187,6 +196,7 @@ describe('TelemetryManagementSectionComponent', () => { it('shows the OptInExampleFlyout', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -208,6 +218,7 @@ describe('TelemetryManagementSectionComponent', () => { onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={false} enableSaving={true} + isSecurityExampleEnabled={isSecurityExampleEnabled} toasts={coreStart.notifications.toasts} /> ); @@ -223,6 +234,7 @@ describe('TelemetryManagementSectionComponent', () => { it('shows the OptInSecurityExampleFlyout', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -243,6 +255,7 @@ describe('TelemetryManagementSectionComponent', () => { telemetryService={telemetryService} onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={false} + isSecurityExampleEnabled={isSecurityExampleEnabled} enableSaving={true} toasts={coreStart.notifications.toasts} /> @@ -257,8 +270,47 @@ describe('TelemetryManagementSectionComponent', () => { } }); + it('does not show the endpoint link when isSecurityExampleEnabled returns false', () => { + const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(false); + const telemetryService = new TelemetryService({ + config: { + enabled: true, + url: '', + banner: true, + allowChangingOptInStatus: true, + optIn: false, + optInStatusUrl: '', + sendUsageFrom: 'browser', + }, + reportOptInStatusChange: false, + notifications: coreStart.notifications, + http: coreSetup.http, + }); + + const component = mountWithIntl( + + ); + + try { + const description = (component.instance() as TelemetryManagementSection).renderDescription(); + expect(isSecurityExampleEnabled).toBeCalled(); + expect(description).toMatchSnapshot(); + } finally { + component.unmount(); + } + }); + it('toggles the OptIn button', async () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -280,6 +332,7 @@ describe('TelemetryManagementSectionComponent', () => { onQueryMatchChange={onQueryMatchChange} showAppliesSettingMessage={false} enableSaving={true} + isSecurityExampleEnabled={isSecurityExampleEnabled} toasts={coreStart.notifications.toasts} /> ); @@ -304,6 +357,7 @@ describe('TelemetryManagementSectionComponent', () => { it('test the wrapper (for coverage purposes)', () => { const onQueryMatchChange = jest.fn(); + const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { enabled: true, @@ -327,6 +381,7 @@ describe('TelemetryManagementSectionComponent', () => { onQueryMatchChange={onQueryMatchChange} enableSaving={true} toasts={coreStart.notifications.toasts} + isSecurityExampleEnabled={isSecurityExampleEnabled} /> ).html() ).toMatchSnapshot(); diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx index 822d8b49661c1..c43b600597c57 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.tsx @@ -45,6 +45,7 @@ const SEARCH_TERMS = ['telemetry', 'usage', 'data', 'usage data']; interface Props { telemetryService: TelemetryService; onQueryMatchChange: (searchTermMatches: boolean) => void; + isSecurityExampleEnabled: () => boolean; showAppliesSettingMessage: boolean; enableSaving: boolean; query?: any; @@ -89,8 +90,9 @@ export class TelemetryManagementSection extends Component { } render() { - const { telemetryService } = this.props; + const { telemetryService, isSecurityExampleEnabled } = this.props; const { showExample, showSecurityExample, queryMatches, enabled, processing } = this.state; + const securityExampleEnabled = isSecurityExampleEnabled(); if (!telemetryService.getCanChangeOptInStatus()) { return null; @@ -108,7 +110,9 @@ export class TelemetryManagementSection extends Component { onClose={this.toggleExample} /> )} - {showSecurityExample && } + {showSecurityExample && securityExampleEnabled && ( + + )} @@ -181,48 +185,63 @@ export class TelemetryManagementSection extends Component { ); }; - renderDescription = () => ( - -

- - - - ), - }} - /> -

-

- - - - ), - endpointSecurityData: ( - - - - ), - }} - /> -

-
- ); + renderDescription = () => { + const { isSecurityExampleEnabled } = this.props; + const securityExampleEnabled = isSecurityExampleEnabled(); + const clusterDataLink = ( + + + + ); + + const endpointSecurityDataLink = ( + + + + ); + + return ( + +

+ + + + ), + }} + /> +

+

+ {securityExampleEnabled ? ( + + ) : ( + + )} +

+
+ ); + }; toggleOptIn = async (): Promise => { const { telemetryService, toasts } = this.props; @@ -264,6 +283,9 @@ export class TelemetryManagementSection extends Component { }; toggleSecurityExample = () => { + const { isSecurityExampleEnabled } = this.props; + const securityExampleEnabled = isSecurityExampleEnabled(); + if (!securityExampleEnabled) return; this.setState({ showSecurityExample: !this.state.showSecurityExample, }); diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section_wrapper.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section_wrapper.tsx index f61268c4772a3..95acbaba38845 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section_wrapper.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section_wrapper.tsx @@ -28,13 +28,15 @@ type Props = any; const TelemetryManagementSectionComponent = lazy(() => import('./telemetry_management_section')); export function telemetryManagementSectionWrapper( - telemetryService: TelemetryPluginSetup['telemetryService'] + telemetryService: TelemetryPluginSetup['telemetryService'], + shouldShowSecuritySolutionUsageExample: () => boolean ) { const TelemetryManagementSectionWrapper = (props: Props) => ( }> diff --git a/src/plugins/telemetry_management_section/public/index.ts b/src/plugins/telemetry_management_section/public/index.ts index 082f68809a67e..f3aef9eca750d 100644 --- a/src/plugins/telemetry_management_section/public/index.ts +++ b/src/plugins/telemetry_management_section/public/index.ts @@ -16,9 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -export { OptInExampleFlyout } from './components'; import { TelemetryManagementSectionPlugin } from './plugin'; +export { OptInExampleFlyout } from './components'; + +export { TelemetryManagementSectionPluginSetup } from './plugin'; export function plugin() { return new TelemetryManagementSectionPlugin(); } diff --git a/src/plugins/telemetry_management_section/public/plugin.tsx b/src/plugins/telemetry_management_section/public/plugin.tsx index 738b38c36d30d..c200e830c8f61 100644 --- a/src/plugins/telemetry_management_section/public/plugin.tsx +++ b/src/plugins/telemetry_management_section/public/plugin.tsx @@ -38,16 +38,32 @@ export interface TelemetryManagementSectionPluginDepsSetup { advancedSettings: AdvancedSettingsSetup; } -export class TelemetryManagementSectionPlugin implements Plugin { +export interface TelemetryManagementSectionPluginSetup { + toggleSecuritySolutionExample: (enabled: boolean) => void; +} + +export class TelemetryManagementSectionPlugin + implements Plugin { + private showSecuritySolutionExample = false; + private shouldShowSecuritySolutionExample = () => { + return this.showSecuritySolutionExample; + }; + public setup( core: CoreSetup, { advancedSettings, telemetry: { telemetryService } }: TelemetryManagementSectionPluginDepsSetup ) { advancedSettings.component.register( advancedSettings.component.componentType.PAGE_FOOTER_COMPONENT, - telemetryManagementSectionWrapper(telemetryService), + telemetryManagementSectionWrapper(telemetryService, this.shouldShowSecuritySolutionExample), true ); + + return { + toggleSecuritySolutionExample: (enabled: boolean) => { + this.showSecuritySolutionExample = enabled; + }, + }; } public start(core: CoreStart) {} diff --git a/src/plugins/timelion/public/flot/jquery.flot.js b/src/plugins/timelion/public/flot/jquery.flot.js deleted file mode 100644 index 5d613037cf234..0000000000000 --- a/src/plugins/timelion/public/flot/jquery.flot.js +++ /dev/null @@ -1,3168 +0,0 @@ -/* JavaScript plotting library for jQuery, version 0.8.3. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -*/ - -// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience - -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ -(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); - -// the actual Flot code -(function($) { - - // Cache the prototype hasOwnProperty for faster access - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM - // operation produces the same effect as detach, i.e. removing the element - // without touching its jQuery data. - - // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+. - - if (!$.fn.detach) { - $.fn.detach = function() { - return this.each(function() { - if (this.parentNode) { - this.parentNode.removeChild( this ); - } - }); - }; - } - - /////////////////////////////////////////////////////////////////////////// - // The Canvas object is a wrapper around an HTML5 tag. - // - // @constructor - // @param {string} cls List of classes to apply to the canvas. - // @param {element} container Element onto which to append the canvas. - // - // Requiring a container is a little iffy, but unfortunately canvas - // operations don't work unless the canvas is attached to the DOM. - - function Canvas(cls, container) { - - var element = container.children("." + cls)[0]; - - if (element == null) { - - element = document.createElement("canvas"); - element.className = cls; - - $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) - .appendTo(container); - - // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas - - if (!element.getContext) { - if (window.G_vmlCanvasManager) { - element = window.G_vmlCanvasManager.initElement(element); - } else { - throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); - } - } - } - - this.element = element; - - var context = this.context = element.getContext("2d"); - - // Determine the screen's ratio of physical to device-independent - // pixels. This is the ratio between the canvas width that the browser - // advertises and the number of pixels actually present in that space. - - // The iPhone 4, for example, has a device-independent width of 320px, - // but its screen is actually 640px wide. It therefore has a pixel - // ratio of 2, while most normal devices have a ratio of 1. - - var devicePixelRatio = window.devicePixelRatio || 1, - backingStoreRatio = - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || 1; - - this.pixelRatio = devicePixelRatio / backingStoreRatio; - - // Size the canvas to match the internal dimensions of its container - - this.resize(container.width(), container.height()); - - // Collection of HTML div layers for text overlaid onto the canvas - - this.textContainer = null; - this.text = {}; - - // Cache of text fragments and metrics, so we can avoid expensively - // re-calculating them when the plot is re-rendered in a loop. - - this._textCache = {}; - } - - // Resizes the canvas to the given dimensions. - // - // @param {number} width New width of the canvas, in pixels. - // @param {number} width New height of the canvas, in pixels. - - Canvas.prototype.resize = function(width, height) { - - if (width <= 0 || height <= 0) { - throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); - } - - var element = this.element, - context = this.context, - pixelRatio = this.pixelRatio; - - // Resize the canvas, increasing its density based on the display's - // pixel ratio; basically giving it more pixels without increasing the - // size of its element, to take advantage of the fact that retina - // displays have that many more pixels in the same advertised space. - - // Resizing should reset the state (excanvas seems to be buggy though) - - if (this.width != width) { - element.width = width * pixelRatio; - element.style.width = width + "px"; - this.width = width; - } - - if (this.height != height) { - element.height = height * pixelRatio; - element.style.height = height + "px"; - this.height = height; - } - - // Save the context, so we can reset in case we get replotted. The - // restore ensure that we're really back at the initial state, and - // should be safe even if we haven't saved the initial state yet. - - context.restore(); - context.save(); - - // Scale the coordinate space to match the display density; so even though we - // may have twice as many pixels, we still want lines and other drawing to - // appear at the same size; the extra pixels will just make them crisper. - - context.scale(pixelRatio, pixelRatio); - }; - - // Clears the entire canvas area, not including any overlaid HTML text - - Canvas.prototype.clear = function() { - this.context.clearRect(0, 0, this.width, this.height); - }; - - // Finishes rendering the canvas, including managing the text overlay. - - Canvas.prototype.render = function() { - - var cache = this._textCache; - - // For each text layer, add elements marked as active that haven't - // already been rendered, and remove those that are no longer active. - - for (var layerKey in cache) { - if (hasOwnProperty.call(cache, layerKey)) { - - var layer = this.getTextLayer(layerKey), - layerCache = cache[layerKey]; - - layer.hide(); - - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length == 0) { - delete styleCache[key]; - } - } - } - } - } - - layer.show(); - } - } - }; - - // Creates (if necessary) and returns the text overlay container. - // - // @param {string} classes String of space-separated CSS classes used to - // uniquely identify the text layer. - // @return {object} The jQuery-wrapped text-layer div. - - Canvas.prototype.getTextLayer = function(classes) { - - var layer = this.text[classes]; - - // Create the text layer if it doesn't exist - - if (layer == null) { - - // Create the text layer container, if it doesn't exist - - if (this.textContainer == null) { - this.textContainer = $("
") - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0, - 'font-size': "smaller", - color: "#545454" - }) - .insertAfter(this.element); - } - - layer = this.text[classes] = $("
") - .addClass(classes) - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0 - }) - .appendTo(this.textContainer); - } - - return layer; - }; - - // Creates (if necessary) and returns a text info object. - // - // The object looks like this: - // - // { - // width: Width of the text's wrapper div. - // height: Height of the text's wrapper div. - // element: The jQuery-wrapped HTML div containing the text. - // positions: Array of positions at which this text is drawn. - // } - // - // The positions array contains objects that look like this: - // - // { - // active: Flag indicating whether the text should be visible. - // rendered: Flag indicating whether the text is currently visible. - // element: The jQuery-wrapped HTML div containing the text. - // x: X coordinate at which to draw the text. - // y: Y coordinate at which to draw the text. - // } - // - // Each position after the first receives a clone of the original element. - // - // The idea is that that the width, height, and general 'identity' of the - // text is constant no matter where it is placed; the placements are a - // secondary property. - // - // Canvas maintains a cache of recently-used text info objects; getTextInfo - // either returns the cached element or creates a new entry. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {string} text Text string to retrieve info for. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @return {object} a text info object. - - Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such - - text = "" + text; - - // If the font is a font-spec object, generate a CSS font definition - - if (typeof font === "object") { - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; - } else { - textStyle = font; - } - - // Retrieve (or create) the cache for the text's layer and styles - - layerCache = this._textCache[layer]; - - if (layerCache == null) { - layerCache = this._textCache[layer] = {}; - } - - styleCache = layerCache[textStyle]; - - if (styleCache == null) { - styleCache = layerCache[textStyle] = {}; - } - - info = styleCache[text]; - - // If we can't find a matching element in our cache, create a new one - - if (info == null) { - - var element = $("
").html(text) - .css({ - position: "absolute", - 'max-width': width, - top: -9999 - }) - .appendTo(this.getTextLayer(layer)); - - if (typeof font === "object") { - element.css({ - font: textStyle, - color: font.color - }); - } else if (typeof font === "string") { - element.addClass(font); - } - - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), - element: element, - positions: [] - }; - - element.detach(); - } - - return info; - }; - - // Adds a text string to the canvas text overlay. - // - // The text isn't drawn immediately; it is marked as rendering, which will - // result in its addition to the canvas on the next render pass. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number} x X coordinate at which to draw the text. - // @param {number} y Y coordinate at which to draw the text. - // @param {string} text Text string to draw. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @param {string=} halign Horizontal alignment of the text; either "left", - // "center" or "right". - // @param {string=} valign Vertical alignment of the text; either "top", - // "middle" or "bottom". - - Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - - var info = this.getTextInfo(layer, text, font, angle, width), - positions = info.positions; - - // Tweak the div's position to match the text's alignment - - if (halign == "center") { - x -= info.width / 2; - } else if (halign == "right") { - x -= info.width; - } - - if (valign == "middle") { - y -= info.height / 2; - } else if (valign == "bottom") { - y -= info.height; - } - - // Determine whether this text already exists at this position. - // If so, mark it for inclusion in the next render pass. - - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = true; - return; - } - } - - // If the text doesn't exist at this position, create a new entry - - // For the very first position we'll re-use the original element, - // while for subsequent ones we'll clone it. - - position = { - active: true, - rendered: false, - element: positions.length ? info.element.clone() : info.element, - x: x, - y: y - }; - - positions.push(position); - - // Move the element to its final position within the container - - position.element.css({ - top: Math.round(y), - left: Math.round(x), - 'text-align': halign // In case the text wraps - }); - }; - - // Removes one or more text strings from the canvas text overlay. - // - // If no parameters are given, all text within the layer is removed. - // - // Note that the text is not immediately removed; it is simply marked as - // inactive, which will result in its removal on the next render pass. - // This avoids the performance penalty for 'clear and redraw' behavior, - // where we potentially get rid of all text on a layer, but will likely - // add back most or all of it later, as when redrawing axes, for example. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number=} x X coordinate of the text. - // @param {number=} y Y coordinate of the text. - // @param {string=} text Text string to remove. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which the text is rotated, in degrees. - // Angle is currently unused, it will be implemented in the future. - - Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { - if (text == null) { - var layerCache = this._textCache[layer]; - if (layerCache != null) { - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - var positions = styleCache[key].positions; - for (var i = 0, position; position = positions[i]; i++) { - position.active = false; - } - } - } - } - } - } - } else { - var positions = this.getTextInfo(layer, text, font, angle).positions; - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = false; - } - } - } - }; - - /////////////////////////////////////////////////////////////////////////// - // The top-level container for the entire plot. - - function Plot(placeholder, data_, options_, plugins) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of columns in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85, // set to 0 to avoid background - sorted: null // default to no legend sorting - }, - xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis - inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - reserveSpace: null, // whether to reserve space even if axis isn't shown - tickLength: null, // size in pixels of ticks, or "full" for whole line - alignTicksWithAxis: null, // axis number or null for no sync - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null // number or [number, "unit"] - }, - yaxis: { - autoscaleMargin: 0.02, - position: "left" // or "right" - }, - xaxes: [], - yaxes: [], - series: { - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff", - symbol: "circle" // or callback - }, - lines: { - // we don't put in show: false so we can see - // whether lines were actively disabled - lineWidth: 2, // in pixels - fill: false, - fillColor: null, - steps: false - // Omit 'zero', so we can later default its value to - // match that of the 'fill' option. - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left", // "left", "right", or "center" - horizontal: false, - zero: true - }, - shadowSize: 3, - highlightColor: null - }, - grid: { - show: true, - aboveData: false, - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - borderColor: null, // set if different from the grid color - tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" - margin: 0, // distance from the canvas edge to the grid - labelMargin: 5, // in pixels - axisMargin: 8, // in pixels - borderWidth: 2, // in pixels - minBorderMargin: null, // in pixels, null means taken from points radius - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - interaction: { - redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow - }, - hooks: {} - }, - surface = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - xaxes = [], yaxes = [], - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - plotWidth = 0, plotHeight = 0, - hooks = { - processOptions: [], - processRawData: [], - processDatapoints: [], - processOffset: [], - drawBackground: [], - drawSeries: [], - draw: [], - bindEvents: [], - drawOverlay: [], - shutdown: [] - }, - plot = this; - - // public functions - plot.setData = setData; - plot.setupGrid = setupGrid; - plot.draw = draw; - plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return surface.element; }; - plot.getPlotOffset = function() { return plotOffset; }; - plot.width = function () { return plotWidth; }; - plot.height = function () { return plotHeight; }; - plot.offset = function () { - var o = eventHolder.offset(); - o.left += plotOffset.left; - o.top += plotOffset.top; - return o; - }; - plot.getData = function () { return series; }; - plot.getAxes = function () { - var res = {}, i; - $.each(xaxes.concat(yaxes), function (_, axis) { - if (axis) - res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; - }); - return res; - }; - plot.getXAxes = function () { return xaxes; }; - plot.getYAxes = function () { return yaxes; }; - plot.c2p = canvasToAxisCoords; - plot.p2c = axisToCanvasCoords; - plot.getOptions = function () { return options; }; - plot.highlight = highlight; - plot.unhighlight = unhighlight; - plot.triggerRedrawOverlay = triggerRedrawOverlay; - plot.pointOffset = function(point) { - return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) - }; - }; - plot.shutdown = shutdown; - plot.destroy = function () { - shutdown(); - placeholder.removeData("plot").empty(); - - series = []; - options = null; - surface = null; - overlay = null; - eventHolder = null; - ctx = null; - octx = null; - xaxes = []; - yaxes = []; - hooks = null; - highlights = []; - plot = null; - }; - plot.resize = function () { - var width = placeholder.width(), - height = placeholder.height(); - surface.resize(width, height); - overlay.resize(width, height); - }; - - // public attributes - plot.hooks = hooks; - - // initialize - initPlugins(plot); - parseOptions(options_); - setupCanvases(); - setData(data_); - setupGrid(); - draw(); - bindEvents(); - - - function executeHooks(hook, args) { - args = [plot].concat(args); - for (var i = 0; i < hook.length; ++i) - hook[i].apply(this, args); - } - - function initPlugins() { - - // References to key classes, allowing plugins to modify them - - var classes = { - Canvas: Canvas - }; - - for (var i = 0; i < plugins.length; ++i) { - var p = plugins[i]; - p.init(plot, classes); - if (p.options) - $.extend(true, options, p.options); - } - } - - function parseOptions(opts) { - - $.extend(true, options, opts); - - // $.extend merges arrays, rather than replacing them. When less - // colors are provided than the size of the default palette, we - // end up with those colors plus the remaining defaults, which is - // not expected behavior; avoid it by replacing them here. - - if (opts && opts.colors) { - options.colors = opts.colors; - } - - if (options.xaxis.color == null) - options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - if (options.yaxis.color == null) - options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility - options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; - if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility - options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; - - if (options.grid.borderColor == null) - options.grid.borderColor = options.grid.color; - if (options.grid.tickColor == null) - options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // Fill in defaults for axis options, including any unspecified - // font-spec fields, if a font-spec was provided. - - // If no x/y axis options were provided, create one of each anyway, - // since the rest of the code assumes that they exist. - - var i, axisOptions, axisCount, - fontSize = placeholder.css("font-size"), - fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, - fontDefaults = { - style: placeholder.css("font-style"), - size: Math.round(0.8 * fontSizeDefault), - variant: placeholder.css("font-variant"), - weight: placeholder.css("font-weight"), - family: placeholder.css("font-family") - }; - - axisCount = options.xaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.xaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.xaxis, axisOptions); - options.xaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - axisCount = options.yaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.yaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.yaxis, axisOptions); - options.yaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.x2axis) { - options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); - options.xaxes[1].position = "top"; - // Override the inherit to allow the axis to auto-scale - if (options.x2axis.min == null) { - options.xaxes[1].min = null; - } - if (options.x2axis.max == null) { - options.xaxes[1].max = null; - } - } - if (options.y2axis) { - options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); - options.yaxes[1].position = "right"; - // Override the inherit to allow the axis to auto-scale - if (options.y2axis.min == null) { - options.yaxes[1].min = null; - } - if (options.y2axis.max == null) { - options.yaxes[1].max = null; - } - } - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - if (options.lines) - $.extend(true, options.series.lines, options.lines); - if (options.points) - $.extend(true, options.series.points, options.points); - if (options.bars) - $.extend(true, options.series.bars, options.bars); - if (options.shadowSize != null) - options.series.shadowSize = options.shadowSize; - if (options.highlightColor != null) - options.series.highlightColor = options.highlightColor; - - // save options on axes for future reference - for (i = 0; i < options.xaxes.length; ++i) - getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; - for (i = 0; i < options.yaxes.length; ++i) - getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; - - // add hooks from options - for (var n in hooks) - if (options.hooks[n] && options.hooks[n].length) - hooks[n] = hooks[n].concat(options.hooks[n]); - - executeHooks(hooks.processOptions, [options]); - } - - function setData(d) { - series = parseData(d); - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s = $.extend(true, {}, options.series); - - if (d[i].data != null) { - s.data = d[i].data; // move the data instead of deep-copy - delete d[i].data; - - $.extend(true, s, d[i]); - - d[i].data = s.data; - } - else - s.data = d[i]; - res.push(s); - } - - return res; - } - - function axisNumber(obj, coord) { - var a = obj[coord + "axis"]; - if (typeof a == "object") // if we got a real axis, extract number - a = a.n; - if (typeof a != "number") - a = 1; // default to first axis - return a; - } - - function allAxes() { - // return flat array without annoying null entries - return $.grep(xaxes.concat(yaxes), function (a) { return a; }); - } - - function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes - var res = {}, i, axis; - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) - res["x" + axis.n] = axis.c2p(pos.left); - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) - res["y" + axis.n] = axis.c2p(pos.top); - } - - if (res.x1 !== undefined) - res.x = res.x1; - if (res.y1 !== undefined) - res.y = res.y1; - - return res; - } - - function axisToCanvasCoords(pos) { - // get canvas coords from the first pair of x/y found in pos - var res = {}, i, axis, key; - - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) { - key = "x" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "x"; - - if (pos[key] != null) { - res.left = axis.p2c(pos[key]); - break; - } - } - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) { - key = "y" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "y"; - - if (pos[key] != null) { - res.top = axis.p2c(pos[key]); - break; - } - } - } - - return res; - } - - function getOrCreateAxis(axes, number) { - if (!axes[number - 1]) - axes[number - 1] = { - n: number, // save the number for future reference - direction: axes == xaxes ? "x" : "y", - options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) - }; - - return axes[number - 1]; - } - - function fillInSeriesOptions() { - - var neededColors = series.length, maxIndex = -1, i; - - // Subtract the number of series that already have fixed colors or - // color indexes from the number that we still need to generate. - - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - neededColors--; - if (typeof sc == "number" && sc > maxIndex) { - maxIndex = sc; - } - } - } - - // If any of the series have fixed color indexes, then we need to - // generate at least as many colors as the highest index. - - if (neededColors <= maxIndex) { - neededColors = maxIndex + 1; - } - - // Generate all the colors, using first the option colors and then - // variations on those colors once they're exhausted. - - var c, colors = [], colorPool = options.colors, - colorPoolSize = colorPool.length, variation = 0; - - for (i = 0; i < neededColors; i++) { - - c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); - - // Each time we exhaust the colors in the pool we adjust - // a scaling factor used to produce more variations on - // those colors. The factor alternates negative/positive - // to produce lighter/darker colors. - - // Reset the variation after every few cycles, or else - // it will end up producing only white or black colors. - - if (i % colorPoolSize == 0 && i) { - if (variation >= 0) { - if (variation < 0.5) { - variation = -variation - 0.2; - } else variation = 0; - } else variation = -variation; - } - - colors[i] = c.scale('rgb', 1 + variation); - } - - // Finalize the series options, filling in their colors - - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // turn on lines automatically in case nothing is set - if (s.lines.show == null) { - var v, show = true; - for (v in s) - if (s[v] && s[v].show) { - show = false; - break; - } - if (show) - s.lines.show = true; - } - - // If nothing was provided for lines.zero, default it to match - // lines.fill, since areas by default should extend to zero. - - if (s.lines.zero == null) { - s.lines.zero = !!s.lines.fill; - } - - // setup axes - s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); - s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - fakeInfinity = Number.MAX_VALUE, - i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p, - data, format; - - function updateAxis(axis, min, max) { - if (min < axis.datamin && min != -fakeInfinity) - axis.datamin = min; - if (max > axis.datamax && max != fakeInfinity) - axis.datamax = max; - } - - $.each(allAxes(), function (_, axis) { - // init axis - axis.datamin = topSentry; - axis.datamax = bottomSentry; - axis.used = false; - }); - - for (i = 0; i < series.length; ++i) { - s = series[i]; - s.datapoints = { points: [] }; - - executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); - } - - // first pass: clean and copy data - for (i = 0; i < series.length; ++i) { - s = series[i]; - - data = s.data; - format = s.datapoints.format; - - if (!format) { - format = []; - // find out how to copy - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); - format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - s.datapoints.format = format; - } - - if (s.datapoints.pointsize != null) - continue; // already filled in - - s.datapoints.pointsize = format.length; - - ps = s.datapoints.pointsize; - points = s.datapoints.points; - - var insertSteps = s.lines.show && s.lines.steps; - s.xaxis.used = s.yaxis.used = true; - - for (j = k = 0; j < data.length; ++j, k += ps) { - p = data[j]; - - var nullify = p == null; - if (!nullify) { - for (m = 0; m < ps; ++m) { - val = p[m]; - f = format[m]; - - if (f) { - if (f.number && val != null) { - val = +val; // convert to number - if (isNaN(val)) - val = null; - else if (val == Infinity) - val = fakeInfinity; - else if (val == -Infinity) - val = -fakeInfinity; - } - - if (val == null) { - if (f.required) - nullify = true; - - if (f.defaultValue != null) - val = f.defaultValue; - } - } - - points[k + m] = val; - } - } - - if (nullify) { - for (m = 0; m < ps; ++m) { - val = points[k + m]; - if (val != null) { - f = format[m]; - // extract min/max info - if (f.autoscale !== false) { - if (f.x) { - updateAxis(s.xaxis, val, val); - } - if (f.y) { - updateAxis(s.yaxis, val, val); - } - } - } - points[k + m] = null; - } - } - else { - // a little bit of line specific stuff that - // perhaps shouldn't be here, but lacking - // better means... - if (insertSteps && k > 0 - && points[k - ps] != null - && points[k - ps] != points[k] - && points[k - ps + 1] != points[k + 1]) { - // copy the point to make room for a middle point - for (m = 0; m < ps; ++m) - points[k + ps + m] = points[k + m]; - - // middle point has same y - points[k + 1] = points[k - ps + 1]; - - // we've added a point, better reflect that - k += ps; - } - } - } - } - - // give the hooks a chance to run - for (i = 0; i < series.length; ++i) { - s = series[i]; - - executeHooks(hooks.processDatapoints, [ s, s.datapoints]); - } - - // second pass: find datamax/datamin for auto-scaling - for (i = 0; i < series.length; ++i) { - s = series[i]; - points = s.datapoints.points; - ps = s.datapoints.pointsize; - format = s.datapoints.format; - - var xmin = topSentry, ymin = topSentry, - xmax = bottomSentry, ymax = bottomSentry; - - for (j = 0; j < points.length; j += ps) { - if (points[j] == null) - continue; - - for (m = 0; m < ps; ++m) { - val = points[j + m]; - f = format[m]; - if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) - continue; - - if (f.x) { - if (val < xmin) - xmin = val; - if (val > xmax) - xmax = val; - } - if (f.y) { - if (val < ymin) - ymin = val; - if (val > ymax) - ymax = val; - } - } - } - - if (s.bars.show) { - // make sure we got room for the bar on the dancing floor - var delta; - - switch (s.bars.align) { - case "left": - delta = 0; - break; - case "right": - delta = -s.bars.barWidth; - break; - default: - delta = -s.bars.barWidth / 2; - } - - if (s.bars.horizontal) { - ymin += delta; - ymax += delta + s.bars.barWidth; - } - else { - xmin += delta; - xmax += delta + s.bars.barWidth; - } - } - - updateAxis(s.xaxis, xmin, xmax); - updateAxis(s.yaxis, ymin, ymax); - } - - $.each(allAxes(), function (_, axis) { - if (axis.datamin == topSentry) - axis.datamin = null; - if (axis.datamax == bottomSentry) - axis.datamax = null; - }); - } - - function setupCanvases() { - - // Make sure the placeholder is clear of everything except canvases - // from a previous plot in this container that we'll try to re-use. - - placeholder.css("padding", 0) // padding messes up the positioning - .children().filter(function(){ - return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); - }).remove(); - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - surface = new Canvas("flot-base", placeholder); - overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features - - ctx = surface.context; - octx = overlay.context; - - // define which element we're listening for events on - eventHolder = $(overlay.element).unbind(); - - // If we're re-using a plot object, shut down the old one - - var existing = placeholder.data("plot"); - - if (existing) { - existing.shutdown(); - overlay.clear(); - } - - // save in case we get replotted - placeholder.data("plot", plot); - } - - function bindEvents() { - // bind events - if (options.grid.hoverable) { - eventHolder.mousemove(onMouseMove); - - // Use bind, rather than .mouseleave, because we officially - // still support jQuery 1.2.6, which doesn't define a shortcut - // for mouseenter or mouseleave. This was a bug/oversight that - // was fixed somewhere around 1.3.x. We can return to using - // .mouseleave when we drop support for 1.2.6. - - eventHolder.bind("mouseleave", onMouseLeave); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - - executeHooks(hooks.bindEvents, [eventHolder]); - } - - function shutdown() { - if (redrawTimeout) - clearTimeout(redrawTimeout); - - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mouseleave", onMouseLeave); - eventHolder.unbind("click", onClick); - - executeHooks(hooks.shutdown, [eventHolder]); - } - - function setTransformationHelpers(axis) { - // set helper functions on the axis, assumes plot area - // has been computed already - - function identity(x) { return x; } - - var s, m, t = axis.options.transform || identity, - it = axis.options.inverseTransform; - - // precompute how much the axis is scaling a point - // in canvas space - if (axis.direction == "x") { - s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); - m = Math.min(t(axis.max), t(axis.min)); - } - else { - s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); - s = -s; - m = Math.max(t(axis.max), t(axis.min)); - } - - // data point to canvas coordinate - if (t == identity) // slight optimization - axis.p2c = function (p) { return (p - m) * s; }; - else - axis.p2c = function (p) { return (t(p) - m) * s; }; - // canvas coordinate to data point - if (!it) - axis.c2p = function (c) { return m + c / s; }; - else - axis.c2p = function (c) { return it(m + c / s); }; - } - - function measureTickLabels(axis) { - - var opts = axis.options, - ticks = axis.ticks || [], - labelWidth = opts.labelWidth || 0, - labelHeight = opts.labelHeight || 0, - maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = opts.font || "flot-tick-label tickLabel"; - - for (var i = 0; i < ticks.length; ++i) { - - var t = ticks[i]; - - if (!t.label) - continue; - - var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - - labelWidth = Math.max(labelWidth, info.width); - labelHeight = Math.max(labelHeight, info.height); - } - - axis.labelWidth = opts.labelWidth || labelWidth; - axis.labelHeight = opts.labelHeight || labelHeight; - } - - function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset; this first phase only looks at one - // dimension per axis, the other dimension depends on the - // other axes so will have to wait - - var lw = axis.labelWidth, - lh = axis.labelHeight, - pos = axis.options.position, - isXAxis = axis.direction === "x", - tickLength = axis.options.tickLength, - axisMargin = options.grid.axisMargin, - padding = options.grid.labelMargin, - innermost = true, - outermost = true, - first = true, - found = false; - - // Determine the axis's position in its direction and on its side - - $.each(isXAxis ? xaxes : yaxes, function(i, a) { - if (a && (a.show || a.reserveSpace)) { - if (a === axis) { - found = true; - } else if (a.options.position === pos) { - if (found) { - outermost = false; - } else { - innermost = false; - } - } - if (!found) { - first = false; - } - } - }); - - // The outermost axis on each side has no margin - - if (outermost) { - axisMargin = 0; - } - - // The ticks for the first axis in each direction stretch across - - if (tickLength == null) { - tickLength = first ? "full" : 5; - } - - if (!isNaN(+tickLength)) - padding += +tickLength; - - if (isXAxis) { - lh += padding; - - if (pos == "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; - } - else { - axis.box = { top: plotOffset.top + axisMargin, height: lh }; - plotOffset.top += lh + axisMargin; - } - } - else { - lw += padding; - - if (pos == "left") { - axis.box = { left: plotOffset.left + axisMargin, width: lw }; - plotOffset.left += lw + axisMargin; - } - else { - plotOffset.right += lw + axisMargin; - axis.box = { left: surface.width - plotOffset.right, width: lw }; - } - } - - // save for future reference - axis.position = pos; - axis.tickLength = tickLength; - axis.box.padding = padding; - axis.innermost = innermost; - } - - function allocateAxisBoxSecondPhase(axis) { - // now that all axis boxes have been placed in one - // dimension, we can set the remaining dimension coordinates - if (axis.direction == "x") { - axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; - } - else { - axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; - } - } - - function adjustLayoutForThingsStickingOut() { - // possibly adjust plot offset to ensure everything stays - // inside the canvas and isn't clipped off - - var minMargin = options.grid.minBorderMargin, - axis, i; - - // check stuff from the plot (FIXME: this should just read - // a value from the series, otherwise it's impossible to - // customize) - if (minMargin == null) { - minMargin = 0; - for (i = 0; i < series.length; ++i) - minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); - } - - var margins = { - left: minMargin, - right: minMargin, - top: minMargin, - bottom: minMargin - }; - - // check axis labels, note we don't check the actual - // labels but instead use the overall width/height to not - // jump as much around with replots - $.each(allAxes(), function (_, axis) { - if (axis.reserveSpace && axis.ticks && axis.ticks.length) { - if (axis.direction === "x") { - margins.left = Math.max(margins.left, axis.labelWidth / 2); - margins.right = Math.max(margins.right, axis.labelWidth / 2); - } else { - margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); - margins.top = Math.max(margins.top, axis.labelHeight / 2); - } - } - }); - - plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); - plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); - plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); - plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); - } - - function setupGrid() { - var i, axes = allAxes(), showGrid = options.grid.show; - - // Initialize the plot's offset from the edge of the canvas - - for (var a in plotOffset) { - var margin = options.grid.margin || 0; - plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; - } - - executeHooks(hooks.processOffset, [plotOffset]); - - // If the grid is visible, add its border width to the offset - - for (var a in plotOffset) { - if(typeof(options.grid.borderWidth) == "object") { - plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; - } - else { - plotOffset[a] += showGrid ? options.grid.borderWidth : 0; - } - } - - $.each(axes, function (_, axis) { - var axisOpts = axis.options; - axis.show = axisOpts.show == null ? axis.used : axisOpts.show; - axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; - setRange(axis); - }); - - if (showGrid) { - - var allocatedAxes = $.grep(axes, function (axis) { - return axis.show || axis.reserveSpace; - }); - - $.each(allocatedAxes, function (_, axis) { - // make the ticks - setupTickGeneration(axis); - setTicks(axis); - snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis - measureTickLabels(axis); - }); - - // with all dimensions calculated, we can compute the - // axis bounding boxes, start from the outside - // (reverse order) - for (i = allocatedAxes.length - 1; i >= 0; --i) - allocateAxisBoxFirstPhase(allocatedAxes[i]); - - // make sure we've got enough space for things that - // might stick out - adjustLayoutForThingsStickingOut(); - - $.each(allocatedAxes, function (_, axis) { - allocateAxisBoxSecondPhase(axis); - }); - } - - plotWidth = surface.width - plotOffset.left - plotOffset.right; - plotHeight = surface.height - plotOffset.bottom - plotOffset.top; - - // now we got the proper plot dimensions, we can compute the scaling - $.each(axes, function (_, axis) { - setTransformationHelpers(axis); - }); - - if (showGrid) { - drawAxisLabels(); - } - - insertLegend(); - } - - function setRange(axis) { - var opts = axis.options, - min = +(opts.min != null ? opts.min : axis.datamin), - max = +(opts.max != null ? opts.max : axis.datamax), - delta = max - min; - - if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; - - if (opts.min == null) - min -= widen; - // always widen max if we couldn't widen min to ensure we - // don't fall into min == max which doesn't work - if (opts.max == null || opts.min != null) - max += widen; - } - else { - // consider autoscaling - var margin = opts.autoscaleMargin; - if (margin != null) { - if (opts.min == null) { - min -= delta * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin != null && axis.datamin >= 0) - min = 0; - } - if (opts.max == null) { - max += delta * margin; - if (max > 0 && axis.datamax != null && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function setupTickGeneration(axis) { - var opts = axis.options; - - // estimate number of ticks - var noTicks; - if (typeof opts.ticks == "number" && opts.ticks > 0) - noTicks = opts.ticks; - else - // heuristic based on the model a*sqrt(x) fitted to - // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); - - var delta = (axis.max - axis.min) / noTicks, - dec = -Math.floor(Math.log(delta) / Math.LN10), - maxDec = opts.tickDecimals; - - if (maxDec != null && dec > maxDec) { - dec = maxDec; - } - - var magn = Math.pow(10, -dec), - norm = delta / magn, // norm is between 1.0 and 10.0 - size; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) { - size = opts.minTickSize; - } - - axis.delta = delta; - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; - - // Time mode was moved to a plug-in in 0.8, and since so many people use it - // we'll add an especially friendly reminder to make sure they included it. - - if (opts.mode == "time" && !axis.tickGenerator) { - throw new Error("Time mode requires the flot.time plugin."); - } - - // Flot supports base-10 axes; any other mode else is handled by a plug-in, - // like flot.time.js. - - if (!axis.tickGenerator) { - - axis.tickGenerator = function (axis) { - - var ticks = [], - start = floorInBase(axis.min, axis.tickSize), - i = 0, - v = Number.NaN, - prev; - - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push(v); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - axis.tickFormatter = function (value, axis) { - - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - var formatted = "" + Math.round(value * factor) / factor; - - // If tickDecimals was specified, ensure that we have exactly that - // much precision; otherwise default to the value's own precision. - - if (axis.tickDecimals != null) { - var decimal = formatted.indexOf("."); - var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - - return formatted; - }; - } - - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - - if (opts.alignTicksWithAxis != null) { - var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; - if (otherAxis && otherAxis.used && otherAxis != axis) { - // consider snapping min/max to outermost nice ticks - var niceTicks = axis.tickGenerator(axis); - if (niceTicks.length > 0) { - if (opts.min == null) - axis.min = Math.min(axis.min, niceTicks[0]); - if (opts.max == null && niceTicks.length > 1) - axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); - } - - axis.tickGenerator = function (axis) { - // copy ticks, scaled to this axis - var ticks = [], v, i; - for (i = 0; i < otherAxis.ticks.length; ++i) { - v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); - v = axis.min + v * (axis.max - axis.min); - ticks.push(v); - } - return ticks; - }; - - // we might need an extra decimal since forced - // ticks don't necessarily fit naturally - if (!axis.mode && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), - ts = axis.tickGenerator(axis); - - // only proceed if the tick interval rounded - // with an extra decimal doesn't give us a - // zero at end - if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) - axis.tickDecimals = extraDec; - } - } - } - } - - function setTicks(axis) { - var oticks = axis.options.ticks, ticks = []; - if (oticks == null || (typeof oticks == "number" && oticks > 0)) - ticks = axis.tickGenerator(axis); - else if (oticks) { - if ($.isFunction(oticks)) - // generate the ticks - ticks = oticks(axis); - else - ticks = oticks; - } - - // clean up/labelify the supplied ticks, copy them over - var i, v; - axis.ticks = []; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = +t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = +t; - if (label == null) - label = axis.tickFormatter(v, axis); - if (!isNaN(v)) - axis.ticks.push({ v: v, label: label }); - } - } - - function snapRangeToTicks(axis, ticks) { - if (axis.options.autoscaleMargin && ticks.length > 0) { - // snap to ticks - if (axis.options.min == null) - axis.min = Math.min(axis.min, ticks[0].v); - if (axis.options.max == null && ticks.length > 1) - axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); - } - } - - function draw() { - - surface.clear(); - - executeHooks(hooks.drawBackground, [ctx]); - - var grid = options.grid; - - // draw background, if any - if (grid.show && grid.backgroundColor) - drawBackground(); - - if (grid.show && !grid.aboveData) { - drawGrid(); - } - - for (var i = 0; i < series.length; ++i) { - executeHooks(hooks.drawSeries, [ctx, series[i]]); - drawSeries(series[i]); - } - - executeHooks(hooks.draw, [ctx]); - - if (grid.show && grid.aboveData) { - drawGrid(); - } - - surface.render(); - - // A draw implies that either the axes or data have changed, so we - // should probably update the overlay highlights as well. - - triggerRedrawOverlay(); - } - - function extractRange(ranges, coord) { - var axis, from, to, key, axes = allAxes(); - - for (var i = 0; i < axes.length; ++i) { - axis = axes[i]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? xaxes[0] : yaxes[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function drawBackground() { - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); - ctx.fillRect(0, 0, plotWidth, plotHeight); - ctx.restore(); - } - - function drawGrid() { - var i, axes, bw, bc; - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw markings - var markings = options.grid.markings; - if (markings) { - if ($.isFunction(markings)) { - axes = plot.getAxes(); - // xmin etc. is backwards compatibility, to be - // removed in the future - axes.xmin = axes.xaxis.min; - axes.xmax = axes.xaxis.max; - axes.ymin = axes.yaxis.min; - axes.ymax = axes.yaxis.max; - - markings = markings(axes); - } - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - var xequal = xrange.from === xrange.to, - yequal = yrange.from === yrange.to; - - if (xequal && yequal) { - continue; - } - - // then draw - xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); - xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); - yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); - yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); - - if (xequal || yequal) { - var lineWidth = m.lineWidth || options.grid.markingsLineWidth, - subPixel = lineWidth % 2 ? 0.5 : 0; - ctx.beginPath(); - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = lineWidth; - if (xequal) { - ctx.moveTo(xrange.to + subPixel, yrange.from); - ctx.lineTo(xrange.to + subPixel, yrange.to); - } else { - ctx.moveTo(xrange.from, yrange.to + subPixel); - ctx.lineTo(xrange.to, yrange.to + subPixel); - } - ctx.stroke(); - } else { - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(xrange.from, yrange.to, - xrange.to - xrange.from, - yrange.from - yrange.to); - } - } - } - - // draw the ticks - axes = allAxes(); - bw = options.grid.borderWidth; - - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box, - t = axis.tickLength, x, y, xoff, yoff; - if (!axis.show || axis.ticks.length == 0) - continue; - - ctx.lineWidth = 1; - - // find the edges - if (axis.direction == "x") { - x = 0; - if (t == "full") - y = (axis.position == "top" ? 0 : plotHeight); - else - y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); - } - else { - y = 0; - if (t == "full") - x = (axis.position == "left" ? 0 : plotWidth); - else - x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); - } - - // draw tick bar - if (!axis.innermost) { - ctx.strokeStyle = axis.options.color; - ctx.beginPath(); - xoff = yoff = 0; - if (axis.direction == "x") - xoff = plotWidth + 1; - else - yoff = plotHeight + 1; - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") { - y = Math.floor(y) + 0.5; - } else { - x = Math.floor(x) + 0.5; - } - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - ctx.stroke(); - } - - // draw ticks - - ctx.strokeStyle = axis.options.tickColor; - - ctx.beginPath(); - for (i = 0; i < axis.ticks.length; ++i) { - var v = axis.ticks[i].v; - - xoff = yoff = 0; - - if (isNaN(v) || v < axis.min || v > axis.max - // skip those lying on the axes if we got a border - || (t == "full" - && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) - && (v == axis.min || v == axis.max))) - continue; - - if (axis.direction == "x") { - x = axis.p2c(v); - yoff = t == "full" ? -plotHeight : t; - - if (axis.position == "top") - yoff = -yoff; - } - else { - y = axis.p2c(v); - xoff = t == "full" ? -plotWidth : t; - - if (axis.position == "left") - xoff = -xoff; - } - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") - x = Math.floor(x) + 0.5; - else - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - } - - ctx.stroke(); - } - - - // draw border - if (bw) { - // If either borderWidth or borderColor is an object, then draw the border - // line by line instead of as one rectangle - bc = options.grid.borderColor; - if(typeof bw == "object" || typeof bc == "object") { - if (typeof bw !== "object") { - bw = {top: bw, right: bw, bottom: bw, left: bw}; - } - if (typeof bc !== "object") { - bc = {top: bc, right: bc, bottom: bc, left: bc}; - } - - if (bw.top > 0) { - ctx.strokeStyle = bc.top; - ctx.lineWidth = bw.top; - ctx.beginPath(); - ctx.moveTo(0 - bw.left, 0 - bw.top/2); - ctx.lineTo(plotWidth, 0 - bw.top/2); - ctx.stroke(); - } - - if (bw.right > 0) { - ctx.strokeStyle = bc.right; - ctx.lineWidth = bw.right; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); - ctx.lineTo(plotWidth + bw.right / 2, plotHeight); - ctx.stroke(); - } - - if (bw.bottom > 0) { - ctx.strokeStyle = bc.bottom; - ctx.lineWidth = bw.bottom; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); - ctx.lineTo(0, plotHeight + bw.bottom / 2); - ctx.stroke(); - } - - if (bw.left > 0) { - ctx.strokeStyle = bc.left; - ctx.lineWidth = bw.left; - ctx.beginPath(); - ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); - ctx.lineTo(0- bw.left/2, 0); - ctx.stroke(); - } - } - else { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); - } - } - - ctx.restore(); - } - - function drawAxisLabels() { - - $.each(allAxes(), function (_, axis) { - var box = axis.box, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = axis.options.font || "flot-tick-label tickLabel", - tick, x, y, halign, valign; - - // Remove text before checking for axis.show and ticks.length; - // otherwise plugins, like flot-tickrotor, that draw their own - // tick labels will end up with both theirs and the defaults. - - surface.removeText(layer); - - if (!axis.show || axis.ticks.length == 0) - return; - - for (var i = 0; i < axis.ticks.length; ++i) { - - tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - - if (axis.direction == "x") { - halign = "center"; - x = plotOffset.left + axis.p2c(tick.v); - if (axis.position == "bottom") { - y = box.top + box.padding; - } else { - y = box.top + box.height - box.padding; - valign = "bottom"; - } - } else { - valign = "middle"; - y = plotOffset.top + axis.p2c(tick.v); - if (axis.position == "left") { - x = box.left + box.width - box.padding; - halign = "right"; - } else { - x = box.left + box.padding; - } - } - - surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); - } - }); - } - - function drawSeries(series) { - if (series.lines.show) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - prevx = null, prevy = null; - - ctx.beginPath(); - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1], - x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); - } - ctx.stroke(); - } - - function plotLineArea(datapoints, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - bottom = Math.min(Math.max(0, axisy.min), axisy.max), - i = 0, top, areaOpen = false, - ypos = 1, segmentStart = 0, segmentEnd = 0; - - // we process each segment in two turns, first forward - // direction to sketch out top, then once we hit the - // end we go backwards to sketch the bottom - while (true) { - if (ps > 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 2; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth, - sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (lw > 0 && sw > 0) { - // draw shadow as a thick and thin line with transparency - ctx.lineWidth = sw; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - // position shadow at angle from the mid of line - var angle = Math.PI/18; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); - ctx.lineWidth = sw/2; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); - if (fillStyle) { - ctx.fillStyle = fillStyle; - plotLineArea(series.datapoints, series.xaxis, series.yaxis); - } - - if (lw > 0) - plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var x = points[i], y = points[i + 1]; - if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - x = axisx.p2c(x); - y = axisy.p2c(y) + offset; - if (symbol == "circle") - ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); - else - symbol(ctx, x, y, radius, shadow); - ctx.closePath(); - - if (fillStyle) { - ctx.fillStyle = fillStyle; - ctx.fill(); - } - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.points.lineWidth, - sw = series.shadowSize, - radius = series.points.radius, - symbol = series.points.symbol; - - // If the user sets the line width to 0, we change it to a very - // small value. A line width of 0 seems to force the default of 1. - // Doing the conditional here allows the shadow setting to still be - // optional even with a lineWidth of 0. - - if( lw == 0 ) - lw = 0.0001; - - if (lw > 0 && sw > 0) { - // draw shadow in two steps - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPoints(series.datapoints, radius, null, w + w/2, true, - series.xaxis, series.yaxis, symbol); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPoints(series.datapoints, radius, null, w/2, true, - series.xaxis, series.yaxis, symbol); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - plotPoints(series.datapoints, radius, - getFillStyle(series.points, series.color), 0, false, - series.xaxis, series.yaxis, symbol); - ctx.restore(); - } - - function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { - var left, right, bottom, top, - drawLeft, drawRight, drawTop, drawBottom, - tmp; - - // in horizontal mode, we start the bar from the left - // instead of from the bottom so it appears to be - // horizontal rather than vertical - if (horizontal) { - drawBottom = drawRight = drawTop = true; - drawLeft = false; - left = b; - right = x; - top = y + barLeft; - bottom = y + barRight; - - // account for negative bars - if (right < left) { - tmp = right; - right = left; - left = tmp; - drawLeft = true; - drawRight = false; - } - } - else { - drawLeft = drawRight = drawTop = true; - drawBottom = false; - left = x + barLeft; - right = x + barRight; - bottom = b; - top = y; - - // account for negative bars - if (top < bottom) { - tmp = top; - top = bottom; - bottom = tmp; - drawBottom = true; - drawTop = false; - } - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - // fill the bar - if (fillStyleCallback) { - c.fillStyle = fillStyleCallback(bottom, top); - c.fillRect(left, top, right - left, bottom - top) - } - - // draw outline - if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { - c.beginPath(); - - // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom); - if (drawLeft) - c.lineTo(left, top); - else - c.moveTo(left, top); - if (drawTop) - c.lineTo(right, top); - else - c.moveTo(right, top); - if (drawRight) - c.lineTo(right, bottom); - else - c.moveTo(right, bottom); - if (drawBottom) - c.lineTo(left, bottom); - else - c.moveTo(left, bottom); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // FIXME: figure out a way to add shadows (for instance along the right edge) - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - - var barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); - ctx.restore(); - } - - function getFillStyle(filloptions, seriesColor, bottom, top) { - var fill = filloptions.fill; - if (!fill) - return null; - - if (filloptions.fillColor) - return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - - var c = $.color.parse(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - return c.toString(); - } - - function insertLegend() { - - if (options.legend.container != null) { - $(options.legend.container).html(""); - } else { - placeholder.find(".legend").remove(); - } - - if (!options.legend.show) { - return; - } - - var fragments = [], entries = [], rowStarted = false, - lf = options.legend.labelFormatter, s, label; - - // Build a list of legend entries, with each having a label and a color - - for (var i = 0; i < series.length; ++i) { - s = series[i]; - if (s.label) { - label = lf ? lf(s.label, s) : s.label; - if (label) { - entries.push({ - label: label, - color: s.color - }); - } - } - } - - // Sort the legend using either the default or a custom comparator - - if (options.legend.sorted) { - if ($.isFunction(options.legend.sorted)) { - entries.sort(options.legend.sorted); - } else if (options.legend.sorted == "reverse") { - entries.reverse(); - } else { - var ascending = options.legend.sorted != "descending"; - entries.sort(function(a, b) { - return a.label == b.label ? 0 : ( - (a.label < b.label) != ascending ? 1 : -1 // Logical XOR - ); - }); - } - } - - // Generate markup for the list of entries, in their final order - - for (var i = 0; i < entries.length; ++i) { - - var entry = entries[i]; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push(''); - fragments.push(''); - rowStarted = true; - } - - fragments.push( - '
' + - '' + entry.label + '' - ); - } - - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '' + fragments.join("") + '
'; - if (options.legend.container != null) - $(options.legend.container).html(table); - else { - var pos = "", - p = options.legend.position, - m = options.legend.margin; - if (m[0] == null) - m = [m, m]; - if (p.charAt(0) == "n") - pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; - var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - c = options.grid.backgroundColor; - if (c && typeof c == "string") - c = $.color.parse(c); - else - c = $.color.extract(legend, 'background-color'); - c.a = 1; - c = c.toString(); - } - var div = legend.children(); - $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } - } - } - - - // interactive features - - var highlights = [], - redrawTimeout = null; - - // returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY, seriesFilter) { - var maxDistance = options.grid.mouseActiveRadius, - smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j, ps; - - for (i = series.length - 1; i >= 0; --i) { - if (!seriesFilter(series[i])) - continue; - - var s = series[i], - axisx = s.xaxis, - axisy = s.yaxis, - points = s.datapoints.points, - mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale; - - ps = s.datapoints.pointsize; - // with inverse transforms, we can't use the maxx/maxy - // optimization, sadly - if (axisx.options.inverseTransform) - maxx = Number.MAX_VALUE; - if (axisy.options.inverseTransform) - maxy = Number.MAX_VALUE; - - if (s.lines.show || s.points.show) { - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1]; - if (x == null) - continue; - - // For points and lines, the cursor must be within a - // certain distance to the data point - if (x - mx > maxx || x - mx < -maxx || - y - my > maxy || y - my < -maxy) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scales of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; // we save the sqrt - - // use <= to ensure last point takes precedence - // (last generally means on top of) - if (dist < smallestDistance) { - smallestDistance = dist; - item = [i, j / ps]; - } - } - } - - if (s.bars.show && !item) { // no other point can be nearby - - var barLeft, barRight; - - switch (s.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -s.bars.barWidth; - break; - default: - barLeft = -s.bars.barWidth / 2; - } - - barRight = barLeft + s.bars.barWidth; - - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1], b = points[j + 2]; - if (x == null) - continue; - - // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && - my >= y + barLeft && my <= y + barRight) : - (mx >= x + barLeft && mx <= x + barRight && - my >= Math.min(b, y) && my <= Math.max(b, y))) - item = [i, j / ps]; - } - } - } - - if (item) { - i = item[0]; - j = item[1]; - ps = series[i].datapoints.pointsize; - - return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - return null; - } - - function onMouseMove(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return s["hoverable"] != false; }); - } - - function onMouseLeave(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return false; }); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e, - function (s) { return s["clickable"] != false; }); - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event, seriesFilter) { - var offset = eventHolder.offset(), - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top, - pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); - - pos.pageX = event.pageX; - pos.pageY = event.pageY; - - var item = findNearbyItem(canvasX, canvasY, seriesFilter); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); - } - - if (options.grid.autoHighlight) { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && - !(item && h.series == item.series && - h.point[0] == item.datapoint[0] && - h.point[1] == item.datapoint[1])) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, eventname); - } - - placeholder.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - var t = options.interaction.redrawOverlayInterval; - if (t == -1) { // skip event queue - drawOverlay(); - return; - } - - if (!redrawTimeout) - redrawTimeout = setTimeout(drawOverlay, t); - } - - function drawOverlay() { - redrawTimeout = null; - - // draw highlights - octx.save(); - overlay.clear(); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - executeHooks(hooks.drawOverlay, [octx]); - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (s == null && point == null) { - highlights = []; - triggerRedrawOverlay(); - return; - } - - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis, - highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = highlightColor; - var radius = 1.5 * pointRadius; - x = axisx.p2c(x); - y = axisy.p2c(y); - - octx.beginPath(); - if (series.points.symbol == "circle") - octx.arc(x, y, radius, 0, 2 * Math.PI, false); - else - series.points.symbol(octx, x, y, radius, false); - octx.closePath(); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), - fillStyle = highlightColor, - barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = highlightColor; - - drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); - } - - function getColorOrGradient(spec, bottom, top, defaultColor) { - if (typeof spec == "string") - return spec; - else { - // assume this is a gradient spec; IE currently only - // supports a simple vertical gradient properly, so that's - // what we support too - var gradient = ctx.createLinearGradient(0, top, 0, bottom); - - for (var i = 0, l = spec.colors.length; i < l; ++i) { - var c = spec.colors[i]; - if (typeof c != "string") { - var co = $.color.parse(defaultColor); - if (c.brightness != null) - co = co.scale('rgb', c.brightness); - if (c.opacity != null) - co.a *= c.opacity; - c = co.toString(); - } - gradient.addColorStop(i / (l - 1), c); - } - - return gradient; - } - } - } - - // Add the plot function to the top level of the jQuery object - - $.plot = function(placeholder, data, options) { - //var t0 = new Date(); - var plot = new Plot($(placeholder), data, options, $.plot.plugins); - //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); - return plot; - }; - - $.plot.version = "0.8.3"; - - $.plot.plugins = []; - - // Also add the plot function as a chainable property - - $.fn.plot = function(data, options) { - return this.each(function() { - $.plot(this, data, options); - }); - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - -})(jQuery); diff --git a/src/plugins/timelion/public/flot/jquery.flot.time.js b/src/plugins/timelion/public/flot/jquery.flot.time.js deleted file mode 100644 index 34c1d121259a2..0000000000000 --- a/src/plugins/timelion/public/flot/jquery.flot.time.js +++ /dev/null @@ -1,432 +0,0 @@ -/* Pretty handling of time axes. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Set axis.mode to "time" to enable. See the section "Time series data" in -API.txt for details. - -*/ - -(function($) { - - var options = { - xaxis: { - timezone: null, // "browser" for local to the client or timezone for timezone-js - timeformat: null, // format string to use - twelveHourClock: false, // 12 or 24 time in time mode - monthNames: null // list of names of months - } - }; - - // round to nearby lower multiple of base - - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - // Returns a string with the date d formatted according to fmt. - // A subset of the Open Group's strftime format is supported. - - function formatDate(d, fmt, monthNames, dayNames) { - - if (typeof d.strftime == "function") { - return d.strftime(fmt); - } - - var leftPad = function(n, pad) { - n = "" + n; - pad = "" + (pad == null ? "0" : pad); - return n.length == 1 ? pad + n : n; - }; - - var r = []; - var escape = false; - var hours = d.getHours(); - var isAM = hours < 12; - - if (monthNames == null) { - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - } - - if (dayNames == null) { - dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - } - - var hours12; - - if (hours > 12) { - hours12 = hours - 12; - } else if (hours == 0) { - hours12 = 12; - } else { - hours12 = hours; - } - - for (var i = 0; i < fmt.length; ++i) { - - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'a': c = "" + dayNames[d.getDay()]; break; - case 'b': c = "" + monthNames[d.getMonth()]; break; - case 'd': c = leftPad(d.getDate()); break; - case 'e': c = leftPad(d.getDate(), " "); break; - case 'h': // For back-compat with 0.7; remove in 1.0 - case 'H': c = leftPad(hours); break; - case 'I': c = leftPad(hours12); break; - case 'l': c = leftPad(hours12, " "); break; - case 'm': c = leftPad(d.getMonth() + 1); break; - case 'M': c = leftPad(d.getMinutes()); break; - // quarters not in Open Group's strftime specification - case 'q': - c = "" + (Math.floor(d.getMonth() / 3) + 1); break; - case 'S': c = leftPad(d.getSeconds()); break; - case 'y': c = leftPad(d.getFullYear() % 100); break; - case 'Y': c = "" + d.getFullYear(); break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case 'w': c = "" + d.getDay(); break; - } - r.push(c); - escape = false; - } else { - if (c == "%") { - escape = true; - } else { - r.push(c); - } - } - } - - return r.join(""); - } - - // To have a consistent view of time-based data independent of which time - // zone the client happens to be in we need a date-like object independent - // of time zones. This is done through a wrapper that only calls the UTC - // versions of the accessor methods. - - function makeUtcWrapper(d) { - - function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { - sourceObj[sourceMethod] = function() { - return targetObj[targetMethod].apply(targetObj, arguments); - }; - }; - - var utc = { - date: d - }; - - // support strftime, if found - - if (d.strftime != undefined) { - addProxyMethod(utc, "strftime", d, "strftime"); - } - - addProxyMethod(utc, "getTime", d, "getTime"); - addProxyMethod(utc, "setTime", d, "setTime"); - - var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; - - for (var p = 0; p < props.length; p++) { - addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); - addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); - } - - return utc; - }; - - // select time zone strategy. This returns a date-like object tied to the - // desired timezone - - function dateGenerator(ts, opts) { - if (opts.timezone == "browser") { - return new Date(ts); - } else if (!opts.timezone || opts.timezone == "utc") { - return makeUtcWrapper(new Date(ts)); - } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { - var d = new timezoneJS.Date(); - // timezone-js is fickle, so be sure to set the time zone before - // setting the time. - d.setTimezone(opts.timezone); - d.setTime(ts); - return d; - } else { - return makeUtcWrapper(new Date(ts)); - } - } - - // map of app. size of time units in milliseconds - - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "quarter": 3 * 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - - var baseSpec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"] - ]; - - // we don't know which variant(s) we'll need yet, but generating both is - // cheap - - var specMonths = baseSpec.concat([[3, "month"], [6, "month"], - [1, "year"]]); - var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], - [1, "year"]]); - - function init(plot) { - plot.hooks.processOptions.push(function (plot, options) { - $.each(plot.getAxes(), function(axisName, axis) { - - var opts = axis.options; - - if (opts.mode == "time") { - axis.tickGenerator = function(axis) { - - var ticks = []; - var d = dateGenerator(axis.min, opts); - var minSize = 0; - - // make quarter use a possibility if quarters are - // mentioned in either of these options - - var spec = (opts.tickSize && opts.tickSize[1] === - "quarter") || - (opts.minTickSize && opts.minTickSize[1] === - "quarter") ? specQuarters : specMonths; - - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") { - minSize = opts.tickSize; - } else { - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; - } - } - - for (var i = 0; i < spec.length - 1; ++i) { - if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { - break; - } - } - - var size = spec[i][0]; - var unit = spec[i][1]; - - // special-case the possibility of several years - - if (unit == "year") { - - // if given a minTickSize in years, just use it, - // ensuring that it's an integer - - if (opts.minTickSize != null && opts.minTickSize[1] == "year") { - size = Math.floor(opts.minTickSize[0]); - } else { - - var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); - var norm = (axis.delta / timeUnitSize.year) / magn; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - } - - // minimum size for years is 1 - - if (size < 1) { - size = 1; - } - } - - axis.tickSize = opts.tickSize || [size, unit]; - var tickSize = axis.tickSize[0]; - unit = axis.tickSize[1]; - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") { - d.setSeconds(floorInBase(d.getSeconds(), tickSize)); - } else if (unit == "minute") { - d.setMinutes(floorInBase(d.getMinutes(), tickSize)); - } else if (unit == "hour") { - d.setHours(floorInBase(d.getHours(), tickSize)); - } else if (unit == "month") { - d.setMonth(floorInBase(d.getMonth(), tickSize)); - } else if (unit == "quarter") { - d.setMonth(3 * floorInBase(d.getMonth() / 3, - tickSize)); - } else if (unit == "year") { - d.setFullYear(floorInBase(d.getFullYear(), tickSize)); - } - - // reset smaller components - - d.setMilliseconds(0); - - if (step >= timeUnitSize.minute) { - d.setSeconds(0); - } - if (step >= timeUnitSize.hour) { - d.setMinutes(0); - } - if (step >= timeUnitSize.day) { - d.setHours(0); - } - if (step >= timeUnitSize.day * 4) { - d.setDate(1); - } - if (step >= timeUnitSize.month * 2) { - d.setMonth(floorInBase(d.getMonth(), 3)); - } - if (step >= timeUnitSize.quarter * 2) { - d.setMonth(floorInBase(d.getMonth(), 6)); - } - if (step >= timeUnitSize.year) { - d.setMonth(0); - } - - var carry = 0; - var v = Number.NaN; - var prev; - - do { - - prev = v; - v = d.getTime(); - ticks.push(v); - - if (unit == "month" || unit == "quarter") { - if (tickSize < 1) { - - // a bit complicated - we'll divide the - // month/quarter up but we need to take - // care of fractions so we don't end up in - // the middle of a day - - d.setDate(1); - var start = d.getTime(); - d.setMonth(d.getMonth() + - (unit == "quarter" ? 3 : 1)); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getHours(); - d.setHours(0); - } else { - d.setMonth(d.getMonth() + - tickSize * (unit == "quarter" ? 3 : 1)); - } - } else if (unit == "year") { - d.setFullYear(d.getFullYear() + tickSize); - } else { - d.setTime(v + step); - } - } while (v < axis.max && v != prev); - - return ticks; - }; - - axis.tickFormatter = function (v, axis) { - - var d = dateGenerator(v, axis.options); - - // first check global format - - if (opts.timeformat != null) { - return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); - } - - // possibly use quarters if quarters are mentioned in - // any of these places - - var useQuarters = (axis.options.tickSize && - axis.options.tickSize[1] == "quarter") || - (axis.options.minTickSize && - axis.options.minTickSize[1] == "quarter"); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; - var fmt; - - if (t < timeUnitSize.minute) { - fmt = hourCode + ":%M:%S" + suffix; - } else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) { - fmt = hourCode + ":%M" + suffix; - } else { - fmt = "%b %d " + hourCode + ":%M" + suffix; - } - } else if (t < timeUnitSize.month) { - fmt = "%b %d"; - } else if ((useQuarters && t < timeUnitSize.quarter) || - (!useQuarters && t < timeUnitSize.year)) { - if (span < timeUnitSize.year) { - fmt = "%b"; - } else { - fmt = "%b %Y"; - } - } else if (useQuarters && t < timeUnitSize.year) { - if (span < timeUnitSize.year) { - fmt = "Q%q"; - } else { - fmt = "Q%q %Y"; - } - } else { - fmt = "%Y"; - } - - var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); - - return rt; - }; - } - }); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'time', - version: '1.0' - }); - - // Time-axis support used to be in Flot core, which exposed the - // formatDate function on the plot object. Various plugins depend - // on the function, so we need to re-expose it here. - - $.plot.formatDate = formatDate; - $.plot.dateGenerator = dateGenerator; - -})(jQuery); diff --git a/src/plugins/timelion/public/panels/timechart/schema.ts b/src/plugins/timelion/public/panels/timechart/schema.ts index b56d8a66110c2..d874f0d32c9d4 100644 --- a/src/plugins/timelion/public/panels/timechart/schema.ts +++ b/src/plugins/timelion/public/panels/timechart/schema.ts @@ -17,7 +17,6 @@ * under the License. */ -import '../../flot'; import _ from 'lodash'; import $ from 'jquery'; import moment from 'moment-timezone'; diff --git a/src/plugins/vis_default_editor/public/default_editor.tsx b/src/plugins/vis_default_editor/public/default_editor.tsx index ed94e52ee2399..a7251acfdf75d 100644 --- a/src/plugins/vis_default_editor/public/default_editor.tsx +++ b/src/plugins/vis_default_editor/public/default_editor.tsx @@ -18,6 +18,7 @@ */ import './index.scss'; +import 'brace/mode/json'; import React, { useEffect, useRef, useState, useCallback } from 'react'; import { EventEmitter } from 'events'; diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap index e32425a095429..88ed7c66a79a2 100644 --- a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap +++ b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud.test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`tag cloud tests tagcloudscreenshot should render simple image 1`] = `"foobarfoobar"`; +exports[`tag cloud tests tagcloudscreenshot should render simple image 1`] = `"foobarfoobar"`; diff --git a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap index dbc3dd1202cbd..d7707f64d8a4f 100644 --- a/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap +++ b/src/plugins/vis_type_tagcloud/public/components/__snapshots__/tag_cloud_visualization.test.js.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TagCloudVisualizationTest TagCloudVisualization - basics simple draw 1`] = `"CNINUSDEBR"`; +exports[`TagCloudVisualizationTest TagCloudVisualization - basics simple draw 1`] = `"CNINUSDEBR"`; -exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 1`] = `"CNINUSDEBR"`; +exports[`TagCloudVisualizationTest TagCloudVisualization - basics with param change 1`] = `"CNINUSDEBR"`; -exports[`TagCloudVisualizationTest TagCloudVisualization - basics with resize 1`] = `"CNINUSDEBR"`; +exports[`TagCloudVisualizationTest TagCloudVisualization - basics with resize 1`] = `"CNINUSDEBR"`; diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js index e48515d243844..b1de60f854a1c 100644 --- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js +++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud.js @@ -48,7 +48,11 @@ export class TagCloud extends EventEmitter { this.resize(); //SETTING (non-configurable) - this._fontFamily = 'Open Sans, sans-serif'; + /** + * the fontFamily should be set explicitly for calculating a layout + * and to avoid words overlapping + */ + this._fontFamily = 'Inter UI, sans-serif'; this._fontStyle = 'normal'; this._fontWeight = 'normal'; this._spiral = 'archimedean'; //layout shape diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx index 18a09ec9f4969..cb0daa6d29382 100644 --- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx +++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_chart.tsx @@ -62,10 +62,10 @@ export const TagCloudChart = ({ () => throttle(() => { if (visController.current) { - visController.current.render().then(renderComplete); + visController.current.render(visData, visParams).then(renderComplete); } }, 300), - [renderComplete] + [renderComplete, visData, visParams] ); return ( diff --git a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js index 5ec22d2c6a4d9..0d64c9d02eafd 100644 --- a/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js +++ b/src/plugins/vis_type_tagcloud/public/components/tag_cloud_visualization.js @@ -85,11 +85,8 @@ export class TagCloudVisualization { } async render(data, visParams) { - if (data && visParams) { - this._updateParams(visParams); - this._updateData(data); - } - + this._updateParams(visParams); + this._updateData(data); this._resize(); await this._renderComplete$.pipe(take(1)).toPromise(); diff --git a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx index a7b623ac8680c..953ec5e819f44 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx +++ b/src/plugins/vis_type_timelion/public/components/timelion_vis_component.tsx @@ -25,7 +25,6 @@ import { useResizeObserver } from '@elastic/eui'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { useKibana } from '../../../kibana_react/public'; -import '../flot'; import { DEFAULT_TIME_FORMAT } from '../../common/lib'; import { diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.axislabels.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.axislabels.js deleted file mode 100644 index cda8038953c76..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.axislabels.js +++ /dev/null @@ -1,462 +0,0 @@ -/* -Axis Labels Plugin for flot. -http://github.com/markrcote/flot-axislabels -Original code is Copyright (c) 2010 Xuan Luo. -Original code was released under the GPLv3 license by Xuan Luo, September 2010. -Original code was rereleased under the MIT license by Xuan Luo, April 2012. -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -(function ($) { - var options = { - axisLabels: { - show: true - } - }; - - function canvasSupported() { - return !!document.createElement('canvas').getContext; - } - - function canvasTextSupported() { - if (!canvasSupported()) { - return false; - } - var dummy_canvas = document.createElement('canvas'); - var context = dummy_canvas.getContext('2d'); - return typeof context.fillText == 'function'; - } - - function css3TransitionSupported() { - var div = document.createElement('div'); - return typeof div.style.MozTransition != 'undefined' // Gecko - || typeof div.style.OTransition != 'undefined' // Opera - || typeof div.style.webkitTransition != 'undefined' // WebKit - || typeof div.style.transition != 'undefined'; - } - - - function AxisLabel(axisName, position, padding, plot, opts) { - this.axisName = axisName; - this.position = position; - this.padding = padding; - this.plot = plot; - this.opts = opts; - this.width = 0; - this.height = 0; - } - - AxisLabel.prototype.cleanup = function() { - }; - - - CanvasAxisLabel.prototype = new AxisLabel(); - CanvasAxisLabel.prototype.constructor = CanvasAxisLabel; - function CanvasAxisLabel(axisName, position, padding, plot, opts) { - AxisLabel.prototype.constructor.call(this, axisName, position, padding, - plot, opts); - } - - CanvasAxisLabel.prototype.calculateSize = function() { - if (!this.opts.axisLabelFontSizePixels) - this.opts.axisLabelFontSizePixels = 14; - if (!this.opts.axisLabelFontFamily) - this.opts.axisLabelFontFamily = 'sans-serif'; - - var textWidth = this.opts.axisLabelFontSizePixels + this.padding; - var textHeight = this.opts.axisLabelFontSizePixels + this.padding; - if (this.position == 'left' || this.position == 'right') { - this.width = this.opts.axisLabelFontSizePixels + this.padding; - this.height = 0; - } else { - this.width = 0; - this.height = this.opts.axisLabelFontSizePixels + this.padding; - } - }; - - CanvasAxisLabel.prototype.draw = function(box) { - if (!this.opts.axisLabelColour) - this.opts.axisLabelColour = 'black'; - var ctx = this.plot.getCanvas().getContext('2d'); - ctx.save(); - ctx.font = this.opts.axisLabelFontSizePixels + 'px ' + - this.opts.axisLabelFontFamily; - ctx.fillStyle = this.opts.axisLabelColour; - var width = ctx.measureText(this.opts.axisLabel).width; - var height = this.opts.axisLabelFontSizePixels; - var x, y, angle = 0; - if (this.position == 'top') { - x = box.left + box.width/2 - width/2; - y = box.top + height*0.72; - } else if (this.position == 'bottom') { - x = box.left + box.width/2 - width/2; - y = box.top + box.height - height*0.72; - } else if (this.position == 'left') { - x = box.left + height*0.72; - y = box.height/2 + box.top + width/2; - angle = -Math.PI/2; - } else if (this.position == 'right') { - x = box.left + box.width - height*0.72; - y = box.height/2 + box.top - width/2; - angle = Math.PI/2; - } - ctx.translate(x, y); - ctx.rotate(angle); - ctx.fillText(this.opts.axisLabel, 0, 0); - ctx.restore(); - }; - - - HtmlAxisLabel.prototype = new AxisLabel(); - HtmlAxisLabel.prototype.constructor = HtmlAxisLabel; - function HtmlAxisLabel(axisName, position, padding, plot, opts) { - AxisLabel.prototype.constructor.call(this, axisName, position, - padding, plot, opts); - this.elem = null; - } - - HtmlAxisLabel.prototype.calculateSize = function() { - var elem = $('
' + - this.opts.axisLabel + '
'); - this.plot.getPlaceholder().append(elem); - // store height and width of label itself, for use in draw() - this.labelWidth = elem.outerWidth(true); - this.labelHeight = elem.outerHeight(true); - elem.remove(); - - this.width = this.height = 0; - if (this.position == 'left' || this.position == 'right') { - this.width = this.labelWidth + this.padding; - } else { - this.height = this.labelHeight + this.padding; - } - }; - - HtmlAxisLabel.prototype.cleanup = function() { - if (this.elem) { - this.elem.remove(); - } - }; - - HtmlAxisLabel.prototype.draw = function(box) { - this.plot.getPlaceholder().find('#' + this.axisName + 'Label').remove(); - this.elem = $('
' - + this.opts.axisLabel + '
'); - this.plot.getPlaceholder().append(this.elem); - if (this.position == 'top') { - this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + - 'px'); - this.elem.css('top', box.top + 'px'); - } else if (this.position == 'bottom') { - this.elem.css('left', box.left + box.width/2 - this.labelWidth/2 + - 'px'); - this.elem.css('top', box.top + box.height - this.labelHeight + - 'px'); - } else if (this.position == 'left') { - this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + - 'px'); - this.elem.css('left', box.left + 'px'); - } else if (this.position == 'right') { - this.elem.css('top', box.top + box.height/2 - this.labelHeight/2 + - 'px'); - this.elem.css('left', box.left + box.width - this.labelWidth + - 'px'); - } - }; - - - CssTransformAxisLabel.prototype = new HtmlAxisLabel(); - CssTransformAxisLabel.prototype.constructor = CssTransformAxisLabel; - function CssTransformAxisLabel(axisName, position, padding, plot, opts) { - HtmlAxisLabel.prototype.constructor.call(this, axisName, position, - padding, plot, opts); - } - - CssTransformAxisLabel.prototype.calculateSize = function() { - HtmlAxisLabel.prototype.calculateSize.call(this); - this.width = this.height = 0; - if (this.position == 'left' || this.position == 'right') { - this.width = this.labelHeight + this.padding; - } else { - this.height = this.labelHeight + this.padding; - } - }; - - CssTransformAxisLabel.prototype.transforms = function(degrees, x, y) { - var stransforms = { - '-moz-transform': '', - '-webkit-transform': '', - '-o-transform': '', - '-ms-transform': '' - }; - if (x != 0 || y != 0) { - var stdTranslate = ' translate(' + x + 'px, ' + y + 'px)'; - stransforms['-moz-transform'] += stdTranslate; - stransforms['-webkit-transform'] += stdTranslate; - stransforms['-o-transform'] += stdTranslate; - stransforms['-ms-transform'] += stdTranslate; - } - if (degrees != 0) { - var rotation = degrees / 90; - var stdRotate = ' rotate(' + degrees + 'deg)'; - stransforms['-moz-transform'] += stdRotate; - stransforms['-webkit-transform'] += stdRotate; - stransforms['-o-transform'] += stdRotate; - stransforms['-ms-transform'] += stdRotate; - } - var s = 'top: 0; left: 0; '; - for (var prop in stransforms) { - if (stransforms[prop]) { - s += prop + ':' + stransforms[prop] + ';'; - } - } - s += ';'; - return s; - }; - - CssTransformAxisLabel.prototype.calculateOffsets = function(box) { - var offsets = { x: 0, y: 0, degrees: 0 }; - if (this.position == 'bottom') { - offsets.x = box.left + box.width/2 - this.labelWidth/2; - offsets.y = box.top + box.height - this.labelHeight; - } else if (this.position == 'top') { - offsets.x = box.left + box.width/2 - this.labelWidth/2; - offsets.y = box.top; - } else if (this.position == 'left') { - offsets.degrees = -90; - offsets.x = box.left - this.labelWidth/2 + this.labelHeight/2; - offsets.y = box.height/2 + box.top; - } else if (this.position == 'right') { - offsets.degrees = 90; - offsets.x = box.left + box.width - this.labelWidth/2 - - this.labelHeight/2; - offsets.y = box.height/2 + box.top; - } - offsets.x = Math.round(offsets.x); - offsets.y = Math.round(offsets.y); - - return offsets; - }; - - CssTransformAxisLabel.prototype.draw = function(box) { - this.plot.getPlaceholder().find("." + this.axisName + "Label").remove(); - var offsets = this.calculateOffsets(box); - this.elem = $('
' + this.opts.axisLabel + '
'); - this.plot.getPlaceholder().append(this.elem); - }; - - - IeTransformAxisLabel.prototype = new CssTransformAxisLabel(); - IeTransformAxisLabel.prototype.constructor = IeTransformAxisLabel; - function IeTransformAxisLabel(axisName, position, padding, plot, opts) { - CssTransformAxisLabel.prototype.constructor.call(this, axisName, - position, padding, - plot, opts); - this.requiresResize = false; - } - - IeTransformAxisLabel.prototype.transforms = function(degrees, x, y) { - // I didn't feel like learning the crazy Matrix stuff, so this uses - // a combination of the rotation transform and CSS positioning. - var s = ''; - if (degrees != 0) { - var rotation = degrees/90; - while (rotation < 0) { - rotation += 4; - } - s += ' filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=' + rotation + '); '; - // see below - this.requiresResize = (this.position == 'right'); - } - if (x != 0) { - s += 'left: ' + x + 'px; '; - } - if (y != 0) { - s += 'top: ' + y + 'px; '; - } - return s; - }; - - IeTransformAxisLabel.prototype.calculateOffsets = function(box) { - var offsets = CssTransformAxisLabel.prototype.calculateOffsets.call( - this, box); - // adjust some values to take into account differences between - // CSS and IE rotations. - if (this.position == 'top') { - // FIXME: not sure why, but placing this exactly at the top causes - // the top axis label to flip to the bottom... - offsets.y = box.top + 1; - } else if (this.position == 'left') { - offsets.x = box.left; - offsets.y = box.height/2 + box.top - this.labelWidth/2; - } else if (this.position == 'right') { - offsets.x = box.left + box.width - this.labelHeight; - offsets.y = box.height/2 + box.top - this.labelWidth/2; - } - return offsets; - }; - - IeTransformAxisLabel.prototype.draw = function(box) { - CssTransformAxisLabel.prototype.draw.call(this, box); - if (this.requiresResize) { - this.elem = this.plot.getPlaceholder().find("." + this.axisName + - "Label"); - // Since we used CSS positioning instead of transforms for - // translating the element, and since the positioning is done - // before any rotations, we have to reset the width and height - // in case the browser wrapped the text (specifically for the - // y2axis). - this.elem.css('width', this.labelWidth); - this.elem.css('height', this.labelHeight); - } - }; - - - function init(plot) { - plot.hooks.processOptions.push(function (plot, options) { - - if (!options.axisLabels.show) - return; - - // This is kind of a hack. There are no hooks in Flot between - // the creation and measuring of the ticks (setTicks, measureTickLabels - // in setupGrid() ) and the drawing of the ticks and plot box - // (insertAxisLabels in setupGrid() ). - // - // Therefore, we use a trick where we run the draw routine twice: - // the first time to get the tick measurements, so that we can change - // them, and then have it draw it again. - var secondPass = false; - - var axisLabels = {}; - var axisOffsetCounts = { left: 0, right: 0, top: 0, bottom: 0 }; - - var defaultPadding = 2; // padding between axis and tick labels - plot.hooks.draw.push(function (plot, ctx) { - var hasAxisLabels = false; - if (!secondPass) { - // MEASURE AND SET OPTIONS - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options // Flot 0.7 - || plot.getOptions()[axisName]; // Flot 0.6 - - // Handle redraws initiated outside of this plug-in. - if (axisName in axisLabels) { - axis.labelHeight = axis.labelHeight - - axisLabels[axisName].height; - axis.labelWidth = axis.labelWidth - - axisLabels[axisName].width; - opts.labelHeight = axis.labelHeight; - opts.labelWidth = axis.labelWidth; - axisLabels[axisName].cleanup(); - delete axisLabels[axisName]; - } - - if (!opts || !opts.axisLabel || !axis.show) - return; - - hasAxisLabels = true; - var renderer = null; - - if (!opts.axisLabelUseHtml && - navigator.appName == 'Microsoft Internet Explorer') { - var ua = navigator.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); - if (re.exec(ua) != null) { - rv = parseFloat(RegExp.$1); - } - if (rv >= 9 && !opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { - renderer = CssTransformAxisLabel; - } else if (!opts.axisLabelUseCanvas && !opts.axisLabelUseHtml) { - renderer = IeTransformAxisLabel; - } else if (opts.axisLabelUseCanvas) { - renderer = CanvasAxisLabel; - } else { - renderer = HtmlAxisLabel; - } - } else { - if (opts.axisLabelUseHtml || (!css3TransitionSupported() && !canvasTextSupported()) && !opts.axisLabelUseCanvas) { - renderer = HtmlAxisLabel; - } else if (opts.axisLabelUseCanvas || !css3TransitionSupported()) { - renderer = CanvasAxisLabel; - } else { - renderer = CssTransformAxisLabel; - } - } - - var padding = opts.axisLabelPadding === undefined ? - defaultPadding : opts.axisLabelPadding; - - axisLabels[axisName] = new renderer(axisName, - axis.position, padding, - plot, opts); - - // flot interprets axis.labelHeight and .labelWidth as - // the height and width of the tick labels. We increase - // these values to make room for the axis label and - // padding. - - axisLabels[axisName].calculateSize(); - - // AxisLabel.height and .width are the size of the - // axis label and padding. - // Just set opts here because axis will be sorted out on - // the redraw. - - opts.labelHeight = axis.labelHeight + - axisLabels[axisName].height; - opts.labelWidth = axis.labelWidth + - axisLabels[axisName].width; - }); - - // If there are axis labels, re-draw with new label widths and - // heights. - - if (hasAxisLabels) { - secondPass = true; - plot.setupGrid(); - plot.draw(); - } - } else { - secondPass = false; - // DRAW - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options // Flot 0.7 - || plot.getOptions()[axisName]; // Flot 0.6 - if (!opts || !opts.axisLabel || !axis.show) - return; - - axisLabels[axisName].draw(axis.box); - }); - } - }); - }); - } - - - $.plot.plugins.push({ - init: init, - options: options, - name: 'axisLabels', - version: '2.0' - }); -})(jQuery); diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.crosshair.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.crosshair.js deleted file mode 100644 index 5111695e3d12c..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.crosshair.js +++ /dev/null @@ -1,176 +0,0 @@ -/* Flot plugin for showing crosshairs when the mouse hovers over the plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - - crosshair: { - mode: null or "x" or "y" or "xy" - color: color - lineWidth: number - } - -Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical -crosshair that lets you trace the values on the x axis, "y" enables a -horizontal crosshair and "xy" enables them both. "color" is the color of the -crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of -the drawn lines (default is 1). - -The plugin also adds four public methods: - - - setCrosshair( pos ) - - Set the position of the crosshair. Note that this is cleared if the user - moves the mouse. "pos" is in coordinates of the plot and should be on the - form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple - axes), which is coincidentally the same format as what you get from a - "plothover" event. If "pos" is null, the crosshair is cleared. - - - clearCrosshair() - - Clear the crosshair. - - - lockCrosshair(pos) - - Cause the crosshair to lock to the current location, no longer updating if - the user moves the mouse. Optionally supply a position (passed on to - setCrosshair()) to move it to. - - Example usage: - - var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } }; - $("#graph").bind( "plothover", function ( evt, position, item ) { - if ( item ) { - // Lock the crosshair to the data point being hovered - myFlot.lockCrosshair({ - x: item.datapoint[ 0 ], - y: item.datapoint[ 1 ] - }); - } else { - // Return normal crosshair operation - myFlot.unlockCrosshair(); - } - }); - - - unlockCrosshair() - - Free the crosshair to move again after locking it. -*/ - -(function ($) { - var options = { - crosshair: { - mode: null, // one of null, "x", "y" or "xy", - color: "rgba(170, 0, 0, 0.80)", - lineWidth: 1 - } - }; - - function init(plot) { - // position of crosshair in pixels - var crosshair = { x: -1, y: -1, locked: false }; - - plot.setCrosshair = function setCrosshair(pos) { - if (!pos) - crosshair.x = -1; - else { - var o = plot.p2c(pos); - crosshair.x = Math.max(0, Math.min(o.left, plot.width())); - crosshair.y = Math.max(0, Math.min(o.top, plot.height())); - } - - plot.triggerRedrawOverlay(); - }; - - plot.clearCrosshair = plot.setCrosshair; // passes null for pos - - plot.lockCrosshair = function lockCrosshair(pos) { - if (pos) - plot.setCrosshair(pos); - crosshair.locked = true; - }; - - plot.unlockCrosshair = function unlockCrosshair() { - crosshair.locked = false; - }; - - function onMouseOut(e) { - if (crosshair.locked) - return; - - if (crosshair.x != -1) { - crosshair.x = -1; - plot.triggerRedrawOverlay(); - } - } - - function onMouseMove(e) { - if (crosshair.locked) - return; - - if (plot.getSelection && plot.getSelection()) { - crosshair.x = -1; // hide the crosshair while selecting - return; - } - - var offset = plot.offset(); - crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width())); - crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height())); - plot.triggerRedrawOverlay(); - } - - plot.hooks.bindEvents.push(function (plot, eventHolder) { - if (!plot.getOptions().crosshair.mode) - return; - - eventHolder.mouseout(onMouseOut); - eventHolder.mousemove(onMouseMove); - }); - - plot.hooks.drawOverlay.push(function (plot, ctx) { - var c = plot.getOptions().crosshair; - if (!c.mode) - return; - - var plotOffset = plot.getPlotOffset(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - if (crosshair.x != -1) { - var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0; - - ctx.strokeStyle = c.color; - ctx.lineWidth = c.lineWidth; - ctx.lineJoin = "round"; - - ctx.beginPath(); - if (c.mode.indexOf("x") != -1) { - var drawX = Math.floor(crosshair.x) + adj; - ctx.moveTo(drawX, 0); - ctx.lineTo(drawX, plot.height()); - } - if (c.mode.indexOf("y") != -1) { - var drawY = Math.floor(crosshair.y) + adj; - ctx.moveTo(0, drawY); - ctx.lineTo(plot.width(), drawY); - } - ctx.stroke(); - } - ctx.restore(); - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mouseout", onMouseOut); - eventHolder.unbind("mousemove", onMouseMove); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'crosshair', - version: '1.0' - }); -})(jQuery); diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.js deleted file mode 100644 index 5d613037cf234..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.js +++ /dev/null @@ -1,3168 +0,0 @@ -/* JavaScript plotting library for jQuery, version 0.8.3. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -*/ - -// first an inline dependency, jquery.colorhelpers.js, we inline it here -// for convenience - -/* Plugin for jQuery for working with colors. - * - * Version 1.1. - * - * Inspiration from jQuery color animation plugin by John Resig. - * - * Released under the MIT license by Ole Laursen, October 2009. - * - * Examples: - * - * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() - * var c = $.color.extract($("#mydiv"), 'background-color'); - * console.log(c.r, c.g, c.b, c.a); - * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" - * - * Note that .scale() and .add() return the same modified object - * instead of making a new one. - * - * V. 1.1: Fix error handling so e.g. parsing an empty string does - * produce a color rather than just crashing. - */ -(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return valuemax?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); - -// the actual Flot code -(function($) { - - // Cache the prototype hasOwnProperty for faster access - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM - // operation produces the same effect as detach, i.e. removing the element - // without touching its jQuery data. - - // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+. - - if (!$.fn.detach) { - $.fn.detach = function() { - return this.each(function() { - if (this.parentNode) { - this.parentNode.removeChild( this ); - } - }); - }; - } - - /////////////////////////////////////////////////////////////////////////// - // The Canvas object is a wrapper around an HTML5 tag. - // - // @constructor - // @param {string} cls List of classes to apply to the canvas. - // @param {element} container Element onto which to append the canvas. - // - // Requiring a container is a little iffy, but unfortunately canvas - // operations don't work unless the canvas is attached to the DOM. - - function Canvas(cls, container) { - - var element = container.children("." + cls)[0]; - - if (element == null) { - - element = document.createElement("canvas"); - element.className = cls; - - $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) - .appendTo(container); - - // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas - - if (!element.getContext) { - if (window.G_vmlCanvasManager) { - element = window.G_vmlCanvasManager.initElement(element); - } else { - throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); - } - } - } - - this.element = element; - - var context = this.context = element.getContext("2d"); - - // Determine the screen's ratio of physical to device-independent - // pixels. This is the ratio between the canvas width that the browser - // advertises and the number of pixels actually present in that space. - - // The iPhone 4, for example, has a device-independent width of 320px, - // but its screen is actually 640px wide. It therefore has a pixel - // ratio of 2, while most normal devices have a ratio of 1. - - var devicePixelRatio = window.devicePixelRatio || 1, - backingStoreRatio = - context.webkitBackingStorePixelRatio || - context.mozBackingStorePixelRatio || - context.msBackingStorePixelRatio || - context.oBackingStorePixelRatio || - context.backingStorePixelRatio || 1; - - this.pixelRatio = devicePixelRatio / backingStoreRatio; - - // Size the canvas to match the internal dimensions of its container - - this.resize(container.width(), container.height()); - - // Collection of HTML div layers for text overlaid onto the canvas - - this.textContainer = null; - this.text = {}; - - // Cache of text fragments and metrics, so we can avoid expensively - // re-calculating them when the plot is re-rendered in a loop. - - this._textCache = {}; - } - - // Resizes the canvas to the given dimensions. - // - // @param {number} width New width of the canvas, in pixels. - // @param {number} width New height of the canvas, in pixels. - - Canvas.prototype.resize = function(width, height) { - - if (width <= 0 || height <= 0) { - throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); - } - - var element = this.element, - context = this.context, - pixelRatio = this.pixelRatio; - - // Resize the canvas, increasing its density based on the display's - // pixel ratio; basically giving it more pixels without increasing the - // size of its element, to take advantage of the fact that retina - // displays have that many more pixels in the same advertised space. - - // Resizing should reset the state (excanvas seems to be buggy though) - - if (this.width != width) { - element.width = width * pixelRatio; - element.style.width = width + "px"; - this.width = width; - } - - if (this.height != height) { - element.height = height * pixelRatio; - element.style.height = height + "px"; - this.height = height; - } - - // Save the context, so we can reset in case we get replotted. The - // restore ensure that we're really back at the initial state, and - // should be safe even if we haven't saved the initial state yet. - - context.restore(); - context.save(); - - // Scale the coordinate space to match the display density; so even though we - // may have twice as many pixels, we still want lines and other drawing to - // appear at the same size; the extra pixels will just make them crisper. - - context.scale(pixelRatio, pixelRatio); - }; - - // Clears the entire canvas area, not including any overlaid HTML text - - Canvas.prototype.clear = function() { - this.context.clearRect(0, 0, this.width, this.height); - }; - - // Finishes rendering the canvas, including managing the text overlay. - - Canvas.prototype.render = function() { - - var cache = this._textCache; - - // For each text layer, add elements marked as active that haven't - // already been rendered, and remove those that are no longer active. - - for (var layerKey in cache) { - if (hasOwnProperty.call(cache, layerKey)) { - - var layer = this.getTextLayer(layerKey), - layerCache = cache[layerKey]; - - layer.hide(); - - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - - var positions = styleCache[key].positions; - - for (var i = 0, position; position = positions[i]; i++) { - if (position.active) { - if (!position.rendered) { - layer.append(position.element); - position.rendered = true; - } - } else { - positions.splice(i--, 1); - if (position.rendered) { - position.element.detach(); - } - } - } - - if (positions.length == 0) { - delete styleCache[key]; - } - } - } - } - } - - layer.show(); - } - } - }; - - // Creates (if necessary) and returns the text overlay container. - // - // @param {string} classes String of space-separated CSS classes used to - // uniquely identify the text layer. - // @return {object} The jQuery-wrapped text-layer div. - - Canvas.prototype.getTextLayer = function(classes) { - - var layer = this.text[classes]; - - // Create the text layer if it doesn't exist - - if (layer == null) { - - // Create the text layer container, if it doesn't exist - - if (this.textContainer == null) { - this.textContainer = $("
") - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0, - 'font-size': "smaller", - color: "#545454" - }) - .insertAfter(this.element); - } - - layer = this.text[classes] = $("
") - .addClass(classes) - .css({ - position: "absolute", - top: 0, - left: 0, - bottom: 0, - right: 0 - }) - .appendTo(this.textContainer); - } - - return layer; - }; - - // Creates (if necessary) and returns a text info object. - // - // The object looks like this: - // - // { - // width: Width of the text's wrapper div. - // height: Height of the text's wrapper div. - // element: The jQuery-wrapped HTML div containing the text. - // positions: Array of positions at which this text is drawn. - // } - // - // The positions array contains objects that look like this: - // - // { - // active: Flag indicating whether the text should be visible. - // rendered: Flag indicating whether the text is currently visible. - // element: The jQuery-wrapped HTML div containing the text. - // x: X coordinate at which to draw the text. - // y: Y coordinate at which to draw the text. - // } - // - // Each position after the first receives a clone of the original element. - // - // The idea is that that the width, height, and general 'identity' of the - // text is constant no matter where it is placed; the placements are a - // secondary property. - // - // Canvas maintains a cache of recently-used text info objects; getTextInfo - // either returns the cached element or creates a new entry. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {string} text Text string to retrieve info for. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @return {object} a text info object. - - Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) { - - var textStyle, layerCache, styleCache, info; - - // Cast the value to a string, in case we were given a number or such - - text = "" + text; - - // If the font is a font-spec object, generate a CSS font definition - - if (typeof font === "object") { - textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; - } else { - textStyle = font; - } - - // Retrieve (or create) the cache for the text's layer and styles - - layerCache = this._textCache[layer]; - - if (layerCache == null) { - layerCache = this._textCache[layer] = {}; - } - - styleCache = layerCache[textStyle]; - - if (styleCache == null) { - styleCache = layerCache[textStyle] = {}; - } - - info = styleCache[text]; - - // If we can't find a matching element in our cache, create a new one - - if (info == null) { - - var element = $("
").html(text) - .css({ - position: "absolute", - 'max-width': width, - top: -9999 - }) - .appendTo(this.getTextLayer(layer)); - - if (typeof font === "object") { - element.css({ - font: textStyle, - color: font.color - }); - } else if (typeof font === "string") { - element.addClass(font); - } - - info = styleCache[text] = { - width: element.outerWidth(true), - height: element.outerHeight(true), - element: element, - positions: [] - }; - - element.detach(); - } - - return info; - }; - - // Adds a text string to the canvas text overlay. - // - // The text isn't drawn immediately; it is marked as rendering, which will - // result in its addition to the canvas on the next render pass. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number} x X coordinate at which to draw the text. - // @param {number} y Y coordinate at which to draw the text. - // @param {string} text Text string to draw. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which to rotate the text, in degrees. - // Angle is currently unused, it will be implemented in the future. - // @param {number=} width Maximum width of the text before it wraps. - // @param {string=} halign Horizontal alignment of the text; either "left", - // "center" or "right". - // @param {string=} valign Vertical alignment of the text; either "top", - // "middle" or "bottom". - - Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) { - - var info = this.getTextInfo(layer, text, font, angle, width), - positions = info.positions; - - // Tweak the div's position to match the text's alignment - - if (halign == "center") { - x -= info.width / 2; - } else if (halign == "right") { - x -= info.width; - } - - if (valign == "middle") { - y -= info.height / 2; - } else if (valign == "bottom") { - y -= info.height; - } - - // Determine whether this text already exists at this position. - // If so, mark it for inclusion in the next render pass. - - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = true; - return; - } - } - - // If the text doesn't exist at this position, create a new entry - - // For the very first position we'll re-use the original element, - // while for subsequent ones we'll clone it. - - position = { - active: true, - rendered: false, - element: positions.length ? info.element.clone() : info.element, - x: x, - y: y - }; - - positions.push(position); - - // Move the element to its final position within the container - - position.element.css({ - top: Math.round(y), - left: Math.round(x), - 'text-align': halign // In case the text wraps - }); - }; - - // Removes one or more text strings from the canvas text overlay. - // - // If no parameters are given, all text within the layer is removed. - // - // Note that the text is not immediately removed; it is simply marked as - // inactive, which will result in its removal on the next render pass. - // This avoids the performance penalty for 'clear and redraw' behavior, - // where we potentially get rid of all text on a layer, but will likely - // add back most or all of it later, as when redrawing axes, for example. - // - // @param {string} layer A string of space-separated CSS classes uniquely - // identifying the layer containing this text. - // @param {number=} x X coordinate of the text. - // @param {number=} y Y coordinate of the text. - // @param {string=} text Text string to remove. - // @param {(string|object)=} font Either a string of space-separated CSS - // classes or a font-spec object, defining the text's font and style. - // @param {number=} angle Angle at which the text is rotated, in degrees. - // Angle is currently unused, it will be implemented in the future. - - Canvas.prototype.removeText = function(layer, x, y, text, font, angle) { - if (text == null) { - var layerCache = this._textCache[layer]; - if (layerCache != null) { - for (var styleKey in layerCache) { - if (hasOwnProperty.call(layerCache, styleKey)) { - var styleCache = layerCache[styleKey]; - for (var key in styleCache) { - if (hasOwnProperty.call(styleCache, key)) { - var positions = styleCache[key].positions; - for (var i = 0, position; position = positions[i]; i++) { - position.active = false; - } - } - } - } - } - } - } else { - var positions = this.getTextInfo(layer, text, font, angle).positions; - for (var i = 0, position; position = positions[i]; i++) { - if (position.x == x && position.y == y) { - position.active = false; - } - } - } - }; - - /////////////////////////////////////////////////////////////////////////// - // The top-level container for the entire plot. - - function Plot(placeholder, data_, options_, plugins) { - // data is on the form: - // [ series1, series2 ... ] - // where series is either just the data as [ [x1, y1], [x2, y2], ... ] - // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } - - var series = [], - options = { - // the color theme used for graphs - colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], - legend: { - show: true, - noColumns: 1, // number of columns in legend table - labelFormatter: null, // fn: string -> string - labelBoxBorderColor: "#ccc", // border color for the little label boxes - container: null, // container (as jQuery object) to put legend in, null means default on top of graph - position: "ne", // position of default legend container within plot - margin: 5, // distance from grid edge to default legend container within plot - backgroundColor: null, // null means auto-detect - backgroundOpacity: 0.85, // set to 0 to avoid background - sorted: null // default to no legend sorting - }, - xaxis: { - show: null, // null = auto-detect, true = always, false = never - position: "bottom", // or "top" - mode: null, // null or "time" - font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } - color: null, // base color, labels, ticks - tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" - transform: null, // null or f: number -> number to transform axis - inverseTransform: null, // if transform is set, this should be the inverse function - min: null, // min. value to show, null means set automatically - max: null, // max. value to show, null means set automatically - autoscaleMargin: null, // margin in % to add if auto-setting min/max - ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks - tickFormatter: null, // fn: number -> string - labelWidth: null, // size of tick labels in pixels - labelHeight: null, - reserveSpace: null, // whether to reserve space even if axis isn't shown - tickLength: null, // size in pixels of ticks, or "full" for whole line - alignTicksWithAxis: null, // axis number or null for no sync - tickDecimals: null, // no. of decimals, null means auto - tickSize: null, // number or [number, "unit"] - minTickSize: null // number or [number, "unit"] - }, - yaxis: { - autoscaleMargin: 0.02, - position: "left" // or "right" - }, - xaxes: [], - yaxes: [], - series: { - points: { - show: false, - radius: 3, - lineWidth: 2, // in pixels - fill: true, - fillColor: "#ffffff", - symbol: "circle" // or callback - }, - lines: { - // we don't put in show: false so we can see - // whether lines were actively disabled - lineWidth: 2, // in pixels - fill: false, - fillColor: null, - steps: false - // Omit 'zero', so we can later default its value to - // match that of the 'fill' option. - }, - bars: { - show: false, - lineWidth: 2, // in pixels - barWidth: 1, // in units of the x axis - fill: true, - fillColor: null, - align: "left", // "left", "right", or "center" - horizontal: false, - zero: true - }, - shadowSize: 3, - highlightColor: null - }, - grid: { - show: true, - aboveData: false, - color: "#545454", // primary color used for outline and labels - backgroundColor: null, // null for transparent, else color - borderColor: null, // set if different from the grid color - tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" - margin: 0, // distance from the canvas edge to the grid - labelMargin: 5, // in pixels - axisMargin: 8, // in pixels - borderWidth: 2, // in pixels - minBorderMargin: null, // in pixels, null means taken from points radius - markings: null, // array of ranges or fn: axes -> array of ranges - markingsColor: "#f4f4f4", - markingsLineWidth: 2, - // interactive stuff - clickable: false, - hoverable: false, - autoHighlight: true, // highlight in case mouse is near - mouseActiveRadius: 10 // how far the mouse can be away to activate an item - }, - interaction: { - redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow - }, - hooks: {} - }, - surface = null, // the canvas for the plot itself - overlay = null, // canvas for interactive stuff on top of plot - eventHolder = null, // jQuery object that events should be bound to - ctx = null, octx = null, - xaxes = [], yaxes = [], - plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, - plotWidth = 0, plotHeight = 0, - hooks = { - processOptions: [], - processRawData: [], - processDatapoints: [], - processOffset: [], - drawBackground: [], - drawSeries: [], - draw: [], - bindEvents: [], - drawOverlay: [], - shutdown: [] - }, - plot = this; - - // public functions - plot.setData = setData; - plot.setupGrid = setupGrid; - plot.draw = draw; - plot.getPlaceholder = function() { return placeholder; }; - plot.getCanvas = function() { return surface.element; }; - plot.getPlotOffset = function() { return plotOffset; }; - plot.width = function () { return plotWidth; }; - plot.height = function () { return plotHeight; }; - plot.offset = function () { - var o = eventHolder.offset(); - o.left += plotOffset.left; - o.top += plotOffset.top; - return o; - }; - plot.getData = function () { return series; }; - plot.getAxes = function () { - var res = {}, i; - $.each(xaxes.concat(yaxes), function (_, axis) { - if (axis) - res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; - }); - return res; - }; - plot.getXAxes = function () { return xaxes; }; - plot.getYAxes = function () { return yaxes; }; - plot.c2p = canvasToAxisCoords; - plot.p2c = axisToCanvasCoords; - plot.getOptions = function () { return options; }; - plot.highlight = highlight; - plot.unhighlight = unhighlight; - plot.triggerRedrawOverlay = triggerRedrawOverlay; - plot.pointOffset = function(point) { - return { - left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), - top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) - }; - }; - plot.shutdown = shutdown; - plot.destroy = function () { - shutdown(); - placeholder.removeData("plot").empty(); - - series = []; - options = null; - surface = null; - overlay = null; - eventHolder = null; - ctx = null; - octx = null; - xaxes = []; - yaxes = []; - hooks = null; - highlights = []; - plot = null; - }; - plot.resize = function () { - var width = placeholder.width(), - height = placeholder.height(); - surface.resize(width, height); - overlay.resize(width, height); - }; - - // public attributes - plot.hooks = hooks; - - // initialize - initPlugins(plot); - parseOptions(options_); - setupCanvases(); - setData(data_); - setupGrid(); - draw(); - bindEvents(); - - - function executeHooks(hook, args) { - args = [plot].concat(args); - for (var i = 0; i < hook.length; ++i) - hook[i].apply(this, args); - } - - function initPlugins() { - - // References to key classes, allowing plugins to modify them - - var classes = { - Canvas: Canvas - }; - - for (var i = 0; i < plugins.length; ++i) { - var p = plugins[i]; - p.init(plot, classes); - if (p.options) - $.extend(true, options, p.options); - } - } - - function parseOptions(opts) { - - $.extend(true, options, opts); - - // $.extend merges arrays, rather than replacing them. When less - // colors are provided than the size of the default palette, we - // end up with those colors plus the remaining defaults, which is - // not expected behavior; avoid it by replacing them here. - - if (opts && opts.colors) { - options.colors = opts.colors; - } - - if (options.xaxis.color == null) - options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - if (options.yaxis.color == null) - options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility - options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; - if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility - options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; - - if (options.grid.borderColor == null) - options.grid.borderColor = options.grid.color; - if (options.grid.tickColor == null) - options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); - - // Fill in defaults for axis options, including any unspecified - // font-spec fields, if a font-spec was provided. - - // If no x/y axis options were provided, create one of each anyway, - // since the rest of the code assumes that they exist. - - var i, axisOptions, axisCount, - fontSize = placeholder.css("font-size"), - fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, - fontDefaults = { - style: placeholder.css("font-style"), - size: Math.round(0.8 * fontSizeDefault), - variant: placeholder.css("font-variant"), - weight: placeholder.css("font-weight"), - family: placeholder.css("font-family") - }; - - axisCount = options.xaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.xaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.xaxis, axisOptions); - options.xaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - axisCount = options.yaxes.length || 1; - for (i = 0; i < axisCount; ++i) { - - axisOptions = options.yaxes[i]; - if (axisOptions && !axisOptions.tickColor) { - axisOptions.tickColor = axisOptions.color; - } - - axisOptions = $.extend(true, {}, options.yaxis, axisOptions); - options.yaxes[i] = axisOptions; - - if (axisOptions.font) { - axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); - if (!axisOptions.font.color) { - axisOptions.font.color = axisOptions.color; - } - if (!axisOptions.font.lineHeight) { - axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15); - } - } - } - - // backwards compatibility, to be removed in future - if (options.xaxis.noTicks && options.xaxis.ticks == null) - options.xaxis.ticks = options.xaxis.noTicks; - if (options.yaxis.noTicks && options.yaxis.ticks == null) - options.yaxis.ticks = options.yaxis.noTicks; - if (options.x2axis) { - options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); - options.xaxes[1].position = "top"; - // Override the inherit to allow the axis to auto-scale - if (options.x2axis.min == null) { - options.xaxes[1].min = null; - } - if (options.x2axis.max == null) { - options.xaxes[1].max = null; - } - } - if (options.y2axis) { - options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); - options.yaxes[1].position = "right"; - // Override the inherit to allow the axis to auto-scale - if (options.y2axis.min == null) { - options.yaxes[1].min = null; - } - if (options.y2axis.max == null) { - options.yaxes[1].max = null; - } - } - if (options.grid.coloredAreas) - options.grid.markings = options.grid.coloredAreas; - if (options.grid.coloredAreasColor) - options.grid.markingsColor = options.grid.coloredAreasColor; - if (options.lines) - $.extend(true, options.series.lines, options.lines); - if (options.points) - $.extend(true, options.series.points, options.points); - if (options.bars) - $.extend(true, options.series.bars, options.bars); - if (options.shadowSize != null) - options.series.shadowSize = options.shadowSize; - if (options.highlightColor != null) - options.series.highlightColor = options.highlightColor; - - // save options on axes for future reference - for (i = 0; i < options.xaxes.length; ++i) - getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; - for (i = 0; i < options.yaxes.length; ++i) - getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; - - // add hooks from options - for (var n in hooks) - if (options.hooks[n] && options.hooks[n].length) - hooks[n] = hooks[n].concat(options.hooks[n]); - - executeHooks(hooks.processOptions, [options]); - } - - function setData(d) { - series = parseData(d); - fillInSeriesOptions(); - processData(); - } - - function parseData(d) { - var res = []; - for (var i = 0; i < d.length; ++i) { - var s = $.extend(true, {}, options.series); - - if (d[i].data != null) { - s.data = d[i].data; // move the data instead of deep-copy - delete d[i].data; - - $.extend(true, s, d[i]); - - d[i].data = s.data; - } - else - s.data = d[i]; - res.push(s); - } - - return res; - } - - function axisNumber(obj, coord) { - var a = obj[coord + "axis"]; - if (typeof a == "object") // if we got a real axis, extract number - a = a.n; - if (typeof a != "number") - a = 1; // default to first axis - return a; - } - - function allAxes() { - // return flat array without annoying null entries - return $.grep(xaxes.concat(yaxes), function (a) { return a; }); - } - - function canvasToAxisCoords(pos) { - // return an object with x/y corresponding to all used axes - var res = {}, i, axis; - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) - res["x" + axis.n] = axis.c2p(pos.left); - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) - res["y" + axis.n] = axis.c2p(pos.top); - } - - if (res.x1 !== undefined) - res.x = res.x1; - if (res.y1 !== undefined) - res.y = res.y1; - - return res; - } - - function axisToCanvasCoords(pos) { - // get canvas coords from the first pair of x/y found in pos - var res = {}, i, axis, key; - - for (i = 0; i < xaxes.length; ++i) { - axis = xaxes[i]; - if (axis && axis.used) { - key = "x" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "x"; - - if (pos[key] != null) { - res.left = axis.p2c(pos[key]); - break; - } - } - } - - for (i = 0; i < yaxes.length; ++i) { - axis = yaxes[i]; - if (axis && axis.used) { - key = "y" + axis.n; - if (pos[key] == null && axis.n == 1) - key = "y"; - - if (pos[key] != null) { - res.top = axis.p2c(pos[key]); - break; - } - } - } - - return res; - } - - function getOrCreateAxis(axes, number) { - if (!axes[number - 1]) - axes[number - 1] = { - n: number, // save the number for future reference - direction: axes == xaxes ? "x" : "y", - options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) - }; - - return axes[number - 1]; - } - - function fillInSeriesOptions() { - - var neededColors = series.length, maxIndex = -1, i; - - // Subtract the number of series that already have fixed colors or - // color indexes from the number that we still need to generate. - - for (i = 0; i < series.length; ++i) { - var sc = series[i].color; - if (sc != null) { - neededColors--; - if (typeof sc == "number" && sc > maxIndex) { - maxIndex = sc; - } - } - } - - // If any of the series have fixed color indexes, then we need to - // generate at least as many colors as the highest index. - - if (neededColors <= maxIndex) { - neededColors = maxIndex + 1; - } - - // Generate all the colors, using first the option colors and then - // variations on those colors once they're exhausted. - - var c, colors = [], colorPool = options.colors, - colorPoolSize = colorPool.length, variation = 0; - - for (i = 0; i < neededColors; i++) { - - c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); - - // Each time we exhaust the colors in the pool we adjust - // a scaling factor used to produce more variations on - // those colors. The factor alternates negative/positive - // to produce lighter/darker colors. - - // Reset the variation after every few cycles, or else - // it will end up producing only white or black colors. - - if (i % colorPoolSize == 0 && i) { - if (variation >= 0) { - if (variation < 0.5) { - variation = -variation - 0.2; - } else variation = 0; - } else variation = -variation; - } - - colors[i] = c.scale('rgb', 1 + variation); - } - - // Finalize the series options, filling in their colors - - var colori = 0, s; - for (i = 0; i < series.length; ++i) { - s = series[i]; - - // assign colors - if (s.color == null) { - s.color = colors[colori].toString(); - ++colori; - } - else if (typeof s.color == "number") - s.color = colors[s.color].toString(); - - // turn on lines automatically in case nothing is set - if (s.lines.show == null) { - var v, show = true; - for (v in s) - if (s[v] && s[v].show) { - show = false; - break; - } - if (show) - s.lines.show = true; - } - - // If nothing was provided for lines.zero, default it to match - // lines.fill, since areas by default should extend to zero. - - if (s.lines.zero == null) { - s.lines.zero = !!s.lines.fill; - } - - // setup axes - s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); - s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); - } - } - - function processData() { - var topSentry = Number.POSITIVE_INFINITY, - bottomSentry = Number.NEGATIVE_INFINITY, - fakeInfinity = Number.MAX_VALUE, - i, j, k, m, length, - s, points, ps, x, y, axis, val, f, p, - data, format; - - function updateAxis(axis, min, max) { - if (min < axis.datamin && min != -fakeInfinity) - axis.datamin = min; - if (max > axis.datamax && max != fakeInfinity) - axis.datamax = max; - } - - $.each(allAxes(), function (_, axis) { - // init axis - axis.datamin = topSentry; - axis.datamax = bottomSentry; - axis.used = false; - }); - - for (i = 0; i < series.length; ++i) { - s = series[i]; - s.datapoints = { points: [] }; - - executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); - } - - // first pass: clean and copy data - for (i = 0; i < series.length; ++i) { - s = series[i]; - - data = s.data; - format = s.datapoints.format; - - if (!format) { - format = []; - // find out how to copy - format.push({ x: true, number: true, required: true }); - format.push({ y: true, number: true, required: true }); - - if (s.bars.show || (s.lines.show && s.lines.fill)) { - var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); - format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); - if (s.bars.horizontal) { - delete format[format.length - 1].y; - format[format.length - 1].x = true; - } - } - - s.datapoints.format = format; - } - - if (s.datapoints.pointsize != null) - continue; // already filled in - - s.datapoints.pointsize = format.length; - - ps = s.datapoints.pointsize; - points = s.datapoints.points; - - var insertSteps = s.lines.show && s.lines.steps; - s.xaxis.used = s.yaxis.used = true; - - for (j = k = 0; j < data.length; ++j, k += ps) { - p = data[j]; - - var nullify = p == null; - if (!nullify) { - for (m = 0; m < ps; ++m) { - val = p[m]; - f = format[m]; - - if (f) { - if (f.number && val != null) { - val = +val; // convert to number - if (isNaN(val)) - val = null; - else if (val == Infinity) - val = fakeInfinity; - else if (val == -Infinity) - val = -fakeInfinity; - } - - if (val == null) { - if (f.required) - nullify = true; - - if (f.defaultValue != null) - val = f.defaultValue; - } - } - - points[k + m] = val; - } - } - - if (nullify) { - for (m = 0; m < ps; ++m) { - val = points[k + m]; - if (val != null) { - f = format[m]; - // extract min/max info - if (f.autoscale !== false) { - if (f.x) { - updateAxis(s.xaxis, val, val); - } - if (f.y) { - updateAxis(s.yaxis, val, val); - } - } - } - points[k + m] = null; - } - } - else { - // a little bit of line specific stuff that - // perhaps shouldn't be here, but lacking - // better means... - if (insertSteps && k > 0 - && points[k - ps] != null - && points[k - ps] != points[k] - && points[k - ps + 1] != points[k + 1]) { - // copy the point to make room for a middle point - for (m = 0; m < ps; ++m) - points[k + ps + m] = points[k + m]; - - // middle point has same y - points[k + 1] = points[k - ps + 1]; - - // we've added a point, better reflect that - k += ps; - } - } - } - } - - // give the hooks a chance to run - for (i = 0; i < series.length; ++i) { - s = series[i]; - - executeHooks(hooks.processDatapoints, [ s, s.datapoints]); - } - - // second pass: find datamax/datamin for auto-scaling - for (i = 0; i < series.length; ++i) { - s = series[i]; - points = s.datapoints.points; - ps = s.datapoints.pointsize; - format = s.datapoints.format; - - var xmin = topSentry, ymin = topSentry, - xmax = bottomSentry, ymax = bottomSentry; - - for (j = 0; j < points.length; j += ps) { - if (points[j] == null) - continue; - - for (m = 0; m < ps; ++m) { - val = points[j + m]; - f = format[m]; - if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) - continue; - - if (f.x) { - if (val < xmin) - xmin = val; - if (val > xmax) - xmax = val; - } - if (f.y) { - if (val < ymin) - ymin = val; - if (val > ymax) - ymax = val; - } - } - } - - if (s.bars.show) { - // make sure we got room for the bar on the dancing floor - var delta; - - switch (s.bars.align) { - case "left": - delta = 0; - break; - case "right": - delta = -s.bars.barWidth; - break; - default: - delta = -s.bars.barWidth / 2; - } - - if (s.bars.horizontal) { - ymin += delta; - ymax += delta + s.bars.barWidth; - } - else { - xmin += delta; - xmax += delta + s.bars.barWidth; - } - } - - updateAxis(s.xaxis, xmin, xmax); - updateAxis(s.yaxis, ymin, ymax); - } - - $.each(allAxes(), function (_, axis) { - if (axis.datamin == topSentry) - axis.datamin = null; - if (axis.datamax == bottomSentry) - axis.datamax = null; - }); - } - - function setupCanvases() { - - // Make sure the placeholder is clear of everything except canvases - // from a previous plot in this container that we'll try to re-use. - - placeholder.css("padding", 0) // padding messes up the positioning - .children().filter(function(){ - return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base'); - }).remove(); - - if (placeholder.css("position") == 'static') - placeholder.css("position", "relative"); // for positioning labels and overlay - - surface = new Canvas("flot-base", placeholder); - overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features - - ctx = surface.context; - octx = overlay.context; - - // define which element we're listening for events on - eventHolder = $(overlay.element).unbind(); - - // If we're re-using a plot object, shut down the old one - - var existing = placeholder.data("plot"); - - if (existing) { - existing.shutdown(); - overlay.clear(); - } - - // save in case we get replotted - placeholder.data("plot", plot); - } - - function bindEvents() { - // bind events - if (options.grid.hoverable) { - eventHolder.mousemove(onMouseMove); - - // Use bind, rather than .mouseleave, because we officially - // still support jQuery 1.2.6, which doesn't define a shortcut - // for mouseenter or mouseleave. This was a bug/oversight that - // was fixed somewhere around 1.3.x. We can return to using - // .mouseleave when we drop support for 1.2.6. - - eventHolder.bind("mouseleave", onMouseLeave); - } - - if (options.grid.clickable) - eventHolder.click(onClick); - - executeHooks(hooks.bindEvents, [eventHolder]); - } - - function shutdown() { - if (redrawTimeout) - clearTimeout(redrawTimeout); - - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mouseleave", onMouseLeave); - eventHolder.unbind("click", onClick); - - executeHooks(hooks.shutdown, [eventHolder]); - } - - function setTransformationHelpers(axis) { - // set helper functions on the axis, assumes plot area - // has been computed already - - function identity(x) { return x; } - - var s, m, t = axis.options.transform || identity, - it = axis.options.inverseTransform; - - // precompute how much the axis is scaling a point - // in canvas space - if (axis.direction == "x") { - s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); - m = Math.min(t(axis.max), t(axis.min)); - } - else { - s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); - s = -s; - m = Math.max(t(axis.max), t(axis.min)); - } - - // data point to canvas coordinate - if (t == identity) // slight optimization - axis.p2c = function (p) { return (p - m) * s; }; - else - axis.p2c = function (p) { return (t(p) - m) * s; }; - // canvas coordinate to data point - if (!it) - axis.c2p = function (c) { return m + c / s; }; - else - axis.c2p = function (c) { return it(m + c / s); }; - } - - function measureTickLabels(axis) { - - var opts = axis.options, - ticks = axis.ticks || [], - labelWidth = opts.labelWidth || 0, - labelHeight = opts.labelHeight || 0, - maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = opts.font || "flot-tick-label tickLabel"; - - for (var i = 0; i < ticks.length; ++i) { - - var t = ticks[i]; - - if (!t.label) - continue; - - var info = surface.getTextInfo(layer, t.label, font, null, maxWidth); - - labelWidth = Math.max(labelWidth, info.width); - labelHeight = Math.max(labelHeight, info.height); - } - - axis.labelWidth = opts.labelWidth || labelWidth; - axis.labelHeight = opts.labelHeight || labelHeight; - } - - function allocateAxisBoxFirstPhase(axis) { - // find the bounding box of the axis by looking at label - // widths/heights and ticks, make room by diminishing the - // plotOffset; this first phase only looks at one - // dimension per axis, the other dimension depends on the - // other axes so will have to wait - - var lw = axis.labelWidth, - lh = axis.labelHeight, - pos = axis.options.position, - isXAxis = axis.direction === "x", - tickLength = axis.options.tickLength, - axisMargin = options.grid.axisMargin, - padding = options.grid.labelMargin, - innermost = true, - outermost = true, - first = true, - found = false; - - // Determine the axis's position in its direction and on its side - - $.each(isXAxis ? xaxes : yaxes, function(i, a) { - if (a && (a.show || a.reserveSpace)) { - if (a === axis) { - found = true; - } else if (a.options.position === pos) { - if (found) { - outermost = false; - } else { - innermost = false; - } - } - if (!found) { - first = false; - } - } - }); - - // The outermost axis on each side has no margin - - if (outermost) { - axisMargin = 0; - } - - // The ticks for the first axis in each direction stretch across - - if (tickLength == null) { - tickLength = first ? "full" : 5; - } - - if (!isNaN(+tickLength)) - padding += +tickLength; - - if (isXAxis) { - lh += padding; - - if (pos == "bottom") { - plotOffset.bottom += lh + axisMargin; - axis.box = { top: surface.height - plotOffset.bottom, height: lh }; - } - else { - axis.box = { top: plotOffset.top + axisMargin, height: lh }; - plotOffset.top += lh + axisMargin; - } - } - else { - lw += padding; - - if (pos == "left") { - axis.box = { left: plotOffset.left + axisMargin, width: lw }; - plotOffset.left += lw + axisMargin; - } - else { - plotOffset.right += lw + axisMargin; - axis.box = { left: surface.width - plotOffset.right, width: lw }; - } - } - - // save for future reference - axis.position = pos; - axis.tickLength = tickLength; - axis.box.padding = padding; - axis.innermost = innermost; - } - - function allocateAxisBoxSecondPhase(axis) { - // now that all axis boxes have been placed in one - // dimension, we can set the remaining dimension coordinates - if (axis.direction == "x") { - axis.box.left = plotOffset.left - axis.labelWidth / 2; - axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; - } - else { - axis.box.top = plotOffset.top - axis.labelHeight / 2; - axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; - } - } - - function adjustLayoutForThingsStickingOut() { - // possibly adjust plot offset to ensure everything stays - // inside the canvas and isn't clipped off - - var minMargin = options.grid.minBorderMargin, - axis, i; - - // check stuff from the plot (FIXME: this should just read - // a value from the series, otherwise it's impossible to - // customize) - if (minMargin == null) { - minMargin = 0; - for (i = 0; i < series.length; ++i) - minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); - } - - var margins = { - left: minMargin, - right: minMargin, - top: minMargin, - bottom: minMargin - }; - - // check axis labels, note we don't check the actual - // labels but instead use the overall width/height to not - // jump as much around with replots - $.each(allAxes(), function (_, axis) { - if (axis.reserveSpace && axis.ticks && axis.ticks.length) { - if (axis.direction === "x") { - margins.left = Math.max(margins.left, axis.labelWidth / 2); - margins.right = Math.max(margins.right, axis.labelWidth / 2); - } else { - margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2); - margins.top = Math.max(margins.top, axis.labelHeight / 2); - } - } - }); - - plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left)); - plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right)); - plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top)); - plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom)); - } - - function setupGrid() { - var i, axes = allAxes(), showGrid = options.grid.show; - - // Initialize the plot's offset from the edge of the canvas - - for (var a in plotOffset) { - var margin = options.grid.margin || 0; - plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; - } - - executeHooks(hooks.processOffset, [plotOffset]); - - // If the grid is visible, add its border width to the offset - - for (var a in plotOffset) { - if(typeof(options.grid.borderWidth) == "object") { - plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; - } - else { - plotOffset[a] += showGrid ? options.grid.borderWidth : 0; - } - } - - $.each(axes, function (_, axis) { - var axisOpts = axis.options; - axis.show = axisOpts.show == null ? axis.used : axisOpts.show; - axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace; - setRange(axis); - }); - - if (showGrid) { - - var allocatedAxes = $.grep(axes, function (axis) { - return axis.show || axis.reserveSpace; - }); - - $.each(allocatedAxes, function (_, axis) { - // make the ticks - setupTickGeneration(axis); - setTicks(axis); - snapRangeToTicks(axis, axis.ticks); - // find labelWidth/Height for axis - measureTickLabels(axis); - }); - - // with all dimensions calculated, we can compute the - // axis bounding boxes, start from the outside - // (reverse order) - for (i = allocatedAxes.length - 1; i >= 0; --i) - allocateAxisBoxFirstPhase(allocatedAxes[i]); - - // make sure we've got enough space for things that - // might stick out - adjustLayoutForThingsStickingOut(); - - $.each(allocatedAxes, function (_, axis) { - allocateAxisBoxSecondPhase(axis); - }); - } - - plotWidth = surface.width - plotOffset.left - plotOffset.right; - plotHeight = surface.height - plotOffset.bottom - plotOffset.top; - - // now we got the proper plot dimensions, we can compute the scaling - $.each(axes, function (_, axis) { - setTransformationHelpers(axis); - }); - - if (showGrid) { - drawAxisLabels(); - } - - insertLegend(); - } - - function setRange(axis) { - var opts = axis.options, - min = +(opts.min != null ? opts.min : axis.datamin), - max = +(opts.max != null ? opts.max : axis.datamax), - delta = max - min; - - if (delta == 0.0) { - // degenerate case - var widen = max == 0 ? 1 : 0.01; - - if (opts.min == null) - min -= widen; - // always widen max if we couldn't widen min to ensure we - // don't fall into min == max which doesn't work - if (opts.max == null || opts.min != null) - max += widen; - } - else { - // consider autoscaling - var margin = opts.autoscaleMargin; - if (margin != null) { - if (opts.min == null) { - min -= delta * margin; - // make sure we don't go below zero if all values - // are positive - if (min < 0 && axis.datamin != null && axis.datamin >= 0) - min = 0; - } - if (opts.max == null) { - max += delta * margin; - if (max > 0 && axis.datamax != null && axis.datamax <= 0) - max = 0; - } - } - } - axis.min = min; - axis.max = max; - } - - function setupTickGeneration(axis) { - var opts = axis.options; - - // estimate number of ticks - var noTicks; - if (typeof opts.ticks == "number" && opts.ticks > 0) - noTicks = opts.ticks; - else - // heuristic based on the model a*sqrt(x) fitted to - // some data points that seemed reasonable - noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); - - var delta = (axis.max - axis.min) / noTicks, - dec = -Math.floor(Math.log(delta) / Math.LN10), - maxDec = opts.tickDecimals; - - if (maxDec != null && dec > maxDec) { - dec = maxDec; - } - - var magn = Math.pow(10, -dec), - norm = delta / magn, // norm is between 1.0 and 10.0 - size; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - // special case for 2.5, requires an extra decimal - if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { - size = 2.5; - ++dec; - } - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - - if (opts.minTickSize != null && size < opts.minTickSize) { - size = opts.minTickSize; - } - - axis.delta = delta; - axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); - axis.tickSize = opts.tickSize || size; - - // Time mode was moved to a plug-in in 0.8, and since so many people use it - // we'll add an especially friendly reminder to make sure they included it. - - if (opts.mode == "time" && !axis.tickGenerator) { - throw new Error("Time mode requires the flot.time plugin."); - } - - // Flot supports base-10 axes; any other mode else is handled by a plug-in, - // like flot.time.js. - - if (!axis.tickGenerator) { - - axis.tickGenerator = function (axis) { - - var ticks = [], - start = floorInBase(axis.min, axis.tickSize), - i = 0, - v = Number.NaN, - prev; - - do { - prev = v; - v = start + i * axis.tickSize; - ticks.push(v); - ++i; - } while (v < axis.max && v != prev); - return ticks; - }; - - axis.tickFormatter = function (value, axis) { - - var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; - var formatted = "" + Math.round(value * factor) / factor; - - // If tickDecimals was specified, ensure that we have exactly that - // much precision; otherwise default to the value's own precision. - - if (axis.tickDecimals != null) { - var decimal = formatted.indexOf("."); - var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; - if (precision < axis.tickDecimals) { - return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); - } - } - - return formatted; - }; - } - - if ($.isFunction(opts.tickFormatter)) - axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; - - if (opts.alignTicksWithAxis != null) { - var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; - if (otherAxis && otherAxis.used && otherAxis != axis) { - // consider snapping min/max to outermost nice ticks - var niceTicks = axis.tickGenerator(axis); - if (niceTicks.length > 0) { - if (opts.min == null) - axis.min = Math.min(axis.min, niceTicks[0]); - if (opts.max == null && niceTicks.length > 1) - axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); - } - - axis.tickGenerator = function (axis) { - // copy ticks, scaled to this axis - var ticks = [], v, i; - for (i = 0; i < otherAxis.ticks.length; ++i) { - v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); - v = axis.min + v * (axis.max - axis.min); - ticks.push(v); - } - return ticks; - }; - - // we might need an extra decimal since forced - // ticks don't necessarily fit naturally - if (!axis.mode && opts.tickDecimals == null) { - var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), - ts = axis.tickGenerator(axis); - - // only proceed if the tick interval rounded - // with an extra decimal doesn't give us a - // zero at end - if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) - axis.tickDecimals = extraDec; - } - } - } - } - - function setTicks(axis) { - var oticks = axis.options.ticks, ticks = []; - if (oticks == null || (typeof oticks == "number" && oticks > 0)) - ticks = axis.tickGenerator(axis); - else if (oticks) { - if ($.isFunction(oticks)) - // generate the ticks - ticks = oticks(axis); - else - ticks = oticks; - } - - // clean up/labelify the supplied ticks, copy them over - var i, v; - axis.ticks = []; - for (i = 0; i < ticks.length; ++i) { - var label = null; - var t = ticks[i]; - if (typeof t == "object") { - v = +t[0]; - if (t.length > 1) - label = t[1]; - } - else - v = +t; - if (label == null) - label = axis.tickFormatter(v, axis); - if (!isNaN(v)) - axis.ticks.push({ v: v, label: label }); - } - } - - function snapRangeToTicks(axis, ticks) { - if (axis.options.autoscaleMargin && ticks.length > 0) { - // snap to ticks - if (axis.options.min == null) - axis.min = Math.min(axis.min, ticks[0].v); - if (axis.options.max == null && ticks.length > 1) - axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); - } - } - - function draw() { - - surface.clear(); - - executeHooks(hooks.drawBackground, [ctx]); - - var grid = options.grid; - - // draw background, if any - if (grid.show && grid.backgroundColor) - drawBackground(); - - if (grid.show && !grid.aboveData) { - drawGrid(); - } - - for (var i = 0; i < series.length; ++i) { - executeHooks(hooks.drawSeries, [ctx, series[i]]); - drawSeries(series[i]); - } - - executeHooks(hooks.draw, [ctx]); - - if (grid.show && grid.aboveData) { - drawGrid(); - } - - surface.render(); - - // A draw implies that either the axes or data have changed, so we - // should probably update the overlay highlights as well. - - triggerRedrawOverlay(); - } - - function extractRange(ranges, coord) { - var axis, from, to, key, axes = allAxes(); - - for (var i = 0; i < axes.length; ++i) { - axis = axes[i]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? xaxes[0] : yaxes[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function drawBackground() { - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); - ctx.fillRect(0, 0, plotWidth, plotHeight); - ctx.restore(); - } - - function drawGrid() { - var i, axes, bw, bc; - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // draw markings - var markings = options.grid.markings; - if (markings) { - if ($.isFunction(markings)) { - axes = plot.getAxes(); - // xmin etc. is backwards compatibility, to be - // removed in the future - axes.xmin = axes.xaxis.min; - axes.xmax = axes.xaxis.max; - axes.ymin = axes.yaxis.min; - axes.ymax = axes.yaxis.max; - - markings = markings(axes); - } - - for (i = 0; i < markings.length; ++i) { - var m = markings[i], - xrange = extractRange(m, "x"), - yrange = extractRange(m, "y"); - - // fill in missing - if (xrange.from == null) - xrange.from = xrange.axis.min; - if (xrange.to == null) - xrange.to = xrange.axis.max; - if (yrange.from == null) - yrange.from = yrange.axis.min; - if (yrange.to == null) - yrange.to = yrange.axis.max; - - // clip - if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || - yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) - continue; - - xrange.from = Math.max(xrange.from, xrange.axis.min); - xrange.to = Math.min(xrange.to, xrange.axis.max); - yrange.from = Math.max(yrange.from, yrange.axis.min); - yrange.to = Math.min(yrange.to, yrange.axis.max); - - var xequal = xrange.from === xrange.to, - yequal = yrange.from === yrange.to; - - if (xequal && yequal) { - continue; - } - - // then draw - xrange.from = Math.floor(xrange.axis.p2c(xrange.from)); - xrange.to = Math.floor(xrange.axis.p2c(xrange.to)); - yrange.from = Math.floor(yrange.axis.p2c(yrange.from)); - yrange.to = Math.floor(yrange.axis.p2c(yrange.to)); - - if (xequal || yequal) { - var lineWidth = m.lineWidth || options.grid.markingsLineWidth, - subPixel = lineWidth % 2 ? 0.5 : 0; - ctx.beginPath(); - ctx.strokeStyle = m.color || options.grid.markingsColor; - ctx.lineWidth = lineWidth; - if (xequal) { - ctx.moveTo(xrange.to + subPixel, yrange.from); - ctx.lineTo(xrange.to + subPixel, yrange.to); - } else { - ctx.moveTo(xrange.from, yrange.to + subPixel); - ctx.lineTo(xrange.to, yrange.to + subPixel); - } - ctx.stroke(); - } else { - ctx.fillStyle = m.color || options.grid.markingsColor; - ctx.fillRect(xrange.from, yrange.to, - xrange.to - xrange.from, - yrange.from - yrange.to); - } - } - } - - // draw the ticks - axes = allAxes(); - bw = options.grid.borderWidth; - - for (var j = 0; j < axes.length; ++j) { - var axis = axes[j], box = axis.box, - t = axis.tickLength, x, y, xoff, yoff; - if (!axis.show || axis.ticks.length == 0) - continue; - - ctx.lineWidth = 1; - - // find the edges - if (axis.direction == "x") { - x = 0; - if (t == "full") - y = (axis.position == "top" ? 0 : plotHeight); - else - y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); - } - else { - y = 0; - if (t == "full") - x = (axis.position == "left" ? 0 : plotWidth); - else - x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); - } - - // draw tick bar - if (!axis.innermost) { - ctx.strokeStyle = axis.options.color; - ctx.beginPath(); - xoff = yoff = 0; - if (axis.direction == "x") - xoff = plotWidth + 1; - else - yoff = plotHeight + 1; - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") { - y = Math.floor(y) + 0.5; - } else { - x = Math.floor(x) + 0.5; - } - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - ctx.stroke(); - } - - // draw ticks - - ctx.strokeStyle = axis.options.tickColor; - - ctx.beginPath(); - for (i = 0; i < axis.ticks.length; ++i) { - var v = axis.ticks[i].v; - - xoff = yoff = 0; - - if (isNaN(v) || v < axis.min || v > axis.max - // skip those lying on the axes if we got a border - || (t == "full" - && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) - && (v == axis.min || v == axis.max))) - continue; - - if (axis.direction == "x") { - x = axis.p2c(v); - yoff = t == "full" ? -plotHeight : t; - - if (axis.position == "top") - yoff = -yoff; - } - else { - y = axis.p2c(v); - xoff = t == "full" ? -plotWidth : t; - - if (axis.position == "left") - xoff = -xoff; - } - - if (ctx.lineWidth == 1) { - if (axis.direction == "x") - x = Math.floor(x) + 0.5; - else - y = Math.floor(y) + 0.5; - } - - ctx.moveTo(x, y); - ctx.lineTo(x + xoff, y + yoff); - } - - ctx.stroke(); - } - - - // draw border - if (bw) { - // If either borderWidth or borderColor is an object, then draw the border - // line by line instead of as one rectangle - bc = options.grid.borderColor; - if(typeof bw == "object" || typeof bc == "object") { - if (typeof bw !== "object") { - bw = {top: bw, right: bw, bottom: bw, left: bw}; - } - if (typeof bc !== "object") { - bc = {top: bc, right: bc, bottom: bc, left: bc}; - } - - if (bw.top > 0) { - ctx.strokeStyle = bc.top; - ctx.lineWidth = bw.top; - ctx.beginPath(); - ctx.moveTo(0 - bw.left, 0 - bw.top/2); - ctx.lineTo(plotWidth, 0 - bw.top/2); - ctx.stroke(); - } - - if (bw.right > 0) { - ctx.strokeStyle = bc.right; - ctx.lineWidth = bw.right; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); - ctx.lineTo(plotWidth + bw.right / 2, plotHeight); - ctx.stroke(); - } - - if (bw.bottom > 0) { - ctx.strokeStyle = bc.bottom; - ctx.lineWidth = bw.bottom; - ctx.beginPath(); - ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); - ctx.lineTo(0, plotHeight + bw.bottom / 2); - ctx.stroke(); - } - - if (bw.left > 0) { - ctx.strokeStyle = bc.left; - ctx.lineWidth = bw.left; - ctx.beginPath(); - ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); - ctx.lineTo(0- bw.left/2, 0); - ctx.stroke(); - } - } - else { - ctx.lineWidth = bw; - ctx.strokeStyle = options.grid.borderColor; - ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); - } - } - - ctx.restore(); - } - - function drawAxisLabels() { - - $.each(allAxes(), function (_, axis) { - var box = axis.box, - legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", - layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, - font = axis.options.font || "flot-tick-label tickLabel", - tick, x, y, halign, valign; - - // Remove text before checking for axis.show and ticks.length; - // otherwise plugins, like flot-tickrotor, that draw their own - // tick labels will end up with both theirs and the defaults. - - surface.removeText(layer); - - if (!axis.show || axis.ticks.length == 0) - return; - - for (var i = 0; i < axis.ticks.length; ++i) { - - tick = axis.ticks[i]; - if (!tick.label || tick.v < axis.min || tick.v > axis.max) - continue; - - if (axis.direction == "x") { - halign = "center"; - x = plotOffset.left + axis.p2c(tick.v); - if (axis.position == "bottom") { - y = box.top + box.padding; - } else { - y = box.top + box.height - box.padding; - valign = "bottom"; - } - } else { - valign = "middle"; - y = plotOffset.top + axis.p2c(tick.v); - if (axis.position == "left") { - x = box.left + box.width - box.padding; - halign = "right"; - } else { - x = box.left + box.padding; - } - } - - surface.addText(layer, x, y, tick.label, font, null, null, halign, valign); - } - }); - } - - function drawSeries(series) { - if (series.lines.show) - drawSeriesLines(series); - if (series.bars.show) - drawSeriesBars(series); - if (series.points.show) - drawSeriesPoints(series); - } - - function drawSeriesLines(series) { - function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - prevx = null, prevy = null; - - ctx.beginPath(); - for (var i = ps; i < points.length; i += ps) { - var x1 = points[i - ps], y1 = points[i - ps + 1], - x2 = points[i], y2 = points[i + 1]; - - if (x1 == null || x2 == null) - continue; - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min) { - if (y2 < axisy.min) - continue; // line segment is outside - // compute new intersection point - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min) { - if (y1 < axisy.min) - continue; - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max) { - if (y2 > axisy.max) - continue; - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max) { - if (y1 > axisy.max) - continue; - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (x1 != prevx || y1 != prevy) - ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); - - prevx = x2; - prevy = y2; - ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); - } - ctx.stroke(); - } - - function plotLineArea(datapoints, axisx, axisy) { - var points = datapoints.points, - ps = datapoints.pointsize, - bottom = Math.min(Math.max(0, axisy.min), axisy.max), - i = 0, top, areaOpen = false, - ypos = 1, segmentStart = 0, segmentEnd = 0; - - // we process each segment in two turns, first forward - // direction to sketch out top, then once we hit the - // end we go backwards to sketch the bottom - while (true) { - if (ps > 0 && i > points.length + ps) - break; - - i += ps; // ps is negative if going backwards - - var x1 = points[i - ps], - y1 = points[i - ps + ypos], - x2 = points[i], y2 = points[i + ypos]; - - if (areaOpen) { - if (ps > 0 && x1 != null && x2 == null) { - // at turning point - segmentEnd = i; - ps = -ps; - ypos = 2; - continue; - } - - if (ps < 0 && i == segmentStart + ps) { - // done with the reverse sweep - ctx.fill(); - areaOpen = false; - ps = -ps; - ypos = 1; - i = segmentStart = segmentEnd + ps; - continue; - } - } - - if (x1 == null || x2 == null) - continue; - - // clip x values - - // clip with xmin - if (x1 <= x2 && x1 < axisx.min) { - if (x2 < axisx.min) - continue; - y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.min; - } - else if (x2 <= x1 && x2 < axisx.min) { - if (x1 < axisx.min) - continue; - y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.min; - } - - // clip with xmax - if (x1 >= x2 && x1 > axisx.max) { - if (x2 > axisx.max) - continue; - y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x1 = axisx.max; - } - else if (x2 >= x1 && x2 > axisx.max) { - if (x1 > axisx.max) - continue; - y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; - x2 = axisx.max; - } - - if (!areaOpen) { - // open area - ctx.beginPath(); - ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); - areaOpen = true; - } - - // now first check the case where both is outside - if (y1 >= axisy.max && y2 >= axisy.max) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); - continue; - } - else if (y1 <= axisy.min && y2 <= axisy.min) { - ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); - continue; - } - - // else it's a bit more complicated, there might - // be a flat maxed out rectangle first, then a - // triangular cutout or reverse; to find these - // keep track of the current x values - var x1old = x1, x2old = x2; - - // clip the y values, without shortcutting, we - // go through all cases in turn - - // clip with ymin - if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { - x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.min; - } - else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { - x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.min; - } - - // clip with ymax - if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { - x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y1 = axisy.max; - } - else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { - x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; - y2 = axisy.max; - } - - // if the x value was changed we got a rectangle - // to fill - if (x1 != x1old) { - ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); - // it goes to (x1, y1), but we fill that below - } - - // fill triangular section, this sometimes result - // in redundant points if (x1, y1) hasn't changed - // from previous line to, but we just ignore that - ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - - // fill the other rectangle if it's there - if (x2 != x2old) { - ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); - ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); - } - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - ctx.lineJoin = "round"; - - var lw = series.lines.lineWidth, - sw = series.shadowSize; - // FIXME: consider another form of shadow when filling is turned on - if (lw > 0 && sw > 0) { - // draw shadow as a thick and thin line with transparency - ctx.lineWidth = sw; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - // position shadow at angle from the mid of line - var angle = Math.PI/18; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); - ctx.lineWidth = sw/2; - plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); - if (fillStyle) { - ctx.fillStyle = fillStyle; - plotLineArea(series.datapoints, series.xaxis, series.yaxis); - } - - if (lw > 0) - plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); - ctx.restore(); - } - - function drawSeriesPoints(series) { - function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - var x = points[i], y = points[i + 1]; - if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - continue; - - ctx.beginPath(); - x = axisx.p2c(x); - y = axisy.p2c(y) + offset; - if (symbol == "circle") - ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); - else - symbol(ctx, x, y, radius, shadow); - ctx.closePath(); - - if (fillStyle) { - ctx.fillStyle = fillStyle; - ctx.fill(); - } - ctx.stroke(); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var lw = series.points.lineWidth, - sw = series.shadowSize, - radius = series.points.radius, - symbol = series.points.symbol; - - // If the user sets the line width to 0, we change it to a very - // small value. A line width of 0 seems to force the default of 1. - // Doing the conditional here allows the shadow setting to still be - // optional even with a lineWidth of 0. - - if( lw == 0 ) - lw = 0.0001; - - if (lw > 0 && sw > 0) { - // draw shadow in two steps - var w = sw / 2; - ctx.lineWidth = w; - ctx.strokeStyle = "rgba(0,0,0,0.1)"; - plotPoints(series.datapoints, radius, null, w + w/2, true, - series.xaxis, series.yaxis, symbol); - - ctx.strokeStyle = "rgba(0,0,0,0.2)"; - plotPoints(series.datapoints, radius, null, w/2, true, - series.xaxis, series.yaxis, symbol); - } - - ctx.lineWidth = lw; - ctx.strokeStyle = series.color; - plotPoints(series.datapoints, radius, - getFillStyle(series.points, series.color), 0, false, - series.xaxis, series.yaxis, symbol); - ctx.restore(); - } - - function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { - var left, right, bottom, top, - drawLeft, drawRight, drawTop, drawBottom, - tmp; - - // in horizontal mode, we start the bar from the left - // instead of from the bottom so it appears to be - // horizontal rather than vertical - if (horizontal) { - drawBottom = drawRight = drawTop = true; - drawLeft = false; - left = b; - right = x; - top = y + barLeft; - bottom = y + barRight; - - // account for negative bars - if (right < left) { - tmp = right; - right = left; - left = tmp; - drawLeft = true; - drawRight = false; - } - } - else { - drawLeft = drawRight = drawTop = true; - drawBottom = false; - left = x + barLeft; - right = x + barRight; - bottom = b; - top = y; - - // account for negative bars - if (top < bottom) { - tmp = top; - top = bottom; - bottom = tmp; - drawBottom = true; - drawTop = false; - } - } - - // clip - if (right < axisx.min || left > axisx.max || - top < axisy.min || bottom > axisy.max) - return; - - if (left < axisx.min) { - left = axisx.min; - drawLeft = false; - } - - if (right > axisx.max) { - right = axisx.max; - drawRight = false; - } - - if (bottom < axisy.min) { - bottom = axisy.min; - drawBottom = false; - } - - if (top > axisy.max) { - top = axisy.max; - drawTop = false; - } - - left = axisx.p2c(left); - bottom = axisy.p2c(bottom); - right = axisx.p2c(right); - top = axisy.p2c(top); - - // fill the bar - if (fillStyleCallback) { - c.fillStyle = fillStyleCallback(bottom, top); - c.fillRect(left, top, right - left, bottom - top) - } - - // draw outline - if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { - c.beginPath(); - - // FIXME: inline moveTo is buggy with excanvas - c.moveTo(left, bottom); - if (drawLeft) - c.lineTo(left, top); - else - c.moveTo(left, top); - if (drawTop) - c.lineTo(right, top); - else - c.moveTo(right, top); - if (drawRight) - c.lineTo(right, bottom); - else - c.moveTo(right, bottom); - if (drawBottom) - c.lineTo(left, bottom); - else - c.moveTo(left, bottom); - c.stroke(); - } - } - - function drawSeriesBars(series) { - function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) { - var points = datapoints.points, ps = datapoints.pointsize; - - for (var i = 0; i < points.length; i += ps) { - if (points[i] == null) - continue; - drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); - } - } - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - // FIXME: figure out a way to add shadows (for instance along the right edge) - ctx.lineWidth = series.bars.lineWidth; - ctx.strokeStyle = series.color; - - var barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; - plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis); - ctx.restore(); - } - - function getFillStyle(filloptions, seriesColor, bottom, top) { - var fill = filloptions.fill; - if (!fill) - return null; - - if (filloptions.fillColor) - return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); - - var c = $.color.parse(seriesColor); - c.a = typeof fill == "number" ? fill : 0.4; - c.normalize(); - return c.toString(); - } - - function insertLegend() { - - if (options.legend.container != null) { - $(options.legend.container).html(""); - } else { - placeholder.find(".legend").remove(); - } - - if (!options.legend.show) { - return; - } - - var fragments = [], entries = [], rowStarted = false, - lf = options.legend.labelFormatter, s, label; - - // Build a list of legend entries, with each having a label and a color - - for (var i = 0; i < series.length; ++i) { - s = series[i]; - if (s.label) { - label = lf ? lf(s.label, s) : s.label; - if (label) { - entries.push({ - label: label, - color: s.color - }); - } - } - } - - // Sort the legend using either the default or a custom comparator - - if (options.legend.sorted) { - if ($.isFunction(options.legend.sorted)) { - entries.sort(options.legend.sorted); - } else if (options.legend.sorted == "reverse") { - entries.reverse(); - } else { - var ascending = options.legend.sorted != "descending"; - entries.sort(function(a, b) { - return a.label == b.label ? 0 : ( - (a.label < b.label) != ascending ? 1 : -1 // Logical XOR - ); - }); - } - } - - // Generate markup for the list of entries, in their final order - - for (var i = 0; i < entries.length; ++i) { - - var entry = entries[i]; - - if (i % options.legend.noColumns == 0) { - if (rowStarted) - fragments.push(''); - fragments.push(''); - rowStarted = true; - } - - fragments.push( - '
' + - '' + entry.label + '' - ); - } - - if (rowStarted) - fragments.push(''); - - if (fragments.length == 0) - return; - - var table = '' + fragments.join("") + '
'; - if (options.legend.container != null) - $(options.legend.container).html(table); - else { - var pos = "", - p = options.legend.position, - m = options.legend.margin; - if (m[0] == null) - m = [m, m]; - if (p.charAt(0) == "n") - pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; - else if (p.charAt(0) == "s") - pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; - if (p.charAt(1) == "e") - pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; - else if (p.charAt(1) == "w") - pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; - var legend = $('
' + table.replace('style="', 'style="position:absolute;' + pos +';') + '
').appendTo(placeholder); - if (options.legend.backgroundOpacity != 0.0) { - // put in the transparent background - // separately to avoid blended labels and - // label boxes - var c = options.legend.backgroundColor; - if (c == null) { - c = options.grid.backgroundColor; - if (c && typeof c == "string") - c = $.color.parse(c); - else - c = $.color.extract(legend, 'background-color'); - c.a = 1; - c = c.toString(); - } - var div = legend.children(); - $('
').prependTo(legend).css('opacity', options.legend.backgroundOpacity); - } - } - } - - - // interactive features - - var highlights = [], - redrawTimeout = null; - - // returns the data item the mouse is over, or null if none is found - function findNearbyItem(mouseX, mouseY, seriesFilter) { - var maxDistance = options.grid.mouseActiveRadius, - smallestDistance = maxDistance * maxDistance + 1, - item = null, foundPoint = false, i, j, ps; - - for (i = series.length - 1; i >= 0; --i) { - if (!seriesFilter(series[i])) - continue; - - var s = series[i], - axisx = s.xaxis, - axisy = s.yaxis, - points = s.datapoints.points, - mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster - my = axisy.c2p(mouseY), - maxx = maxDistance / axisx.scale, - maxy = maxDistance / axisy.scale; - - ps = s.datapoints.pointsize; - // with inverse transforms, we can't use the maxx/maxy - // optimization, sadly - if (axisx.options.inverseTransform) - maxx = Number.MAX_VALUE; - if (axisy.options.inverseTransform) - maxy = Number.MAX_VALUE; - - if (s.lines.show || s.points.show) { - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1]; - if (x == null) - continue; - - // For points and lines, the cursor must be within a - // certain distance to the data point - if (x - mx > maxx || x - mx < -maxx || - y - my > maxy || y - my < -maxy) - continue; - - // We have to calculate distances in pixels, not in - // data units, because the scales of the axes may be different - var dx = Math.abs(axisx.p2c(x) - mouseX), - dy = Math.abs(axisy.p2c(y) - mouseY), - dist = dx * dx + dy * dy; // we save the sqrt - - // use <= to ensure last point takes precedence - // (last generally means on top of) - if (dist < smallestDistance) { - smallestDistance = dist; - item = [i, j / ps]; - } - } - } - - if (s.bars.show && !item) { // no other point can be nearby - - var barLeft, barRight; - - switch (s.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -s.bars.barWidth; - break; - default: - barLeft = -s.bars.barWidth / 2; - } - - barRight = barLeft + s.bars.barWidth; - - for (j = 0; j < points.length; j += ps) { - var x = points[j], y = points[j + 1], b = points[j + 2]; - if (x == null) - continue; - - // for a bar graph, the cursor must be inside the bar - if (series[i].bars.horizontal ? - (mx <= Math.max(b, x) && mx >= Math.min(b, x) && - my >= y + barLeft && my <= y + barRight) : - (mx >= x + barLeft && mx <= x + barRight && - my >= Math.min(b, y) && my <= Math.max(b, y))) - item = [i, j / ps]; - } - } - } - - if (item) { - i = item[0]; - j = item[1]; - ps = series[i].datapoints.pointsize; - - return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), - dataIndex: j, - series: series[i], - seriesIndex: i }; - } - - return null; - } - - function onMouseMove(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return s["hoverable"] != false; }); - } - - function onMouseLeave(e) { - if (options.grid.hoverable) - triggerClickHoverEvent("plothover", e, - function (s) { return false; }); - } - - function onClick(e) { - triggerClickHoverEvent("plotclick", e, - function (s) { return s["clickable"] != false; }); - } - - // trigger click or hover event (they send the same parameters - // so we share their code) - function triggerClickHoverEvent(eventname, event, seriesFilter) { - var offset = eventHolder.offset(), - canvasX = event.pageX - offset.left - plotOffset.left, - canvasY = event.pageY - offset.top - plotOffset.top, - pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); - - pos.pageX = event.pageX; - pos.pageY = event.pageY; - - var item = findNearbyItem(canvasX, canvasY, seriesFilter); - - if (item) { - // fill in mouse pos for any listeners out there - item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); - item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); - } - - if (options.grid.autoHighlight) { - // clear auto-highlights - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.auto == eventname && - !(item && h.series == item.series && - h.point[0] == item.datapoint[0] && - h.point[1] == item.datapoint[1])) - unhighlight(h.series, h.point); - } - - if (item) - highlight(item.series, item.datapoint, eventname); - } - - placeholder.trigger(eventname, [ pos, item ]); - } - - function triggerRedrawOverlay() { - var t = options.interaction.redrawOverlayInterval; - if (t == -1) { // skip event queue - drawOverlay(); - return; - } - - if (!redrawTimeout) - redrawTimeout = setTimeout(drawOverlay, t); - } - - function drawOverlay() { - redrawTimeout = null; - - // draw highlights - octx.save(); - overlay.clear(); - octx.translate(plotOffset.left, plotOffset.top); - - var i, hi; - for (i = 0; i < highlights.length; ++i) { - hi = highlights[i]; - - if (hi.series.bars.show) - drawBarHighlight(hi.series, hi.point); - else - drawPointHighlight(hi.series, hi.point); - } - octx.restore(); - - executeHooks(hooks.drawOverlay, [octx]); - } - - function highlight(s, point, auto) { - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i == -1) { - highlights.push({ series: s, point: point, auto: auto }); - - triggerRedrawOverlay(); - } - else if (!auto) - highlights[i].auto = false; - } - - function unhighlight(s, point) { - if (s == null && point == null) { - highlights = []; - triggerRedrawOverlay(); - return; - } - - if (typeof s == "number") - s = series[s]; - - if (typeof point == "number") { - var ps = s.datapoints.pointsize; - point = s.datapoints.points.slice(ps * point, ps * (point + 1)); - } - - var i = indexOfHighlight(s, point); - if (i != -1) { - highlights.splice(i, 1); - - triggerRedrawOverlay(); - } - } - - function indexOfHighlight(s, p) { - for (var i = 0; i < highlights.length; ++i) { - var h = highlights[i]; - if (h.series == s && h.point[0] == p[0] - && h.point[1] == p[1]) - return i; - } - return -1; - } - - function drawPointHighlight(series, point) { - var x = point[0], y = point[1], - axisx = series.xaxis, axisy = series.yaxis, - highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); - - if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) - return; - - var pointRadius = series.points.radius + series.points.lineWidth / 2; - octx.lineWidth = pointRadius; - octx.strokeStyle = highlightColor; - var radius = 1.5 * pointRadius; - x = axisx.p2c(x); - y = axisy.p2c(y); - - octx.beginPath(); - if (series.points.symbol == "circle") - octx.arc(x, y, radius, 0, 2 * Math.PI, false); - else - series.points.symbol(octx, x, y, radius, false); - octx.closePath(); - octx.stroke(); - } - - function drawBarHighlight(series, point) { - var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), - fillStyle = highlightColor, - barLeft; - - switch (series.bars.align) { - case "left": - barLeft = 0; - break; - case "right": - barLeft = -series.bars.barWidth; - break; - default: - barLeft = -series.bars.barWidth / 2; - } - - octx.lineWidth = series.bars.lineWidth; - octx.strokeStyle = highlightColor; - - drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, - function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); - } - - function getColorOrGradient(spec, bottom, top, defaultColor) { - if (typeof spec == "string") - return spec; - else { - // assume this is a gradient spec; IE currently only - // supports a simple vertical gradient properly, so that's - // what we support too - var gradient = ctx.createLinearGradient(0, top, 0, bottom); - - for (var i = 0, l = spec.colors.length; i < l; ++i) { - var c = spec.colors[i]; - if (typeof c != "string") { - var co = $.color.parse(defaultColor); - if (c.brightness != null) - co = co.scale('rgb', c.brightness); - if (c.opacity != null) - co.a *= c.opacity; - c = co.toString(); - } - gradient.addColorStop(i / (l - 1), c); - } - - return gradient; - } - } - } - - // Add the plot function to the top level of the jQuery object - - $.plot = function(placeholder, data, options) { - //var t0 = new Date(); - var plot = new Plot($(placeholder), data, options, $.plot.plugins); - //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); - return plot; - }; - - $.plot.version = "0.8.3"; - - $.plot.plugins = []; - - // Also add the plot function as a chainable property - - $.fn.plot = function(data, options) { - return this.each(function() { - $.plot(this, data, options); - }); - }; - - // round to nearby lower multiple of base - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - -})(jQuery); diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.selection.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.selection.js deleted file mode 100644 index c8707b30f4e6f..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.selection.js +++ /dev/null @@ -1,360 +0,0 @@ -/* Flot plugin for selecting regions of a plot. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin supports these options: - -selection: { - mode: null or "x" or "y" or "xy", - color: color, - shape: "round" or "miter" or "bevel", - minSize: number of pixels -} - -Selection support is enabled by setting the mode to one of "x", "y" or "xy". -In "x" mode, the user will only be able to specify the x range, similarly for -"y" mode. For "xy", the selection becomes a rectangle where both ranges can be -specified. "color" is color of the selection (if you need to change the color -later on, you can get to it with plot.getOptions().selection.color). "shape" -is the shape of the corners of the selection. - -"minSize" is the minimum size a selection can be in pixels. This value can -be customized to determine the smallest size a selection can be and still -have the selection rectangle be displayed. When customizing this value, the -fact that it refers to pixels, not axis units must be taken into account. -Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 -minute, setting "minSize" to 1 will not make the minimum selection size 1 -minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent -"plotunselected" events from being fired when the user clicks the mouse without -dragging. - -When selection support is enabled, a "plotselected" event will be emitted on -the DOM element you passed into the plot function. The event handler gets a -parameter with the ranges selected on the axes, like this: - - placeholder.bind( "plotselected", function( event, ranges ) { - alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to) - // similar for yaxis - with multiple axes, the extra ones are in - // x2axis, x3axis, ... - }); - -The "plotselected" event is only fired when the user has finished making the -selection. A "plotselecting" event is fired during the process with the same -parameters as the "plotselected" event, in case you want to know what's -happening while it's happening, - -A "plotunselected" event with no arguments is emitted when the user clicks the -mouse to remove the selection. As stated above, setting "minSize" to 0 will -destroy this behavior. - -The plugin also adds the following methods to the plot object: - -- setSelection( ranges, preventEvent ) - - Set the selection rectangle. The passed in ranges is on the same form as - returned in the "plotselected" event. If the selection mode is "x", you - should put in either an xaxis range, if the mode is "y" you need to put in - an yaxis range and both xaxis and yaxis if the selection mode is "xy", like - this: - - setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } }); - - setSelection will trigger the "plotselected" event when called. If you don't - want that to happen, e.g. if you're inside a "plotselected" handler, pass - true as the second parameter. If you are using multiple axes, you can - specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of - xaxis, the plugin picks the first one it sees. - -- clearSelection( preventEvent ) - - Clear the selection rectangle. Pass in true to avoid getting a - "plotunselected" event. - -- getSelection() - - Returns the current selection in the same format as the "plotselected" - event. If there's currently no selection, the function returns null. - -*/ - -(function ($) { - function init(plot) { - var selection = { - first: { x: -1, y: -1}, second: { x: -1, y: -1}, - show: false, - active: false - }; - - // FIXME: The drag handling implemented here should be - // abstracted out, there's some similar code from a library in - // the navigation plugin, this should be massaged a bit to fit - // the Flot cases here better and reused. Doing this would - // make this plugin much slimmer. - var savedhandlers = {}; - - var mouseUpHandler = null; - - function onMouseMove(e) { - if (selection.active) { - updateSelection(e); - - plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]); - } - } - - function onMouseDown(e) { - if (e.which != 1) // only accept left-click - return; - - // cancel out any text selections - document.body.focus(); - - // prevent text selection and drag in old-school browsers - if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) { - savedhandlers.onselectstart = document.onselectstart; - document.onselectstart = function () { return false; }; - } - if (document.ondrag !== undefined && savedhandlers.ondrag == null) { - savedhandlers.ondrag = document.ondrag; - document.ondrag = function () { return false; }; - } - - setSelectionPos(selection.first, e); - - selection.active = true; - - // this is a bit silly, but we have to use a closure to be - // able to whack the same handler again - mouseUpHandler = function (e) { onMouseUp(e); }; - - $(document).one("mouseup", mouseUpHandler); - } - - function onMouseUp(e) { - mouseUpHandler = null; - - // revert drag stuff for old-school browsers - if (document.onselectstart !== undefined) - document.onselectstart = savedhandlers.onselectstart; - if (document.ondrag !== undefined) - document.ondrag = savedhandlers.ondrag; - - // no more dragging - selection.active = false; - updateSelection(e); - - if (selectionIsSane()) - triggerSelectedEvent(); - else { - // this counts as a clear - plot.getPlaceholder().trigger("plotunselected", [ ]); - plot.getPlaceholder().trigger("plotselecting", [ null ]); - } - - return false; - } - - function getSelection() { - if (!selectionIsSane()) - return null; - - if (!selection.show) return null; - - var r = {}, c1 = selection.first, c2 = selection.second; - $.each(plot.getAxes(), function (name, axis) { - if (axis.used) { - var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); - r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) }; - } - }); - return r; - } - - function triggerSelectedEvent() { - var r = getSelection(); - - plot.getPlaceholder().trigger("plotselected", [ r ]); - - // backwards-compat stuff, to be removed in future - if (r.xaxis && r.yaxis) - plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]); - } - - function clamp(min, value, max) { - return value < min ? min: (value > max ? max: value); - } - - function setSelectionPos(pos, e) { - var o = plot.getOptions(); - var offset = plot.getPlaceholder().offset(); - var plotOffset = plot.getPlotOffset(); - pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width()); - pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height()); - - if (o.selection.mode == "y") - pos.x = pos == selection.first ? 0 : plot.width(); - - if (o.selection.mode == "x") - pos.y = pos == selection.first ? 0 : plot.height(); - } - - function updateSelection(pos) { - if (pos.pageX == null) - return; - - setSelectionPos(selection.second, pos); - if (selectionIsSane()) { - selection.show = true; - plot.triggerRedrawOverlay(); - } - else - clearSelection(true); - } - - function clearSelection(preventEvent) { - if (selection.show) { - selection.show = false; - plot.triggerRedrawOverlay(); - if (!preventEvent) - plot.getPlaceholder().trigger("plotunselected", [ ]); - } - } - - // function taken from markings support in Flot - function extractRange(ranges, coord) { - var axis, from, to, key, axes = plot.getAxes(); - - for (var k in axes) { - axis = axes[k]; - if (axis.direction == coord) { - key = coord + axis.n + "axis"; - if (!ranges[key] && axis.n == 1) - key = coord + "axis"; // support x1axis as xaxis - if (ranges[key]) { - from = ranges[key].from; - to = ranges[key].to; - break; - } - } - } - - // backwards-compat stuff - to be removed in future - if (!ranges[key]) { - axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; - from = ranges[coord + "1"]; - to = ranges[coord + "2"]; - } - - // auto-reverse as an added bonus - if (from != null && to != null && from > to) { - var tmp = from; - from = to; - to = tmp; - } - - return { from: from, to: to, axis: axis }; - } - - function setSelection(ranges, preventEvent) { - var axis, range, o = plot.getOptions(); - - if (o.selection.mode == "y") { - selection.first.x = 0; - selection.second.x = plot.width(); - } - else { - range = extractRange(ranges, "x"); - - selection.first.x = range.axis.p2c(range.from); - selection.second.x = range.axis.p2c(range.to); - } - - if (o.selection.mode == "x") { - selection.first.y = 0; - selection.second.y = plot.height(); - } - else { - range = extractRange(ranges, "y"); - - selection.first.y = range.axis.p2c(range.from); - selection.second.y = range.axis.p2c(range.to); - } - - selection.show = true; - plot.triggerRedrawOverlay(); - if (!preventEvent && selectionIsSane()) - triggerSelectedEvent(); - } - - function selectionIsSane() { - var minSize = plot.getOptions().selection.minSize; - return Math.abs(selection.second.x - selection.first.x) >= minSize && - Math.abs(selection.second.y - selection.first.y) >= minSize; - } - - plot.clearSelection = clearSelection; - plot.setSelection = setSelection; - plot.getSelection = getSelection; - - plot.hooks.bindEvents.push(function(plot, eventHolder) { - var o = plot.getOptions(); - if (o.selection.mode != null) { - eventHolder.mousemove(onMouseMove); - eventHolder.mousedown(onMouseDown); - } - }); - - - plot.hooks.drawOverlay.push(function (plot, ctx) { - // draw selection - if (selection.show && selectionIsSane()) { - var plotOffset = plot.getPlotOffset(); - var o = plot.getOptions(); - - ctx.save(); - ctx.translate(plotOffset.left, plotOffset.top); - - var c = $.color.parse(o.selection.color); - - ctx.strokeStyle = c.scale('a', 0.8).toString(); - ctx.lineWidth = 1; - ctx.lineJoin = o.selection.shape; - ctx.fillStyle = c.scale('a', 0.4).toString(); - - var x = Math.min(selection.first.x, selection.second.x) + 0.5, - y = Math.min(selection.first.y, selection.second.y) + 0.5, - w = Math.abs(selection.second.x - selection.first.x) - 1, - h = Math.abs(selection.second.y - selection.first.y) - 1; - - ctx.fillRect(x, y, w, h); - ctx.strokeRect(x, y, w, h); - - ctx.restore(); - } - }); - - plot.hooks.shutdown.push(function (plot, eventHolder) { - eventHolder.unbind("mousemove", onMouseMove); - eventHolder.unbind("mousedown", onMouseDown); - - if (mouseUpHandler) - $(document).unbind("mouseup", mouseUpHandler); - }); - - } - - $.plot.plugins.push({ - init: init, - options: { - selection: { - mode: null, // one of null, "x", "y" or "xy" - color: "#e8cfac", - shape: "round", // one of "round", "miter", or "bevel" - minSize: 5 // minimum number of pixels - } - }, - name: 'selection', - version: '1.1' - }); -})(jQuery); diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.stack.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.stack.js deleted file mode 100644 index 0d91c0f3c0160..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.stack.js +++ /dev/null @@ -1,188 +0,0 @@ -/* Flot plugin for stacking data sets rather than overlaying them. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The plugin assumes the data is sorted on x (or y if stacking horizontally). -For line charts, it is assumed that if a line has an undefined gap (from a -null point), then the line above it should have the same gap - insert zeros -instead of "null" if you want another behaviour. This also holds for the start -and end of the chart. Note that stacking a mix of positive and negative values -in most instances doesn't make sense (so it looks weird). - -Two or more series are stacked when their "stack" attribute is set to the same -key (which can be any number or string or just "true"). To specify the default -stack, you can set the stack option like this: - - series: { - stack: null/false, true, or a key (number/string) - } - -You can also specify it for a single series, like this: - - $.plot( $("#placeholder"), [{ - data: [ ... ], - stack: true - }]) - -The stacking order is determined by the order of the data series in the array -(later series end up on top of the previous). - -Internally, the plugin modifies the datapoints in each series, adding an -offset to the y value. For line series, extra data points are inserted through -interpolation. If there's a second y value, it's also adjusted (e.g for bar -charts or filled areas). - -*/ - -(function ($) { - var options = { - series: { stack: null } // or number/string - }; - - function init(plot) { - function findMatchingSeries(s, allseries) { - var res = null; - for (var i = 0; i < allseries.length; ++i) { - if (s == allseries[i]) - break; - - if (allseries[i].stack == s.stack) - res = allseries[i]; - } - - return res; - } - - function stackData(plot, s, datapoints) { - if (s.stack == null || s.stack === false) - return; - - var other = findMatchingSeries(s, plot.getData()); - if (!other) - return; - - var ps = datapoints.pointsize, - points = datapoints.points, - otherps = other.datapoints.pointsize, - otherpoints = other.datapoints.points, - newpoints = [], - px, py, intery, qx, qy, bottom, - withlines = s.lines.show, - horizontal = s.bars.horizontal, - withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y), - withsteps = withlines && s.lines.steps, - fromgap = true, - keyOffset = horizontal ? 1 : 0, - accumulateOffset = horizontal ? 0 : 1, - i = 0, j = 0, l, m; - - while (true) { - if (i >= points.length) - break; - - l = newpoints.length; - - if (points[i] == null) { - // copy gaps - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - i += ps; - } - else if (j >= otherpoints.length) { - // for lines, we can't use the rest of the points - if (!withlines) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - } - i += ps; - } - else if (otherpoints[j] == null) { - // oops, got a gap - for (m = 0; m < ps; ++m) - newpoints.push(null); - fromgap = true; - j += otherps; - } - else { - // cases where we actually got two points - px = points[i + keyOffset]; - py = points[i + accumulateOffset]; - qx = otherpoints[j + keyOffset]; - qy = otherpoints[j + accumulateOffset]; - bottom = 0; - - if (px == qx) { - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - newpoints[l + accumulateOffset] += qy; - bottom = qy; - - i += ps; - j += otherps; - } - else if (px > qx) { - // we got past point below, might need to - // insert interpolated extra point - if (withlines && i > 0 && points[i - ps] != null) { - intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px); - newpoints.push(qx); - newpoints.push(intery + qy); - for (m = 2; m < ps; ++m) - newpoints.push(points[i + m]); - bottom = qy; - } - - j += otherps; - } - else { // px < qx - if (fromgap && withlines) { - // if we come from a gap, we just skip this point - i += ps; - continue; - } - - for (m = 0; m < ps; ++m) - newpoints.push(points[i + m]); - - // we might be able to interpolate a point below, - // this can give us a better y - if (withlines && j > 0 && otherpoints[j - otherps] != null) - bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx); - - newpoints[l + accumulateOffset] += bottom; - - i += ps; - } - - fromgap = false; - - if (l != newpoints.length && withbottom) - newpoints[l + 2] += bottom; - } - - // maintain the line steps invariant - if (withsteps && l != newpoints.length && l > 0 - && newpoints[l] != null - && newpoints[l] != newpoints[l - ps] - && newpoints[l + 1] != newpoints[l - ps + 1]) { - for (m = 0; m < ps; ++m) - newpoints[l + ps + m] = newpoints[l + m]; - newpoints[l + 1] = newpoints[l - ps + 1]; - } - } - - datapoints.points = newpoints; - } - - plot.hooks.processDatapoints.push(stackData); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'stack', - version: '1.2' - }); -})(jQuery); diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.symbol.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.symbol.js deleted file mode 100644 index 79f634971b6fa..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.symbol.js +++ /dev/null @@ -1,71 +0,0 @@ -/* Flot plugin that adds some extra symbols for plotting points. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -The symbols are accessed as strings through the standard symbol options: - - series: { - points: { - symbol: "square" // or "diamond", "triangle", "cross" - } - } - -*/ - -(function ($) { - function processRawData(plot, series, datapoints) { - // we normalize the area of each symbol so it is approximately the - // same as a circle of the given radius - - var handlers = { - square: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.rect(x - size, y - size, size + size, size + size); - }, - diamond: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 2s^2 => s = r * sqrt(pi/2) - var size = radius * Math.sqrt(Math.PI / 2); - ctx.moveTo(x - size, y); - ctx.lineTo(x, y - size); - ctx.lineTo(x + size, y); - ctx.lineTo(x, y + size); - ctx.lineTo(x - size, y); - }, - triangle: function (ctx, x, y, radius, shadow) { - // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3)) - var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3)); - var height = size * Math.sin(Math.PI / 3); - ctx.moveTo(x - size/2, y + height/2); - ctx.lineTo(x + size/2, y + height/2); - if (!shadow) { - ctx.lineTo(x, y - height/2); - ctx.lineTo(x - size/2, y + height/2); - } - }, - cross: function (ctx, x, y, radius, shadow) { - // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2 - var size = radius * Math.sqrt(Math.PI) / 2; - ctx.moveTo(x - size, y - size); - ctx.lineTo(x + size, y + size); - ctx.moveTo(x - size, y + size); - ctx.lineTo(x + size, y - size); - } - }; - - var s = series.points.symbol; - if (handlers[s]) - series.points.symbol = handlers[s]; - } - - function init(plot) { - plot.hooks.processDatapoints.push(processRawData); - } - - $.plot.plugins.push({ - init: init, - name: 'symbols', - version: '1.0' - }); -})(jQuery); diff --git a/src/plugins/vis_type_timelion/public/flot/jquery.flot.time.js b/src/plugins/vis_type_timelion/public/flot/jquery.flot.time.js deleted file mode 100644 index 34c1d121259a2..0000000000000 --- a/src/plugins/vis_type_timelion/public/flot/jquery.flot.time.js +++ /dev/null @@ -1,432 +0,0 @@ -/* Pretty handling of time axes. - -Copyright (c) 2007-2014 IOLA and Ole Laursen. -Licensed under the MIT license. - -Set axis.mode to "time" to enable. See the section "Time series data" in -API.txt for details. - -*/ - -(function($) { - - var options = { - xaxis: { - timezone: null, // "browser" for local to the client or timezone for timezone-js - timeformat: null, // format string to use - twelveHourClock: false, // 12 or 24 time in time mode - monthNames: null // list of names of months - } - }; - - // round to nearby lower multiple of base - - function floorInBase(n, base) { - return base * Math.floor(n / base); - } - - // Returns a string with the date d formatted according to fmt. - // A subset of the Open Group's strftime format is supported. - - function formatDate(d, fmt, monthNames, dayNames) { - - if (typeof d.strftime == "function") { - return d.strftime(fmt); - } - - var leftPad = function(n, pad) { - n = "" + n; - pad = "" + (pad == null ? "0" : pad); - return n.length == 1 ? pad + n : n; - }; - - var r = []; - var escape = false; - var hours = d.getHours(); - var isAM = hours < 12; - - if (monthNames == null) { - monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; - } - - if (dayNames == null) { - dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - } - - var hours12; - - if (hours > 12) { - hours12 = hours - 12; - } else if (hours == 0) { - hours12 = 12; - } else { - hours12 = hours; - } - - for (var i = 0; i < fmt.length; ++i) { - - var c = fmt.charAt(i); - - if (escape) { - switch (c) { - case 'a': c = "" + dayNames[d.getDay()]; break; - case 'b': c = "" + monthNames[d.getMonth()]; break; - case 'd': c = leftPad(d.getDate()); break; - case 'e': c = leftPad(d.getDate(), " "); break; - case 'h': // For back-compat with 0.7; remove in 1.0 - case 'H': c = leftPad(hours); break; - case 'I': c = leftPad(hours12); break; - case 'l': c = leftPad(hours12, " "); break; - case 'm': c = leftPad(d.getMonth() + 1); break; - case 'M': c = leftPad(d.getMinutes()); break; - // quarters not in Open Group's strftime specification - case 'q': - c = "" + (Math.floor(d.getMonth() / 3) + 1); break; - case 'S': c = leftPad(d.getSeconds()); break; - case 'y': c = leftPad(d.getFullYear() % 100); break; - case 'Y': c = "" + d.getFullYear(); break; - case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break; - case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break; - case 'w': c = "" + d.getDay(); break; - } - r.push(c); - escape = false; - } else { - if (c == "%") { - escape = true; - } else { - r.push(c); - } - } - } - - return r.join(""); - } - - // To have a consistent view of time-based data independent of which time - // zone the client happens to be in we need a date-like object independent - // of time zones. This is done through a wrapper that only calls the UTC - // versions of the accessor methods. - - function makeUtcWrapper(d) { - - function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) { - sourceObj[sourceMethod] = function() { - return targetObj[targetMethod].apply(targetObj, arguments); - }; - }; - - var utc = { - date: d - }; - - // support strftime, if found - - if (d.strftime != undefined) { - addProxyMethod(utc, "strftime", d, "strftime"); - } - - addProxyMethod(utc, "getTime", d, "getTime"); - addProxyMethod(utc, "setTime", d, "setTime"); - - var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"]; - - for (var p = 0; p < props.length; p++) { - addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]); - addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]); - } - - return utc; - }; - - // select time zone strategy. This returns a date-like object tied to the - // desired timezone - - function dateGenerator(ts, opts) { - if (opts.timezone == "browser") { - return new Date(ts); - } else if (!opts.timezone || opts.timezone == "utc") { - return makeUtcWrapper(new Date(ts)); - } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") { - var d = new timezoneJS.Date(); - // timezone-js is fickle, so be sure to set the time zone before - // setting the time. - d.setTimezone(opts.timezone); - d.setTime(ts); - return d; - } else { - return makeUtcWrapper(new Date(ts)); - } - } - - // map of app. size of time units in milliseconds - - var timeUnitSize = { - "second": 1000, - "minute": 60 * 1000, - "hour": 60 * 60 * 1000, - "day": 24 * 60 * 60 * 1000, - "month": 30 * 24 * 60 * 60 * 1000, - "quarter": 3 * 30 * 24 * 60 * 60 * 1000, - "year": 365.2425 * 24 * 60 * 60 * 1000 - }; - - // the allowed tick sizes, after 1 year we use - // an integer algorithm - - var baseSpec = [ - [1, "second"], [2, "second"], [5, "second"], [10, "second"], - [30, "second"], - [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"], - [30, "minute"], - [1, "hour"], [2, "hour"], [4, "hour"], - [8, "hour"], [12, "hour"], - [1, "day"], [2, "day"], [3, "day"], - [0.25, "month"], [0.5, "month"], [1, "month"], - [2, "month"] - ]; - - // we don't know which variant(s) we'll need yet, but generating both is - // cheap - - var specMonths = baseSpec.concat([[3, "month"], [6, "month"], - [1, "year"]]); - var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"], - [1, "year"]]); - - function init(plot) { - plot.hooks.processOptions.push(function (plot, options) { - $.each(plot.getAxes(), function(axisName, axis) { - - var opts = axis.options; - - if (opts.mode == "time") { - axis.tickGenerator = function(axis) { - - var ticks = []; - var d = dateGenerator(axis.min, opts); - var minSize = 0; - - // make quarter use a possibility if quarters are - // mentioned in either of these options - - var spec = (opts.tickSize && opts.tickSize[1] === - "quarter") || - (opts.minTickSize && opts.minTickSize[1] === - "quarter") ? specQuarters : specMonths; - - if (opts.minTickSize != null) { - if (typeof opts.tickSize == "number") { - minSize = opts.tickSize; - } else { - minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]]; - } - } - - for (var i = 0; i < spec.length - 1; ++i) { - if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] - + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 - && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) { - break; - } - } - - var size = spec[i][0]; - var unit = spec[i][1]; - - // special-case the possibility of several years - - if (unit == "year") { - - // if given a minTickSize in years, just use it, - // ensuring that it's an integer - - if (opts.minTickSize != null && opts.minTickSize[1] == "year") { - size = Math.floor(opts.minTickSize[0]); - } else { - - var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10)); - var norm = (axis.delta / timeUnitSize.year) / magn; - - if (norm < 1.5) { - size = 1; - } else if (norm < 3) { - size = 2; - } else if (norm < 7.5) { - size = 5; - } else { - size = 10; - } - - size *= magn; - } - - // minimum size for years is 1 - - if (size < 1) { - size = 1; - } - } - - axis.tickSize = opts.tickSize || [size, unit]; - var tickSize = axis.tickSize[0]; - unit = axis.tickSize[1]; - - var step = tickSize * timeUnitSize[unit]; - - if (unit == "second") { - d.setSeconds(floorInBase(d.getSeconds(), tickSize)); - } else if (unit == "minute") { - d.setMinutes(floorInBase(d.getMinutes(), tickSize)); - } else if (unit == "hour") { - d.setHours(floorInBase(d.getHours(), tickSize)); - } else if (unit == "month") { - d.setMonth(floorInBase(d.getMonth(), tickSize)); - } else if (unit == "quarter") { - d.setMonth(3 * floorInBase(d.getMonth() / 3, - tickSize)); - } else if (unit == "year") { - d.setFullYear(floorInBase(d.getFullYear(), tickSize)); - } - - // reset smaller components - - d.setMilliseconds(0); - - if (step >= timeUnitSize.minute) { - d.setSeconds(0); - } - if (step >= timeUnitSize.hour) { - d.setMinutes(0); - } - if (step >= timeUnitSize.day) { - d.setHours(0); - } - if (step >= timeUnitSize.day * 4) { - d.setDate(1); - } - if (step >= timeUnitSize.month * 2) { - d.setMonth(floorInBase(d.getMonth(), 3)); - } - if (step >= timeUnitSize.quarter * 2) { - d.setMonth(floorInBase(d.getMonth(), 6)); - } - if (step >= timeUnitSize.year) { - d.setMonth(0); - } - - var carry = 0; - var v = Number.NaN; - var prev; - - do { - - prev = v; - v = d.getTime(); - ticks.push(v); - - if (unit == "month" || unit == "quarter") { - if (tickSize < 1) { - - // a bit complicated - we'll divide the - // month/quarter up but we need to take - // care of fractions so we don't end up in - // the middle of a day - - d.setDate(1); - var start = d.getTime(); - d.setMonth(d.getMonth() + - (unit == "quarter" ? 3 : 1)); - var end = d.getTime(); - d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize); - carry = d.getHours(); - d.setHours(0); - } else { - d.setMonth(d.getMonth() + - tickSize * (unit == "quarter" ? 3 : 1)); - } - } else if (unit == "year") { - d.setFullYear(d.getFullYear() + tickSize); - } else { - d.setTime(v + step); - } - } while (v < axis.max && v != prev); - - return ticks; - }; - - axis.tickFormatter = function (v, axis) { - - var d = dateGenerator(v, axis.options); - - // first check global format - - if (opts.timeformat != null) { - return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames); - } - - // possibly use quarters if quarters are mentioned in - // any of these places - - var useQuarters = (axis.options.tickSize && - axis.options.tickSize[1] == "quarter") || - (axis.options.minTickSize && - axis.options.minTickSize[1] == "quarter"); - - var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]]; - var span = axis.max - axis.min; - var suffix = (opts.twelveHourClock) ? " %p" : ""; - var hourCode = (opts.twelveHourClock) ? "%I" : "%H"; - var fmt; - - if (t < timeUnitSize.minute) { - fmt = hourCode + ":%M:%S" + suffix; - } else if (t < timeUnitSize.day) { - if (span < 2 * timeUnitSize.day) { - fmt = hourCode + ":%M" + suffix; - } else { - fmt = "%b %d " + hourCode + ":%M" + suffix; - } - } else if (t < timeUnitSize.month) { - fmt = "%b %d"; - } else if ((useQuarters && t < timeUnitSize.quarter) || - (!useQuarters && t < timeUnitSize.year)) { - if (span < timeUnitSize.year) { - fmt = "%b"; - } else { - fmt = "%b %Y"; - } - } else if (useQuarters && t < timeUnitSize.year) { - if (span < timeUnitSize.year) { - fmt = "Q%q"; - } else { - fmt = "Q%q %Y"; - } - } else { - fmt = "%Y"; - } - - var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames); - - return rt; - }; - } - }); - }); - } - - $.plot.plugins.push({ - init: init, - options: options, - name: 'time', - version: '1.0' - }); - - // Time-axis support used to be in Flot core, which exposed the - // formatDate function on the plot object. Various plugins depend - // on the function, so we need to re-expose it here. - - $.plot.formatDate = formatDate; - $.plot.dateGenerator = dateGenerator; - -})(jQuery); diff --git a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js index 1209a105af805..664751bbc0ec0 100644 --- a/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js +++ b/src/plugins/vis_type_timeseries/public/application/visualizations/views/timeseries/index.js @@ -70,19 +70,20 @@ export const TimeSeries = ({ annotations, }) => { const chartRef = useRef(); - const updateCursor = (_, cursor) => { - if (chartRef.current) { - chartRef.current.dispatchExternalPointerEvent(cursor); - } - }; useEffect(() => { + const updateCursor = (_, cursor) => { + if (chartRef.current) { + chartRef.current.dispatchExternalPointerEvent(cursor); + } + }; + eventBus.on(ACTIVE_CURSOR, updateCursor); return () => { eventBus.off(ACTIVE_CURSOR, undefined, updateCursor); }; - }, []); // eslint-disable-line + }, []); const tooltipFormatter = decorateFormatter(xAxisFormatter); const uiSettings = getUISettings(); @@ -139,6 +140,7 @@ export const TimeSeries = ({ type: tooltipMode === 'show_focused' ? TooltipType.Follow : TooltipType.VerticalCursor, headerFormatter: tooltipFormatter, }} + externalPointerEvents={{ tooltip: { visible: false } }} /> {annotations.map(({ id, data, icon, color }) => { diff --git a/src/plugins/visualizations/server/saved_objects/visualization.ts b/src/plugins/visualizations/server/saved_objects/visualization.ts index ad7618a8640ba..5261b2cac7dcf 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization.ts @@ -45,13 +45,13 @@ export const visualizationSavedObjectType: SavedObjectsType = { properties: { description: { type: 'text' }, kibanaSavedObjectMeta: { - properties: { searchSourceJSON: { type: 'text', index: false, doc_values: false } }, + properties: { searchSourceJSON: { type: 'text', index: false } }, }, savedSearchRefName: { type: 'keyword', index: false, doc_values: false }, title: { type: 'text' }, - uiStateJSON: { type: 'text', index: false, doc_values: false }, + uiStateJSON: { type: 'text', index: false }, version: { type: 'integer' }, - visState: { type: 'text', index: false, doc_values: false }, + visState: { type: 'text', index: false }, }, }, migrations: visualizationSavedObjectTypeMigrations, diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 38c376e01b5e4..ef7d8ea189024 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -49,7 +49,7 @@ import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { SavedObjectsStart } from '../../saved_objects/public'; import { EmbeddableStart } from '../../embeddable/public'; import { DashboardStart } from '../../dashboard/public'; -import { UiActionsStart, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public'; +import { UiActionsSetup, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public'; import { setUISettings, setApplication, @@ -69,7 +69,6 @@ export interface VisualizePluginStartDependencies { urlForwarding: UrlForwardingStart; savedObjects: SavedObjectsStart; dashboard: DashboardStart; - uiActions: UiActionsStart; } export interface VisualizePluginSetupDependencies { @@ -77,6 +76,7 @@ export interface VisualizePluginSetupDependencies { urlForwarding: UrlForwardingSetup; data: DataPublicPluginSetup; share?: SharePluginSetup; + uiActions: UiActionsSetup; } export class VisualizePlugin @@ -90,7 +90,7 @@ export class VisualizePlugin public async setup( core: CoreSetup, - { home, urlForwarding, data, share }: VisualizePluginSetupDependencies + { home, urlForwarding, data, share, uiActions }: VisualizePluginSetupDependencies ) { const { appMounted, @@ -135,11 +135,12 @@ export class VisualizePlugin ); } setUISettings(core.uiSettings); + uiActions.addTriggerAction(VISUALIZE_FIELD_TRIGGER, visualizeFieldAction); core.application.register({ id: 'visualize', title: 'Visualize', - order: -1002, + order: 8000, euiIconType: 'logoKibana', defaultPath: '#/', category: DEFAULT_APP_CATEGORIES.kibana, @@ -236,7 +237,6 @@ export class VisualizePlugin if (plugins.share) { setShareService(plugins.share); } - plugins.uiActions.addTriggerAction(VISUALIZE_FIELD_TRIGGER, visualizeFieldAction); } stop() { diff --git a/tasks/config/run.js b/tasks/config/run.js index fac5b6085aa2e..b3c0659b8001d 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -175,7 +175,11 @@ module.exports = function () { '--config', 'test/server_integration/http/ssl_redirect/config.js', '--config', - 'test/server_integration/http/cache/config.js', + 'test/server_integration/http/platform/config.ts', + '--config', + 'test/server_integration/http/ssl_with_p12/config.js', + '--config', + 'test/server_integration/http/ssl_with_p12_intermediate/config.js', '--bail', '--debug', '--kibana-install-dir', diff --git a/test/accessibility/apps/kibana_overview.ts b/test/accessibility/apps/kibana_overview.ts new file mode 100644 index 0000000000000..1f703c64bbde3 --- /dev/null +++ b/test/accessibility/apps/kibana_overview.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'home']); + const a11y = getService('a11y'); + + describe('Kibana overview', () => { + const esArchiver = getService('esArchiver'); + + before(async () => { + await esArchiver.load('empty_kibana'); + await PageObjects.common.navigateToApp('kibanaOverview'); + }); + + after(async () => { + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.home.removeSampleDataSet('flights'); + await esArchiver.unload('empty_kibana'); + }); + + it('Getting started view', async () => { + await a11y.testAppSnapshot(); + }); + + it('Overview view', async () => { + await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { + useActualUrl: true, + }); + await PageObjects.home.addSampleDataSet('flights'); + await PageObjects.common.navigateToApp('kibanaOverview'); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/test/accessibility/config.ts b/test/accessibility/config.ts index 9068a7e06defc..9730eae1e1360 100644 --- a/test/accessibility/config.ts +++ b/test/accessibility/config.ts @@ -36,6 +36,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/console'), require.resolve('./apps/home'), require.resolve('./apps/filter_panel'), + require.resolve('./apps/kibana_overview'), ], pageObjects, services, diff --git a/test/accessibility/services/a11y/analyze_with_axe.js b/test/accessibility/services/a11y/analyze_with_axe.js index 6d4aa1491278e..aaecdbeb0e95d 100644 --- a/test/accessibility/services/a11y/analyze_with_axe.js +++ b/test/accessibility/services/a11y/analyze_with_axe.js @@ -33,6 +33,14 @@ export function analyzeWithAxe(context, options, callback) { id: 'aria-required-children', selector: '[data-skip-axe="aria-required-children"] > *', }, + { + id: 'label', + selector: '[data-test-subj="comboBoxSearchInput"] *', + }, + { + id: 'aria-roles', + selector: '[data-test-subj="comboBoxSearchInput"] *', + }, ], }); return window.axe.run(context, options); diff --git a/test/common/config.js b/test/common/config.js index dbbd75d1f9577..9d6d531ae4b37 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -48,6 +48,8 @@ export default function () { `--elasticsearch.username=${kibanaServerTestUser.username}`, `--elasticsearch.password=${kibanaServerTestUser.password}`, `--home.disableWelcomeScreen=true`, + // Needed for async search functional tests to introduce a delay + `--data.search.aggs.shardDelay.enabled=true`, `--security.showInsecureClusterWarning=false`, '--telemetry.banner=false', '--telemetry.optIn=false', diff --git a/test/common/services/security/role_mappings.ts b/test/common/services/security/role_mappings.ts index 7951d4b5b47b2..267294991f30e 100644 --- a/test/common/services/security/role_mappings.ts +++ b/test/common/services/security/role_mappings.ts @@ -23,10 +23,24 @@ import { KbnClient, ToolingLog } from '@kbn/dev-utils'; export class RoleMappings { constructor(private log: ToolingLog, private kbnClient: KbnClient) {} + public async getAll() { + this.log.debug(`Getting role mappings`); + const { data, status, statusText } = await this.kbnClient.request>({ + path: `/internal/security/role_mapping`, + method: 'GET', + }); + if (status !== 200) { + throw new Error( + `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}` + ); + } + return data; + } + public async create(name: string, roleMapping: Record) { this.log.debug(`creating role mapping ${name}`); const { data, status, statusText } = await this.kbnClient.request({ - path: `/internal/security/role_mapping/${name}`, + path: `/internal/security/role_mapping/${encodeURIComponent(name)}`, method: 'POST', body: roleMapping, }); @@ -41,7 +55,7 @@ export class RoleMappings { public async delete(name: string) { this.log.debug(`deleting role mapping ${name}`); const { data, status, statusText } = await this.kbnClient.request({ - path: `/internal/security/role_mapping/${name}`, + path: `/internal/security/role_mapping/${encodeURIComponent(name)}`, method: 'DELETE', }); if (status !== 200 && status !== 404) { diff --git a/test/functional/apps/dashboard/embeddable_rendering.js b/test/functional/apps/dashboard/embeddable_rendering.js index 73c36c7562e8b..7b45962dfcde5 100644 --- a/test/functional/apps/dashboard/embeddable_rendering.js +++ b/test/functional/apps/dashboard/embeddable_rendering.js @@ -52,7 +52,7 @@ export default function ({ getService, getPageObjects }) { await dashboardExpect.seriesElementCount(19); const tsvbGuageExists = await find.existsByCssSelector('.tvbVisHalfGauge'); expect(tsvbGuageExists).to.be(true); - await dashboardExpect.timelionLegendCount(0); + await dashboardExpect.timelionLegendCount(5); await dashboardExpect.markdownWithValuesExists(["I'm a markdown!"]); await dashboardExpect.vegaTextsExist(['5,000']); await dashboardExpect.goalAndGuageLabelsExist(['62.925%', '55.625%', '11.915 GB']); diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 20f3394af2d6a..413a39cab21f1 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -26,6 +26,7 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const queryBar = getService('queryBar'); + const inspector = getService('inspector'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -317,5 +318,37 @@ export default function ({ getService, getPageObjects }) { expect(currentUrlWithoutScore).not.to.contain('_score'); }); }); + + describe('refresh interval', function () { + it('should refetch when autofresh is enabled', async () => { + const intervalS = 5; + await PageObjects.timePicker.startAutoRefresh(intervalS); + + // check inspector panel request stats for timestamp + await inspector.open(); + + const getRequestTimestamp = async () => { + const requestStats = await inspector.getTableData(); + const requestTimestamp = requestStats.filter((r) => + r[0].includes('Request timestamp') + )[0][1]; + return requestTimestamp; + }; + + const requestTimestampBefore = await getRequestTimestamp(); + await retry.waitFor('refetch because of refresh interval', async () => { + const requestTimestampAfter = await getRequestTimestamp(); + log.debug( + `Timestamp before: ${requestTimestampBefore}, Timestamp after: ${requestTimestampAfter}` + ); + return requestTimestampBefore !== requestTimestampAfter; + }); + }); + + after(async () => { + await inspector.close(); + await PageObjects.timePicker.pauseAutoRefresh(); + }); + }); }); } diff --git a/test/functional/apps/discover/_field_data.js b/test/functional/apps/discover/_field_data.js index f0472fb5a3da5..118234d54626c 100644 --- a/test/functional/apps/discover/_field_data.js +++ b/test/functional/apps/discover/_field_data.js @@ -27,20 +27,18 @@ export default function ({ getService, getPageObjects }) { const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); - describe('discover tab', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/78689 + describe.skip('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('discover'); - // delete .kibana index and update configDoc await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', }); - + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); }); - describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; diff --git a/test/functional/apps/discover/_field_visualize.ts b/test/functional/apps/discover/_field_visualize.ts index c95211e98cdba..1a1631b9db48b 100644 --- a/test/functional/apps/discover/_field_visualize.ts +++ b/test/functional/apps/discover/_field_visualize.ts @@ -32,7 +32,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { defaultIndex: 'logstash-*', }; - describe('discover field visualize button', () => { + describe('discover field visualize button', function () { + // unskipped on cloud as these tests test the navigation + // from Discover to Visualize which happens only on OSS + this.tags(['skipCloud']); before(async function () { log.debug('load kibana index with default index pattern'); await esArchiver.load('discover'); diff --git a/test/functional/apps/home/_add_data.js b/test/functional/apps/home/_add_data.js index 6ba123e579f7a..401a5becceb30 100644 --- a/test/functional/apps/home/_add_data.js +++ b/test/functional/apps/home/_add_data.js @@ -28,7 +28,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.common.navigateToUrl('home', 'tutorial_directory', { useActualUrl: true }); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.try(async () => { - const tutorialExists = await PageObjects.home.doesSynopsisExist('netflow'); + const tutorialExists = await PageObjects.home.doesSynopsisExist('netflowlogs'); expect(tutorialExists).to.be(true); }); }); diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js index 8b11a02099f61..90838ecc1f6fb 100644 --- a/test/functional/apps/management/_create_index_pattern_wizard.js +++ b/test/functional/apps/management/_create_index_pattern_wizard.js @@ -24,6 +24,7 @@ export default function ({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const es = getService('legacyEs'); const PageObjects = getPageObjects(['settings', 'common']); + const security = getService('security'); describe('"Create Index Pattern" wizard', function () { before(async function () { @@ -51,6 +52,9 @@ export default function ({ getService, getPageObjects }) { }); describe('index alias', () => { + before(async function () { + await security.testUser.setRoles(['kibana_admin', 'test_alias1_reader']); + }); it('can be an index pattern', async () => { await es.transport.request({ path: '/blogs/_doc', @@ -77,6 +81,7 @@ export default function ({ getService, getPageObjects }) { path: '/blogs', method: 'DELETE', }); + await security.testUser.restoreDefaults(); }); }); }); diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index 24e4ef4a7fe25..8dfc4d352b133 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -136,15 +136,8 @@ export default function ({ getService, getPageObjects }) { }); it('should request new data when autofresh is enabled', async () => { - // enable autorefresh - const interval = 3; - await PageObjects.timePicker.openQuickSelectTimeMenu(); - await PageObjects.timePicker.inputValue( - 'superDatePickerRefreshIntervalInput', - interval.toString() - ); - await testSubjects.click('superDatePickerToggleRefreshButton'); - await PageObjects.timePicker.closeQuickSelectTimeMenu(); + const intervalS = 3; + await PageObjects.timePicker.startAutoRefresh(intervalS); // check inspector panel request stats for timestamp await inspector.open(); @@ -155,7 +148,7 @@ export default function ({ getService, getPageObjects }) { )[0][1]; // pause to allow time for autorefresh to fire another request - await PageObjects.common.sleep(interval * 1000 * 1.5); + await PageObjects.common.sleep(intervalS * 1000 * 1.5); // get the latest timestamp from request stats const requestStatsAfter = await inspector.getTableData(); diff --git a/test/functional/config.js b/test/functional/config.js index 6081810d41272..e465937aff0e5 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -288,6 +288,18 @@ export default async function ({ readConfigFile }) { }, kibana: [], }, + + test_alias1_reader: { + elasticsearch: { + cluster: [], + indices: [ + { + names: ['alias1'], + privileges: ['read', 'view_index_metadata'], + }, + ], + }, + }, }, defaultRoles: ['test_logstash_reader', 'kibana_admin'], }, diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index bd60faedb1181..50dfb86526732 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -117,11 +117,12 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo } else { log.debug(`navigateToUrl ${appUrl}`); await browser.get(appUrl, insertTimestamp); - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); } + // accept alert if it pops up + const alert = await browser.getAlert(); + await alert?.accept(); + const currentUrl = shouldLoginIfPrompted ? await this.loginIfPrompted(appUrl, insertTimestamp) : await browser.getCurrentUrl(); diff --git a/test/functional/page_objects/management/saved_objects_page.ts b/test/functional/page_objects/management/saved_objects_page.ts index e165341dbd63d..8e65b6488836c 100644 --- a/test/functional/page_objects/management/saved_objects_page.ts +++ b/test/functional/page_objects/management/saved_objects_page.ts @@ -146,6 +146,20 @@ export function SavedObjectsPageProvider({ getService, getPageObjects }: FtrProv } } + async clickInspectByTitle(title: string) { + const table = keyBy(await this.getElementsInTable(), 'title'); + if (table[title].menuElement) { + await table[title].menuElement?.click(); + // Wait for context menu to render + const menuPanel = await find.byCssSelector('.euiContextMenuPanel'); + const panelButton = await menuPanel.findByTestSubject('savedObjectsTableAction-inspect'); + await panelButton.click(); + } else { + // or the action elements are on the row without the menu + await table[title].copySaveObjectsElement?.click(); + } + } + async clickCheckboxByTitle(title: string) { const table = keyBy(await this.getElementsInTable(), 'title'); // should we check if table size > 0 and log error if not? diff --git a/test/functional/page_objects/time_picker.ts b/test/functional/page_objects/time_picker.ts index 237dc8946ae0e..272fc5a7914be 100644 --- a/test/functional/page_objects/time_picker.ts +++ b/test/functional/page_objects/time_picker.ts @@ -35,12 +35,18 @@ export type CommonlyUsed = export function TimePickerProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); - const retry = getService('retry'); const find = getService('find'); const browser = getService('browser'); const testSubjects = getService('testSubjects'); const { header } = getPageObjects(['header']); const kibanaServer = getService('kibanaServer'); + const MenuToggle = getService('MenuToggle'); + + const quickSelectTimeMenuToggle = new MenuToggle({ + name: 'QuickSelectTime Menu', + menuTestSubject: 'superDatePickerQuickMenu', + toggleButtonTestSubject: 'superDatePickerToggleQuickMenuButton', + }); class TimePicker { defaultStartTime = 'Sep 19, 2015 @ 06:31:44.000'; @@ -158,34 +164,8 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo return await find.existsByCssSelector('.euiDatePickerRange--readOnly'); } - public async isQuickSelectMenuOpen() { - return await testSubjects.exists('superDatePickerQuickMenu'); - } - - public async openQuickSelectTimeMenu() { - log.debug('openQuickSelectTimeMenu'); - const isMenuOpen = await this.isQuickSelectMenuOpen(); - if (!isMenuOpen) { - log.debug('opening quick select menu'); - await retry.try(async () => { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - }); - } - } - - public async closeQuickSelectTimeMenu() { - log.debug('closeQuickSelectTimeMenu'); - const isMenuOpen = await this.isQuickSelectMenuOpen(); - if (isMenuOpen) { - log.debug('closing quick select menu'); - await retry.try(async () => { - await testSubjects.click('superDatePickerToggleQuickMenuButton'); - }); - } - } - public async getRefreshConfig(keepQuickSelectOpen = false) { - await this.openQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.open(); const interval = await testSubjects.getAttribute( 'superDatePickerRefreshIntervalInput', 'value' @@ -207,7 +187,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo 'superDatePickerToggleRefreshButton' ); if (!keepQuickSelectOpen) { - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } return { @@ -269,16 +249,27 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo return moment.duration(endMoment.diff(startMoment)).asHours(); } + public async startAutoRefresh(intervalS = 3) { + await quickSelectTimeMenuToggle.open(); + await this.inputValue('superDatePickerRefreshIntervalInput', intervalS.toString()); + const refreshConfig = await this.getRefreshConfig(true); + if (refreshConfig.isPaused) { + log.debug('start auto refresh'); + await testSubjects.click('superDatePickerToggleRefreshButton'); + } + await quickSelectTimeMenuToggle.close(); + } + public async pauseAutoRefresh() { log.debug('pauseAutoRefresh'); const refreshConfig = await this.getRefreshConfig(true); + if (!refreshConfig.isPaused) { log.debug('pause auto refresh'); await testSubjects.click('superDatePickerToggleRefreshButton'); - await this.closeQuickSelectTimeMenu(); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async resumeAutoRefresh() { @@ -289,7 +280,7 @@ export function TimePickerProvider({ getService, getPageObjects }: FtrProviderCo await testSubjects.click('superDatePickerToggleRefreshButton'); } - await this.closeQuickSelectTimeMenu(); + await quickSelectTimeMenuToggle.close(); } public async setHistoricalDataRange() { diff --git a/test/functional/services/common/failure_debugging.ts b/test/functional/services/common/failure_debugging.ts index aa67c455e0100..8b0e095b71ff8 100644 --- a/test/functional/services/common/failure_debugging.ts +++ b/test/functional/services/common/failure_debugging.ts @@ -38,7 +38,7 @@ export async function FailureDebuggingProvider({ getService }: FtrProviderContex const log = getService('log'); const browser = getService('browser'); - if (process.env.CI !== 'true') { + if (process.env.CI !== 'true' && !process.env.stack_functional_integration) { await del(config.get('failureDebugging.htmlDirectory')); } diff --git a/test/functional/services/common/screenshots.ts b/test/functional/services/common/screenshots.ts index daa55240f3eb7..5bce0d4cf6c87 100644 --- a/test/functional/services/common/screenshots.ts +++ b/test/functional/services/common/screenshots.ts @@ -40,7 +40,7 @@ export async function ScreenshotsProvider({ getService }: FtrProviderContext) { const FAILURE_DIRECTORY = resolve(config.get('screenshots.directory'), 'failure'); const BASELINE_DIRECTORY = resolve(config.get('screenshots.directory'), 'baseline'); - if (process.env.CI !== 'true') { + if (process.env.CI !== 'true' && !process.env.stack_functional_integration) { await del([SESSION_DIRECTORY, FAILURE_DIRECTORY]); } diff --git a/test/functional/services/common/snapshots.ts b/test/functional/services/common/snapshots.ts index 2e0b360e594e5..03eadff82e31f 100644 --- a/test/functional/services/common/snapshots.ts +++ b/test/functional/services/common/snapshots.ts @@ -35,7 +35,7 @@ export async function SnapshotsProvider({ getService }: FtrProviderContext) { const SESSION_DIRECTORY = resolve(config.get('snapshots.directory'), 'session'); const BASELINE_DIRECTORY = resolve(config.get('snapshots.directory'), 'baseline'); - if (process.env.CI !== 'true') { + if (process.env.CI !== 'true' && !process.env.stack_functional_integration) { await del([SESSION_DIRECTORY]); } diff --git a/test/functional/services/common/test_subjects.ts b/test/functional/services/common/test_subjects.ts index 56c5e6acd5ad1..e464eaf943627 100644 --- a/test/functional/services/common/test_subjects.ts +++ b/test/functional/services/common/test_subjects.ts @@ -167,10 +167,30 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { }); } - public async getAttribute(selector: string, attribute: string): Promise { - return await retry.try(async () => { - log.debug(`TestSubjects.getAttribute(${selector}, ${attribute})`); - const element = await this.find(selector); + public async getAttribute( + selector: string, + attribute: string, + options?: + | number + | { + findTimeout?: number; + tryTimeout?: number; + } + ): Promise { + const findTimeout = + (typeof options === 'number' ? options : options?.findTimeout) ?? + config.get('timeouts.find'); + + const tryTimeout = + (typeof options !== 'number' ? options?.tryTimeout : undefined) ?? + config.get('timeouts.try'); + + log.debug( + `TestSubjects.getAttribute(${selector}, ${attribute}, tryTimeout=${tryTimeout}, findTimeout=${findTimeout})` + ); + + return await retry.tryForTime(tryTimeout, async () => { + const element = await this.find(selector, findTimeout); return await element.getAttribute(attribute); }); } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index 057ae0bd13b6e..2c71fd8ef8f55 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -57,6 +57,7 @@ import { import { ListingTableProvider } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; import { KibanaSupertestProvider } from './supertest'; +import { MenuToggleProvider } from './menu_toggle'; export const services = { ...commonServiceProviders, @@ -93,4 +94,5 @@ export const services = { elasticChart: ElasticChartProvider, supertest: KibanaSupertestProvider, managementMenu: ManagementMenuProvider, + MenuToggle: MenuToggleProvider, }; diff --git a/test/functional/services/menu_toggle.ts b/test/functional/services/menu_toggle.ts new file mode 100644 index 0000000000000..073aa846f47a8 --- /dev/null +++ b/test/functional/services/menu_toggle.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 { FtrProviderContext } from '../ftr_provider_context'; + +export function MenuToggleProvider({ getService }: FtrProviderContext) { + const log = getService('log'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + + interface Options { + name: string; + menuTestSubject: string; + toggleButtonTestSubject: string; + } + + return class MenuToggle { + private readonly name: string; + private readonly menuTestSubject: string; + private readonly toggleButtonTestSubject: string; + + constructor(options: Options) { + this.name = options.name; + this.menuTestSubject = options.menuTestSubject; + this.toggleButtonTestSubject = options.toggleButtonTestSubject; + } + + async open() { + await this.setState(true); + } + + async close() { + await this.setState(false); + } + + private async setState(expectedState: boolean) { + log.debug( + `setting menu open state [name=${this.name}] [state=${expectedState ? 'open' : 'closed'}]` + ); + + await retry.try(async () => { + // if the menu is clearly in the expected state already, bail out quickly if so + const isOpen = await testSubjects.exists(this.menuTestSubject, { timeout: 1000 }); + if (isOpen === expectedState) { + return; + } + + // toggle the view state by clicking the button + await testSubjects.click(this.toggleButtonTestSubject); + + if (expectedState === true) { + // wait for up to 10 seconds for the menu to show up, otherwise fail and retry + await testSubjects.existOrFail(this.menuTestSubject, { timeout: 10000 }); + } else { + // wait for the form to hide, otherwise fail and retry + await testSubjects.waitForDeleted(this.menuTestSubject); + } + }); + } + }; +} 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 0d6d0286c5a8f..f12451f7b8926 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.3.0", + "@elastic/eui": "29.3.1", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "react-dom": "^16.12.0", diff --git a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json index 8efd2ee432415..cb3302bba2809 100644 --- a/test/plugin_functional/plugins/kbn_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_sample_panel_action/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.3.0", + "@elastic/eui": "29.3.1", "react": "^16.12.0", "typescript": "4.0.2" } 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 4405063e54c06..d2545c621df0f 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -12,7 +12,7 @@ "build": "rm -rf './target' && tsc" }, "devDependencies": { - "@elastic/eui": "29.3.0", + "@elastic/eui": "29.3.1", "@kbn/plugin-helpers": "1.0.0", "react": "^16.12.0", "typescript": "4.0.2" diff --git a/test/scripts/checks/bundle_limits.sh b/test/scripts/checks/bundle_limits.sh new file mode 100755 index 0000000000000..10d9d9343fda4 --- /dev/null +++ b/test/scripts/checks/bundle_limits.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +source src/dev/ci_setup/setup_env.sh + +node scripts/build_kibana_platform_plugins --validate-limits diff --git a/test/scripts/jenkins_test_setup_oss.sh b/test/scripts/jenkins_test_setup_oss.sh index b7eac33f35176..53626ce89462a 100755 --- a/test/scripts/jenkins_test_setup_oss.sh +++ b/test/scripts/jenkins_test_setup_oss.sh @@ -3,11 +3,7 @@ source test/scripts/jenkins_test_setup.sh if [[ -z "$CODE_COVERAGE" ]]; then - - destDir="build/kibana-build-oss" - if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then - destDir="${destDir}-${CI_PARALLEL_PROCESS_NUMBER}" - fi + destDir="$WORKSPACE/kibana-build-oss-${TASK_QUEUE_PROCESS_ID:-$CI_PARALLEL_PROCESS_NUMBER}" if [[ ! -d $destDir ]]; then mkdir -p $destDir diff --git a/test/scripts/jenkins_test_setup_xpack.sh b/test/scripts/jenkins_test_setup_xpack.sh index 74a3de77e3a76..b9227fd8ff416 100755 --- a/test/scripts/jenkins_test_setup_xpack.sh +++ b/test/scripts/jenkins_test_setup_xpack.sh @@ -3,11 +3,7 @@ source test/scripts/jenkins_test_setup.sh if [[ -z "$CODE_COVERAGE" ]]; then - - destDir="build/kibana-build-xpack" - if [[ ! "$TASK_QUEUE_PROCESS_ID" ]]; then - destDir="${destDir}-${CI_PARALLEL_PROCESS_NUMBER}" - fi + destDir="$WORKSPACE/kibana-build-xpack-${TASK_QUEUE_PROCESS_ID:-$CI_PARALLEL_PROCESS_NUMBER}" if [[ ! -d $destDir ]]; then mkdir -p $destDir diff --git a/test/scripts/jenkins_xpack_build_plugins.sh b/test/scripts/jenkins_xpack_build_plugins.sh index 3fd3d02de1304..289e64f66c89b 100755 --- a/test/scripts/jenkins_xpack_build_plugins.sh +++ b/test/scripts/jenkins_xpack_build_plugins.sh @@ -10,5 +10,6 @@ node scripts/build_kibana_platform_plugins \ --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ + --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ --workers 12 \ --verbose diff --git a/test/server_integration/http/cache/index.js b/test/server_integration/http/platform/cache.ts similarity index 92% rename from test/server_integration/http/cache/index.js rename to test/server_integration/http/platform/cache.ts index 5299ce361327e..e39990ca001be 100644 --- a/test/server_integration/http/cache/index.js +++ b/test/server_integration/http/platform/cache.ts @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ - +import { FtrProviderContext } from '../../services/types'; // eslint-disable-next-line import/no-default-export -export default function ({ getService }) { +export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('kibana server cache-control', () => { diff --git a/test/server_integration/http/cache/config.js b/test/server_integration/http/platform/config.ts similarity index 76% rename from test/server_integration/http/cache/config.js rename to test/server_integration/http/platform/config.ts index de20bc6fc1f14..00577e092a26a 100644 --- a/test/server_integration/http/cache/config.js +++ b/test/server_integration/http/platform/config.ts @@ -16,16 +16,18 @@ * specific language governing permissions and limitations * under the License. */ +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; -export default async function ({ readConfigFile }) { +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { const httpConfig = await readConfigFile(require.resolve('../../config')); return { - testFiles: [require.resolve('./')], + testFiles: [require.resolve('./cache'), require.resolve('./headers')], services: httpConfig.get('services'), servers: httpConfig.get('servers'), junit: { - reportName: 'Http Cache-Control Integration Tests', + reportName: 'Kibana Platform Http Integration Tests', }, esTestCluster: httpConfig.get('esTestCluster'), kbnTestServer: httpConfig.get('kbnTestServer'), diff --git a/test/server_integration/http/platform/headers.ts b/test/server_integration/http/platform/headers.ts new file mode 100644 index 0000000000000..260bc37bd1328 --- /dev/null +++ b/test/server_integration/http/platform/headers.ts @@ -0,0 +1,82 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 Http from 'http'; +import Url from 'url'; +import { FtrProviderContext } from '../../services/types'; + +// @ts-ignore +import getUrl from '../../../../src/test_utils/get_url'; + +const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +const oneSec = 1_000; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService }: FtrProviderContext) { + const config = getService('config'); + + describe('headers timeout ', () => { + it('issue-73849', async () => { + const agent = new Http.Agent({ + keepAlive: true, + }); + const { protocol, hostname, port } = Url.parse(getUrl.baseUrl(config.get('servers.kibana'))); + + function performRequest() { + return new Promise((resolve, reject) => { + const req = Http.request( + { + protocol, + hostname, + port, + path: '/', + method: 'GET', + agent, + }, + function (res) { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => resolve(data)); + } + ); + + req.on('socket', (socket) => { + socket.write('GET / HTTP/1.1\r\n'); + setTimeout(() => { + socket.write('Host: localhost\r\n'); + }, oneSec); + setTimeout(() => { + // headersTimeout doesn't seem to fire if request headers are sent in one packet. + socket.write('\r\n'); + req.end(); + }, 2 * oneSec); + }); + + req.on('error', reject); + }); + } + + await performRequest(); + const defaultHeadersTimeout = 40 * oneSec; + await delay(defaultHeadersTimeout + oneSec); + await performRequest(); + }); + }); +} diff --git a/src/plugins/timelion/public/flot/index.js b/test/server_integration/services/types.d.ts similarity index 67% rename from src/plugins/timelion/public/flot/index.js rename to test/server_integration/services/types.d.ts index a066fd3ab8607..c79c8db57d7df 100644 --- a/src/plugins/timelion/public/flot/index.js +++ b/test/server_integration/services/types.d.ts @@ -17,10 +17,11 @@ * under the License. */ -import './jquery.flot'; -import './jquery.flot.time'; -import './jquery.flot.symbol'; -import './jquery.flot.crosshair'; -import './jquery.flot.selection'; -import './jquery.flot.stack'; -import './jquery.flot.axislabels'; +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; +import { services as kibanaCommonServices } from '../../common/services'; +import { services as kibanaApiIntegrationServices } from '../../api_integration/services'; + +export type FtrProviderContext = GenericFtrProviderContext< + typeof kibanaCommonServices & { supertest: typeof kibanaApiIntegrationServices.supertest }, + {} +>; diff --git a/vars/getCheckoutInfo.groovy b/vars/getCheckoutInfo.groovy index 32a7b054bfd15..f9d797f8127c7 100644 --- a/vars/getCheckoutInfo.groovy +++ b/vars/getCheckoutInfo.groovy @@ -2,6 +2,7 @@ def call(branchOverride) { def repoInfo = [ branch: branchOverride ?: env.ghprbSourceBranch, targetBranch: env.ghprbTargetBranch, + targetsTrackedBranch: true ] if (repoInfo.branch == null) { @@ -35,6 +36,10 @@ def call(branchOverride) { label: "determining merge point with '${repoInfo.targetBranch}' at origin", returnStdout: true ).trim() + + def pkgJson = readFile("package.json") + def releaseBranch = toJSON(pkgJson).branch + repoInfo.targetsTrackedBranch = releaseBranch == repoInfo.targetBranch } print "repoInfo: ${repoInfo}" diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index ec3dbd919fed6..546a6785ac2f4 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -87,15 +87,6 @@ def getLatestBuildInfo(comment) { return comment ? getBuildInfoFromComment(comment.body) : null } -def createBuildInfo() { - return [ - status: buildUtils.getBuildStatus(), - url: env.BUILD_URL, - number: env.BUILD_NUMBER, - commit: getCommitHash() - ] -} - def getHistoryText(builds) { if (!builds || builds.size() < 1) { return "" @@ -155,6 +146,16 @@ def getTestFailuresMessage() { return messages.join("\n") } +def getBuildStatusIncludingMetrics() { + def status = buildUtils.getBuildStatus() + + if (status == 'SUCCESS' && shouldCheckCiMetricSuccess() && !ciStats.getMetricsSuccess()) { + return 'FAILURE' + } + + return status +} + def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { def info = previousCommentInfo ?: [:] info.builds = previousCommentInfo.builds ?: [] @@ -163,7 +164,10 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { info.builds = info.builds.findAll { it.number != env.BUILD_NUMBER } def messages = [] - def status = buildUtils.getBuildStatus() + + def status = isFinal + ? getBuildStatusIncludingMetrics() + : buildUtils.getBuildStatus() if (!isFinal) { def failuresPart = status != 'SUCCESS' ? ', with failures' : '' @@ -228,7 +232,12 @@ def getNextCommentMessage(previousCommentInfo = [:], isFinal = false) { messages << "To update your PR or re-run it, just comment with:\n`@elasticmachine merge upstream`" - info.builds << createBuildInfo() + info.builds << [ + status: status, + url: env.BUILD_URL, + number: env.BUILD_NUMBER, + commit: getCommitHash() + ] messages << """