diff --git a/.buildkite/scripts/build_kibana.sh b/.buildkite/scripts/build_kibana.sh index 19dbbcb025fb971..7a9878b5bcd13e1 100755 --- a/.buildkite/scripts/build_kibana.sh +++ b/.buildkite/scripts/build_kibana.sh @@ -5,7 +5,7 @@ set -euo pipefail export KBN_NP_PLUGINS_BUILT=true echo "--- Build Kibana Distribution" -node scripts/build --debug --no-oss +node scripts/build --debug echo "--- Archive Kibana Distribution" linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" diff --git a/.buildkite/scripts/common/env.sh b/.buildkite/scripts/common/env.sh index ff09126f60b089c..901fa9f50a61f79 100755 --- a/.buildkite/scripts/common/env.sh +++ b/.buildkite/scripts/common/env.sh @@ -67,6 +67,6 @@ export TEST_KIBANA_HOST=localhost export TEST_KIBANA_PORT=6101 export TEST_KIBANA_URL="http://elastic:changeme@localhost:6101" export TEST_ES_URL="http://elastic:changeme@localhost:6102" -export TEST_ES_TRANSPORT_PORT=6103 +export TEST_ES_TRANSPORT_PORT=6301-6309 export TEST_CORS_SERVER_PORT=6106 export ALERTING_PROXY_PORT=6105 diff --git a/.buildkite/scripts/post_build_kibana.sh b/.buildkite/scripts/post_build_kibana.sh index ad22a224f7c5544..06864b204bc3ceb 100755 --- a/.buildkite/scripts/post_build_kibana.sh +++ b/.buildkite/scripts/post_build_kibana.sh @@ -6,7 +6,7 @@ if [[ ! "${DISABLE_CI_STATS_SHIPPING:-}" ]]; then echo "--- Ship Kibana Distribution Metrics to CI Stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json + --metrics build/kibana/node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json fi echo "--- Upload Build Artifacts" diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 9445d0226572503..e6fdda9f1cb236d 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -28,6 +28,11 @@ jobs: - name: Install Actions run: npm install --production --prefix ./actions + - name: Fix Version Label Gaps + uses: ./actions/fix-version-gaps + with: + github_token: ${{secrets.KIBANAMACHINE_TOKEN}} + - name: Run Backport uses: ./actions/backport with: diff --git a/.github/workflows/fix-version-gaps.yml b/.github/workflows/fix-version-gaps.yml new file mode 100644 index 000000000000000..ea832ff22a68aab --- /dev/null +++ b/.github/workflows/fix-version-gaps.yml @@ -0,0 +1,35 @@ +on: + pull_request: + branches: + - master + types: + - closed + +jobs: + gaps: + name: Fix Version Label Gaps + # This fix also runs as part of the backport action (because backport depends on the labels) + # So we only need to trigger it for merged PRs that also won't be auto-backported + if: | + github.event.pull_request.merged == true + && !contains(github.event.pull_request.labels.*.name, 'auto-backport') + && !( + (github.event.action == 'labeled' && github.event.label.name == 'auto-backport') + || (github.event.action == 'closed') + ) + runs-on: ubuntu-latest + steps: + - name: Checkout Actions + uses: actions/checkout@v2 + with: + repository: 'elastic/kibana-github-actions' + ref: main + path: ./actions + + - name: Install Actions + run: npm install --production --prefix ./actions + + - name: Run Fix Gaps + uses: ./actions/fix-version-gaps + with: + github_token: ${{secrets.KIBANAMACHINE_TOKEN}} diff --git a/.i18nrc.json b/.i18nrc.json index bdfe444bb99b56f..2709d5ad7a6716d 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -18,6 +18,8 @@ "devTools": "src/plugins/dev_tools", "expressions": "src/plugins/expressions", "expressionError": "src/plugins/expression_error", + "expressionImage": "src/plugins/expression_image", + "expressionMetric": "src/plugins/expression_metric", "expressionRepeatImage": "src/plugins/expression_repeat_image", "expressionRevealImage": "src/plugins/expression_reveal_image", "expressionShape": "src/plugins/expression_shape", diff --git a/docs/CHANGELOG.asciidoc b/docs/CHANGELOG.asciidoc index d0c19f862836140..9be0170b67b7414 100644 --- a/docs/CHANGELOG.asciidoc +++ b/docs/CHANGELOG.asciidoc @@ -1,5 +1,5 @@ [[release-notes]] -= Release Notes += Release notes [partintro] -- @@ -19,3 +19,242 @@ This section summarizes the changes in each release. == {kib} 8.0.0-alpha1 coming[8.0.0] + +The following changes are released for the first time in {kib} 8.0.0-alpha1. Review the changes, then use the <> to complete the upgrade. + +[float] +[[breaking-changes-8.0.0]] +=== Breaking changes + +Breaking changes can prevent your application from optimal operation and performance. Review the breaking changes, then mitigrate the impact to your appilication. + +// tag::notable-breaking-changes[] + +[float] +[[enterprise-search-change]] +==== Enterprise Search changes + +[discrete] +[[breaking-106307]] +.Required security plugin in 8.0 +[%collapsible] +==== +*Details* + +Enterprise Search now requires that you enable X-Pack Security. For more information, refer to {kibana-pull}106307[#106307] + +*Impact* + +Enable X-Pack Security. +==== + +[float] +[[index-pattern-change]] +==== Index pattern changes + +[discrete] +[[breaking-35173]] +.Removed support for time-based interval index patterns +[%collapsible] +==== +*Details* + +Time-based interval index patterns were deprecated in 5.x. In 6.x, you could no longer create time-based interval index patterns, but they continued to function as expected. Support for these index patterns has been removed in 8.0. For more information, refer to {kibana-pull}35173[#35173] + +*Impact* + +You must migrate your time_based index patterns to a wildcard pattern. For example, logstash-*. +==== + +[float] +[[operations-changes]] +==== Operations changes + +[discrete] +[[breaking-93835]] +.Removed platform from archive root directory +[%collapsible] +==== +*Details* + +For the `.tar.gz` and `.zip` archives, `platform` has been removed from the `root` folder name. For more information, refer to {kibana-pull}93835[#93835] + +*Impact* + +The `root` folder name now appears as `kibana-8.0.0-SNAPSHOT-linux-aarch64.tar.gz -> kibana-8.0.0-SNAPSHOT`. +==== + +[discrete] +[[breaking-90511]] +.Removed default support for TLS v1.0 and v1.1 +[%collapsible] +==== +*Details* + +The default support for TLS v1.0 and v1.1 has been removed. For more information, refer to {kibana-pull}90511[#90511] + +*Impact* + +To enable support, set the environment variable to `NODE_OPTIONS=--tls-min-1.0`. +==== + +[discrete] +[[breaking-74424]] +.Removed support for sysv init +[%collapsible] +==== +*Details* + +Systems that don't have `service` aliased to use kibana.service are unable to use `service start kibana`. For more information, refer to {kibana-pull}74424[#74424] + +*Impact* + +If your system doesn't have `service` aliased to use kibana.service, use `systemctl start kibana.service`. +==== + +[discrete] +[[breaking-42353]] +.Disabled response logging as a default +[%collapsible] +==== +*Details* + +By default, responses are not logged. Previously, responses were logged if `logging.json` was set to `true`, `logging.dest` was specified, or a TTY was detected. For more information, refer to {kibana-pull}42353[#42353] + +*Impact* + +To log responses, set `logging.events.response=*` in kibana.yml. +==== + +[float] +[[reporting-changes-8.0.0-alpha1]] +==== Reporting changes + +[discrete] +[[breaking-52539]] +.Removed legacy Reporting job params compatibility shim +[%collapsible] +==== +*Details* + +*Reporting* is no longer compatible with POST URL snippets generated with {kib} 6.2.0 and earlier. For more information, refer to {kibana-pull}52539[#52539] + +*Impact* + +If you use POST URL snippets to automatically generate PDF reports, regenerate the POST URL strings. +==== + +[float] +[[rest-api-changes]] +==== Security changes + +[discrete] +[[breaking-47929]] +.Removed `/api/security/v1/saml` route +[%collapsible] +==== +*Details* + +The `/api/security/v1/saml` route has been removed and is reflected in the kibana.yml `server.xsrf.whitelist` setting, {es}, and the Identity Provider SAML settings. For more information, refer to {kibana-pull}47929[#47929] + +*Impact* + +Use the `/api/security/saml/callback` route, or wait to upgrade to 8.0.0-alpha2 when the `/api/security/saml/callback` route breaking change is reverted. +==== + +[discrete] +[[breaking-41700]] +.Reject legacy browsers by default +[%collapsible] +==== +*Details* + +To provide the maximum level of protection for most installations, the csp.strict config is now enabled by default. Legacy browsers not supported by Kibana, such as IE11, are unable to access {kib} unless explicitly enabled. All browsers officially supported by Kibana do not have this issue. For more information, refer to {kibana-pull}41700[#41700] + +*Impact* + +To enable support for legacy browsers, set `csp.strict: false` in kibana.yml. +==== + +[float] +[[settings-changes-8.0.0-alpha1]] +==== Settings changes + +[discrete] +[[breaking-106061]] +.Use new session timeout defaults +[%collapsible] +==== +*Details* + +The default values for the session timeout `xpack.security.session.{lifespan|idleTimeout}` settings have changed. For more information, refer to {kibana-pull}106061[#106061] + +*Impact* + +Use the following default values: + +* `xpack.security.session.idleTimeout: 1h` +* `xpack.security.session.lifespan: 30d` +==== + +[discrete] +[[breaking-87114]] +.Removed support for setting `server.host` to '0' +[%collapsible] +==== +*Details* + +Support for configuring {kib} with `0` as the `server.host` has been removed. Please use `0.0.0.0` instead. For more information, refer to {kibana-pull}87114[#87114] + +*Impact* + +You are now unable to use `0` as the `server.host`. +==== + +[discrete] +[[breaking-38657]] +.Removed `xpack.security.authProviders` and `xpack.security.public` +[%collapsible] +==== +*Details* + +The `xpack.security.public` and `xpack.security.authProviders` settings have been removed. For more information, refer to {kibana-pull}38657[#38657] + +*Impact* + +Use the `xpack.security.authc.saml.realm` setting. +==== + +[discrete] +[[breaking-22696]] +.Removed useUTC deprecation +[%collapsible] +==== +*Details* + +The `logging.useUTC` setting has been removed. For more information, refer to {kibana-pull}22696[#22696] + +*Impact* + +The default timezone is UTC. To change the timezone, set `logging.timezone: false` in kibana.yml. Change the timezone when the system, such as a docker container, is configured for a nonlocal timezone. +==== + +// end::notable-breaking-changes[] + +[float] +[[deprecations-8.0.0]] +=== Deprecations + +The following functionality is deprecated in 8.0.0, and will be removed in 9.0.0. Deprecated functionality does not have an immediate impact on your application, but we strongly recommend you make the necessary updates after you complete the upgrade. + +[discrete] +[[deprecation-74424]] +.Removed support for SysV init +[%collapsible] +==== +*Details* + +Systems that don't have `service` aliased to use kibana.service are unable to use `service start kibana`. For more information, refer to {kibana-pull}74424[#74424] + +*Impact* + +If your system doesn't have `service` aliased to use kibana.service, use `systemctl start kibana.service`. +==== + +[discrete] +[[deprecation-33603]] +.Removed `xpack:defaultAdminEmail` setting +[%collapsible] +==== +*Details* + +The `xpack:default_admin_email` setting for monitoring use has been removed. For more information, refer to {kibana-pull}33603[#33603] + +*Impact* + +Use the `xpack.monitoring.clusterAlertsEmail` in kibana.yml. +==== + +[float] +[[enhancements-and-bug-fixes-v8.0.0]] +=== Bug fix + +The 8.0.0-alpha1 release includes the following bug fix. + +Operations:: +* Moves systemd service to /usr/lib/systemd/system {kibana-pull}83571[#83571] + +//[[release-notes-8.0.0]] +//== {kib} 8.0.0 + +//coming::[8.0.0] diff --git a/docs/canvas/canvas-edit-workpads.asciidoc b/docs/canvas/canvas-edit-workpads.asciidoc index 9f2808c9ad451e0..0dd7351e9cbccb0 100644 --- a/docs/canvas/canvas-edit-workpads.asciidoc +++ b/docs/canvas/canvas-edit-workpads.asciidoc @@ -12,6 +12,7 @@ When you frequently use copy and paste, create variables to easily reuse strings each element instead of updating them manually. . Create the variables. +.. Expand the *Variables* options. .. Click *Add a variable*. .. Specify the variable options, then click *Save changes*. @@ -25,12 +26,12 @@ For example, to change the index pattern for a set of charts: . Specify the variable options. + [role="screenshot"] -image::images/specify_variable_syntax.png[Image describing how to specify the variable syntax] +image::images/specify_variable_syntax.png[Variable syntax options] + . Copy the variable, then apply it to each element you want to update in the *Expression editor*. + [role="screenshot"] -image::images/copy_variable_syntax.png[Image demonstrating expression editor] +image::images/copy_variable_syntax.png[Copied variable syntax pasted in the Expression editor] [float] [[apply-changes-to-the-entire-workpad]] @@ -84,9 +85,6 @@ To use an element with the same functionality and appearance in multiple places, Select the element, then click *Edit > Clone*. -[role="screenshot"] -image::images/clone_element.gif[Image showing how to clone elements] - [float] [[move-and-resize-elements]] ==== Move and resize elements diff --git a/docs/canvas/canvas-present-workpad.asciidoc b/docs/canvas/canvas-present-workpad.asciidoc index 438e09b701fa3dd..0dab86fd0f4446e 100644 --- a/docs/canvas/canvas-present-workpad.asciidoc +++ b/docs/canvas/canvas-present-workpad.asciidoc @@ -9,16 +9,10 @@ When you are ready to present your workpad, use and enable the presentation opti .. From the workpad menu, click *View > Autoplay settings*. .. Under *Change cycling interval*, select the interval you want to use, or *Set a custom interval*. -+ -[role="screenshot"] -image::images/canvas-autoplay-interval.png[Element autoplay interval] . To enable autoplay, click *View > Turn autoplay on*. . To start your presentation, click *View > Enter fullscreen mode*. -+ -[role="screenshot"] -image::images/canvas-fullscreen.png[Image showing how to enter fullscreen mode from view dropdown] . When you are ready to exit fullscreen mode, press Esc. @@ -31,9 +25,6 @@ To get a closer look at a portion of your workpad, use the zoom options. . Click *View > Zoom*. . Select the zoom option. -+ -[role="screenshot"] -image::images/canvas-zoom-controls.png[Zoom controls, also in view dropdown] [float] [[configure-auto-refresh-interval]] @@ -45,7 +36,4 @@ Change how often the data refreshes on your workpad. . Select the interval you want to use, or *Set a custom interval*. + -[role="screenshot"] -image::images/canvas-refresh-interval.png[Element data refresh interval] -+ To manually refresh the data, click image:canvas/images/canvas-refresh-data.png[Canvas refresh data button]. diff --git a/docs/canvas/canvas-tutorial.asciidoc b/docs/canvas/canvas-tutorial.asciidoc index 89114affb932275..8700161df2bf61f 100644 --- a/docs/canvas/canvas-tutorial.asciidoc +++ b/docs/canvas/canvas-tutorial.asciidoc @@ -9,9 +9,9 @@ To familiarize yourself with *Canvas*, add the Sample eCommerce orders data, the To create a workpad of the eCommerce store data, add the data set, then create the workpad. -. On the {kib} *Home* page, click *Try our sample data*. +. Go to the {kib} *Home* page, then click *Try our sample data*. -. From *Sample eCommerce orders data*, click *Add data*. +. On the *Sample eCommerce orders data* card, click *Add data*. . Open the main menu, then click *Canvas*. @@ -26,8 +26,8 @@ To customize your workpad to look the way you want, add your own images. + The default Elastic logo image appears on the page. -. To replace the Elastic logo with your own image, select the image, then use the editor. - +. To add your own image, click the Elastic logo, then drag the image file to the *Select or drag and drop an image* field. ++ [role="screenshot"] image::images/canvas-image-element.png[Image showing how to add the image element] diff --git a/docs/canvas/images/canvas-add-image.gif b/docs/canvas/images/canvas-add-image.gif deleted file mode 100644 index 994ec6e1b4f2885..000000000000000 Binary files a/docs/canvas/images/canvas-add-image.gif and /dev/null differ diff --git a/docs/canvas/images/canvas-add-pages.gif b/docs/canvas/images/canvas-add-pages.gif index c6e09d6f386ae59..0b001c5f8b10c25 100644 Binary files a/docs/canvas/images/canvas-add-pages.gif and b/docs/canvas/images/canvas-add-pages.gif differ diff --git a/docs/canvas/images/canvas-autoplay-interval.png b/docs/canvas/images/canvas-autoplay-interval.png index a7b1251efc8081a..52837a4feb04776 100644 Binary files a/docs/canvas/images/canvas-autoplay-interval.png and b/docs/canvas/images/canvas-autoplay-interval.png differ diff --git a/docs/canvas/images/canvas-background-color-picker.png b/docs/canvas/images/canvas-background-color-picker.png deleted file mode 100644 index ec38b5c1c5f7e83..000000000000000 Binary files a/docs/canvas/images/canvas-background-color-picker.png and /dev/null differ diff --git a/docs/canvas/images/canvas-chart-element.png b/docs/canvas/images/canvas-chart-element.png index bf5e04bf89af545..c6942dfb61a3f46 100644 Binary files a/docs/canvas/images/canvas-chart-element.png and b/docs/canvas/images/canvas-chart-element.png differ diff --git a/docs/canvas/images/canvas-create-URL.gif b/docs/canvas/images/canvas-create-URL.gif deleted file mode 100644 index 60d69cdd599a3ed..000000000000000 Binary files a/docs/canvas/images/canvas-create-URL.gif and /dev/null differ diff --git a/docs/canvas/images/canvas-element-select.gif b/docs/canvas/images/canvas-element-select.gif index 1bfd1132f25c7a7..3bcba5c64c93dd2 100644 Binary files a/docs/canvas/images/canvas-element-select.gif and b/docs/canvas/images/canvas-element-select.gif differ diff --git a/docs/canvas/images/canvas-embed_workpad.gif b/docs/canvas/images/canvas-embed_workpad.gif deleted file mode 100644 index 1cda5b572acefb5..000000000000000 Binary files a/docs/canvas/images/canvas-embed_workpad.gif and /dev/null differ diff --git a/docs/canvas/images/canvas-export-workpad.png b/docs/canvas/images/canvas-export-workpad.png deleted file mode 100644 index 213bbaa5a26d357..000000000000000 Binary files a/docs/canvas/images/canvas-export-workpad.png and /dev/null differ diff --git a/docs/canvas/images/canvas-fullscreen.png b/docs/canvas/images/canvas-fullscreen.png deleted file mode 100644 index b8a816d2903961f..000000000000000 Binary files a/docs/canvas/images/canvas-fullscreen.png and /dev/null differ diff --git a/docs/canvas/images/canvas-generate-pdf.gif b/docs/canvas/images/canvas-generate-pdf.gif deleted file mode 100644 index 24711d01fbe0c03..000000000000000 Binary files a/docs/canvas/images/canvas-generate-pdf.gif and /dev/null differ diff --git a/docs/canvas/images/canvas-gs-example.png b/docs/canvas/images/canvas-gs-example.png deleted file mode 100644 index bae32ef96a93fd1..000000000000000 Binary files a/docs/canvas/images/canvas-gs-example.png and /dev/null differ diff --git a/docs/canvas/images/canvas-image-element.png b/docs/canvas/images/canvas-image-element.png index 13c9090e77c7600..ef1ab3bb3f105d2 100644 Binary files a/docs/canvas/images/canvas-image-element.png and b/docs/canvas/images/canvas-image-element.png differ diff --git a/docs/canvas/images/canvas-map-embed.gif b/docs/canvas/images/canvas-map-embed.gif deleted file mode 100644 index c6ba5c29df42ab4..000000000000000 Binary files a/docs/canvas/images/canvas-map-embed.gif and /dev/null differ diff --git a/docs/canvas/images/canvas-metric-element.png b/docs/canvas/images/canvas-metric-element.png index 03871dcc81862ca..8dcdb8d42cc9d82 100644 Binary files a/docs/canvas/images/canvas-metric-element.png and b/docs/canvas/images/canvas-metric-element.png differ diff --git a/docs/canvas/images/canvas-refresh-data.png b/docs/canvas/images/canvas-refresh-data.png index 7a71686f04491ba..6f051274245c824 100644 Binary files a/docs/canvas/images/canvas-refresh-data.png and b/docs/canvas/images/canvas-refresh-data.png differ diff --git a/docs/canvas/images/canvas-refresh-interval.png b/docs/canvas/images/canvas-refresh-interval.png deleted file mode 100644 index c097d950a7ec7ae..000000000000000 Binary files a/docs/canvas/images/canvas-refresh-interval.png and /dev/null differ diff --git a/docs/canvas/images/canvas-timefilter-element.png b/docs/canvas/images/canvas-timefilter-element.png index e210b0b3288c6a8..b79adbfeea09425 100644 Binary files a/docs/canvas/images/canvas-timefilter-element.png and b/docs/canvas/images/canvas-timefilter-element.png differ diff --git a/docs/canvas/images/canvas-zoom-controls.png b/docs/canvas/images/canvas-zoom-controls.png deleted file mode 100644 index 1407ca3cd86270b..000000000000000 Binary files a/docs/canvas/images/canvas-zoom-controls.png and /dev/null differ diff --git a/docs/canvas/images/canvas_element_options.png b/docs/canvas/images/canvas_element_options.png deleted file mode 100644 index 41457bab4ff3689..000000000000000 Binary files a/docs/canvas/images/canvas_element_options.png and /dev/null differ diff --git a/docs/canvas/images/canvas_save_element.png b/docs/canvas/images/canvas_save_element.png deleted file mode 100644 index 8a601efab874a8a..000000000000000 Binary files a/docs/canvas/images/canvas_save_element.png and /dev/null differ diff --git a/docs/canvas/images/clone_element.gif b/docs/canvas/images/clone_element.gif deleted file mode 100644 index ef8f44223d240c9..000000000000000 Binary files a/docs/canvas/images/clone_element.gif and /dev/null differ diff --git a/docs/dev-tools/console/console.asciidoc b/docs/dev-tools/console/console.asciidoc index 731290fea22d0c5..e1cd156e6a9e4f1 100644 --- a/docs/dev-tools/console/console.asciidoc +++ b/docs/dev-tools/console/console.asciidoc @@ -1,7 +1,7 @@ [[console-kibana]] -== Console +== Run {es} API requests -*Console* enables you to interact with the REST API of {es}. You can: +Interact with the REST API of {es} with *Console*. You can: * Send requests to {es} and view the responses * View API documentation diff --git a/docs/dev-tools/painlesslab/index.asciidoc b/docs/dev-tools/painlesslab/index.asciidoc index 4077ffe87ca1a6a..387c0522dd1caad 100644 --- a/docs/dev-tools/painlesslab/index.asciidoc +++ b/docs/dev-tools/painlesslab/index.asciidoc @@ -1,6 +1,6 @@ [role="xpack"] [[painlesslab]] -== Painless Lab +== Debug Painless scripts beta::[] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 77f16a9d69d46c9..ba594b1f312e981 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -76,6 +76,14 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image. +|{kib-repo}blob/{branch}/src/plugins/expression_image/README.md[expressionImage] +|Expression Image plugin adds an image renderer to the expression plugin. The renderer will display the given image. + + +|{kib-repo}blob/{branch}/src/plugins/expression_metric/README.md[expressionMetric] +|Expression Metric plugin adds a metric renderer and function to the expression plugin. + + |{kib-repo}blob/{branch}/src/plugins/expression_repeat_image/README.md[expressionRepeatImage] |Expression Repeat Image plugin adds a repeatImage function to the expression plugin and an associated renderer. The renderer will display the given image in mutliple instances. diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md index 7592b8486d950fe..e5f08213da510f6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.md @@ -19,4 +19,5 @@ export interface DeprecationsDetails | [documentationUrl](./kibana-plugin-core-server.deprecationsdetails.documentationurl.md) | string | | | [level](./kibana-plugin-core-server.deprecationsdetails.level.md) | 'warning' | 'critical' | 'fetch_error' | levels: - warning: will not break deployment upon upgrade - critical: needs to be addressed before upgrade. - fetch\_error: Deprecations service failed to grab the deprecation details for the domain. | | [message](./kibana-plugin-core-server.deprecationsdetails.message.md) | string | | +| [requireRestart](./kibana-plugin-core-server.deprecationsdetails.requirerestart.md) | boolean | | diff --git a/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.requirerestart.md b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.requirerestart.md new file mode 100644 index 000000000000000..52c0fcf1c300161 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.deprecationsdetails.requirerestart.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DeprecationsDetails](./kibana-plugin-core-server.deprecationsdetails.md) > [requireRestart](./kibana-plugin-core-server.deprecationsdetails.requirerestart.md) + +## DeprecationsDetails.requireRestart property + +Signature: + +```typescript +requireRestart?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md index 517e8f3ffda1f1c..c21eb1110ed8e8d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md @@ -18,6 +18,6 @@ export interface KibanaExecutionContext | [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md) | string | human readable description. For example, a vis title, action name | | [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | string | unique value to identify the source | | [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md) | string | public name of a user-facing feature | -| [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) | string | Kibana application initated an operation. Can be narrowed to an enum later. | +| [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) | string | Kibana application initated an operation. | | [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md) | string | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url | diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md index 534b0cdea17532c..6941bb150efdd4b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.type.md @@ -4,7 +4,7 @@ ## KibanaExecutionContext.type property -Kibana application initated an operation. Can be narrowed to an enum later. +Kibana application initated an operation. Signature: diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.getallmigrations.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.getallmigrations.md new file mode 100644 index 000000000000000..6613afb98efc26f --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.getallmigrations.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [getAllMigrations](./kibana-plugin-plugins-expressions-public.executor.getallmigrations.md) + +## Executor.getAllMigrations() method + +Signature: + +```typescript +getAllMigrations(): MigrateFunctionsObject; +``` +Returns: + +`MigrateFunctionsObject` + diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md index 6835188c2fb0476..61caebbd36368af 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.md @@ -34,12 +34,13 @@ export declare class Executor = Record -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [migrate](./kibana-plugin-plugins-expressions-public.executor.migrate.md) +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [Executor](./kibana-plugin-plugins-expressions-public.executor.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-public.executor.migratetolatest.md) -## Executor.migrate() method +## Executor.migrateToLatest() method Signature: ```typescript -migrate(ast: SerializableState, version: string): ExpressionAstExpression; +migrateToLatest(state: VersionedState): ExpressionAstExpression; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| ast | SerializableState | | -| version | string | | +| state | VersionedState | | Returns: diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.getallmigrations.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.getallmigrations.md new file mode 100644 index 000000000000000..b337d0dc21b4eff --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.getallmigrations.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [getAllMigrations](./kibana-plugin-plugins-expressions-public.expressionsservice.getallmigrations.md) + +## ExpressionsService.getAllMigrations property + +gets an object with semver mapped to a migration function + +Signature: + +```typescript +getAllMigrations: () => import("../../../kibana_utils/common").MigrateFunctionsObject; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md index 9afd603bc486940..cde8c7c1a8f24fa 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.md @@ -32,6 +32,7 @@ export declare class ExpressionsService implements PersistableStateServiceExecutor | | | [extract](./kibana-plugin-plugins-expressions-public.expressionsservice.extract.md) | | (state: ExpressionAstExpression) => {
state: ExpressionAstExpression;
references: SavedObjectReference[];
} | Extracts saved object references from expression AST | | [fork](./kibana-plugin-plugins-expressions-public.expressionsservice.fork.md) | | () => ExpressionsService | | +| [getAllMigrations](./kibana-plugin-plugins-expressions-public.expressionsservice.getallmigrations.md) | | () => import("../../../kibana_utils/common").MigrateFunctionsObject | gets an object with semver mapped to a migration function | | [getFunction](./kibana-plugin-plugins-expressions-public.expressionsservice.getfunction.md) | | ExpressionsServiceStart['getFunction'] | | | [getFunctions](./kibana-plugin-plugins-expressions-public.expressionsservice.getfunctions.md) | | () => ReturnType<Executor['getFunctions']> | Returns POJO map of all registered expression functions, where keys are names of the functions and values are ExpressionFunction instances. | | [getRenderer](./kibana-plugin-plugins-expressions-public.expressionsservice.getrenderer.md) | | ExpressionsServiceStart['getRenderer'] | | @@ -39,7 +40,7 @@ export declare class ExpressionsService implements PersistableStateServiceExpressionsServiceStart['getType'] | | | [getTypes](./kibana-plugin-plugins-expressions-public.expressionsservice.gettypes.md) | | () => ReturnType<Executor['getTypes']> | Returns POJO map of all registered expression types, where keys are names of the types and values are ExpressionType instances. | | [inject](./kibana-plugin-plugins-expressions-public.expressionsservice.inject.md) | | (state: ExpressionAstExpression, references: SavedObjectReference[]) => ExpressionAstExpression | Injects saved object references into expression AST | -| [migrate](./kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md) | | (state: SerializableState, version: string) => ExpressionAstExpression | Runs the migration (if it exists) for specified version. This will run a single migration step (ie from 7.10.0 to 7.10.1) | +| [migrateToLatest](./kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md) | | (state: VersionedState) => ExpressionAstExpression | migrates an old expression to latest version | | [registerFunction](./kibana-plugin-plugins-expressions-public.expressionsservice.registerfunction.md) | | (functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)) => void | Register an expression function, which will be possible to execute as part of the expression pipeline.Below we register a function which simply sleeps for given number of milliseconds to delay the execution and outputs its input as-is. ```ts expressions.registerFunction({ diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md deleted file mode 100644 index d1f24bfcfc0bbb6..000000000000000 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [migrate](./kibana-plugin-plugins-expressions-public.expressionsservice.migrate.md) - -## ExpressionsService.migrate property - -Runs the migration (if it exists) for specified version. This will run a single migration step (ie from 7.10.0 to 7.10.1) - -Signature: - -```typescript -readonly migrate: (state: SerializableState, version: string) => ExpressionAstExpression; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md new file mode 100644 index 000000000000000..55efb8d5a8af395 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExpressionsService](./kibana-plugin-plugins-expressions-public.expressionsservice.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-public.expressionsservice.migratetolatest.md) + +## ExpressionsService.migrateToLatest property + +migrates an old expression to latest version + +Signature: + +```typescript +migrateToLatest: (state: VersionedState) => ExpressionAstExpression; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index dcdbd663f84e4e5..a228628fece0fd6 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -28,7 +28,7 @@ export interface IExpressionLoaderParams | [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | SerializableState | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | string | | | [syncColors](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md) | boolean | | -| [throttle](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md) | number | Throttling of partial results in milliseconds. By default, throttling is disabled. | +| [throttle](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md) | number | Throttling of partial results in milliseconds. 0 is disabling the throttling. By default, it equals 1000. | | [uiState](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.uistate.md) | unknown | | | [variables](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.variables.md) | Record<string, any> | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md index 3383bce8797762f..54949bbbd5dd63f 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md @@ -4,7 +4,7 @@ ## IExpressionLoaderParams.throttle property -Throttling of partial results in milliseconds. By default, throttling is disabled. +Throttling of partial results in milliseconds. 0 is disabling the throttling. By default, it equals 1000. Signature: diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.getallmigrations.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.getallmigrations.md new file mode 100644 index 000000000000000..f397f3a9869b8fa --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.getallmigrations.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [getAllMigrations](./kibana-plugin-plugins-expressions-server.executor.getallmigrations.md) + +## Executor.getAllMigrations() method + +Signature: + +```typescript +getAllMigrations(): MigrateFunctionsObject; +``` +Returns: + +`MigrateFunctionsObject` + diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md index 48002a9f986dfc5..00b4300990ca842 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.md @@ -34,12 +34,13 @@ export declare class Executor = Record -[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [migrate](./kibana-plugin-plugins-expressions-server.executor.migrate.md) +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [Executor](./kibana-plugin-plugins-expressions-server.executor.md) > [migrateToLatest](./kibana-plugin-plugins-expressions-server.executor.migratetolatest.md) -## Executor.migrate() method +## Executor.migrateToLatest() method Signature: ```typescript -migrate(ast: SerializableState, version: string): ExpressionAstExpression; +migrateToLatest(state: VersionedState): ExpressionAstExpression; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| ast | SerializableState | | -| version | string | | +| state | VersionedState | | Returns: diff --git a/docs/management/field-formatters/color-formatter.asciidoc b/docs/management/field-formatters/color-formatter.asciidoc index 488fb3715379970..855b7d804c7fbad 100644 --- a/docs/management/field-formatters/color-formatter.asciidoc +++ b/docs/management/field-formatters/color-formatter.asciidoc @@ -1,5 +1,3 @@ The *Color* field formatter enables you to specify colors with ranges of values for a number field. When you select the *Color* formatter, click *Add Color*, then specify the *Range*, *Text color*, and *Background color*. - -image::images/colorformatter.png[] diff --git a/docs/settings/fleet-settings.asciidoc b/docs/settings/fleet-settings.asciidoc index cb80165e7099042..3ae1c9df616b046 100644 --- a/docs/settings/fleet-settings.asciidoc +++ b/docs/settings/fleet-settings.asciidoc @@ -5,6 +5,11 @@ {fleet} settings ++++ +[NOTE] +==== +In {ecloud}, {fleet} flags are already configured. +==== + You can configure `xpack.fleet` settings in your `kibana.yml`. By default, {fleet} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts. @@ -28,7 +33,10 @@ See the {fleet-guide}/index.html[{fleet}] docs for more information. [cols="2*<"] |=== | `xpack.fleet.registryUrl` - | The address to use to reach {package-manager} registry. + | The address to use to reach the {package-manager} registry. +| `xpack.fleet.registryProxyUrl` + | The proxy address to use to reach the {package-manager} registry. + |=== ==== {fleet} settings @@ -39,9 +47,86 @@ See the {fleet-guide}/index.html[{fleet}] docs for more information. | Hostnames used by {agent} for accessing {fleet-server}. | `xpack.fleet.agents.elasticsearch.hosts` | Hostnames used by {agent} for accessing {es}. +| `xpack.fleet.agents.elasticsearch.ca_sha256` + | Hash pin used for certificate verification. The pin is a base64-encoded + string of the SHA-256 fingerprint. |=== -[NOTE] -==== -In {ecloud}, {fleet} flags are already configured. -==== + +==== Preconfiguration settings (for advanced use cases) + +Use these settings to pre-define integrations and agent policies that you +want {fleet} to load up by default. + +[cols="2*> to a non-loopback address. -. To allow remote users to connect to {kib}, set the parameter `server.host` in kibana.yml to a non-loopback address. +. Log on to your account. -. On the home page, click *{kib}*. -+ -To make the {kib} page your landing page, click *Make this my landing page*. +. Go to the home page, then click *{kib}*. + +. To make the {kib} page your landing page, click *Make this my landing page*. [float] [[status]] === Check the {kib} status -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. +The status page displays information about the server resource usage and installed plugins. + +To view the {kib} status page, use the status endpoint. For example, `localhost:5601/status`. [role="screenshot"] -image::images/kibana-status-page-7_5_0.png[] +image::images/kibana-status-page-7_14_0.png[Kibana server status page] For JSON-formatted server status details, use the `localhost:5601/api/status` API endpoint. diff --git a/docs/setup/configuring-reporting.asciidoc b/docs/setup/configuring-reporting.asciidoc index af4fc14448ac571..0dba7befa293189 100644 --- a/docs/setup/configuring-reporting.asciidoc +++ b/docs/setup/configuring-reporting.asciidoc @@ -42,14 +42,10 @@ To troubleshoot the problem, start the {kib} server with environment variables t [[grant-user-access]] === Grant users access to reporting -When security is enabled, access to the {report-features} is controlled by roles and privileges. +When security is enabled, access to the {report-features} is controlled by roles and <>. With privileges, you can define custom roles that grant *Reporting* privileges as sub-features of {kib} applications. To grant users permission to generate reports and view their reports in *Reporting*, create and assign the reporting role. [[reporting-app-users]] -In 7.12.0 and earlier, you grant access to the {report-features} by assigning users the `reporting_user` role in {es}. - -In 7.14.0 and later, you configure *Reporting* to use <>. By using {kib} privileges, you can define custom roles that grant *Reporting* privileges as sub-features of {kib} applications. - -To grant users permission to generate reports and view their reports in *Reporting*, create and assign the reporting role. +NOTE: In 7.12.0 and earlier, you grant access to the {report-features} by assigning users the `reporting_user` role in {es}. . Create the reporting role. @@ -59,6 +55,7 @@ To grant users permission to generate reports and view their reports in *Reporti . Specify the role settings. + .. Enter the *Role name*. For example, `custom_reporting_user`. .. Specify the *Indices* and *Privileges*. @@ -66,22 +63,18 @@ To grant users permission to generate reports and view their reports in *Reporti Access to data is an index-level privilege. For each index that contains the data you want to include in reports, add a line, then give each index `read` and `view_index_metadata` privileges. + For more information, refer to {ref}/security-privileges.html[Security privileges]. -+ -[role="screenshot"] -image::user/security/images/reporting-privileges-example.png["Reporting privileges"] . Add the {kib} privileges. .. Click *Add Kibana privilege*. -.. Select one or more *Spaces* that you want to grant reporting privileges to. +.. Select one or more *Spaces*. .. Click *Customize*, then click *Analytics*. -.. Next to each application you want to grant reporting privileges to, click *All*. +.. Next to the applications you want to grant reporting privileges, click *All*. + -[role="screenshot"] -image::user/security/images/reporting-custom-role.png["Reporting custom role"] +If the *Reporting* option is unavailable, contact your administrator, or <>. .. Click *Add {kib} privilege*. diff --git a/docs/setup/images/kibana-status-page-7_14_0.png b/docs/setup/images/kibana-status-page-7_14_0.png new file mode 100644 index 000000000000000..db172d87eedb5cc Binary files /dev/null and b/docs/setup/images/kibana-status-page-7_14_0.png differ diff --git a/docs/user/canvas.asciidoc b/docs/user/canvas.asciidoc index 08462e0f0ed241e..1cd8eacc456c7fd 100644 --- a/docs/user/canvas.asciidoc +++ b/docs/user/canvas.asciidoc @@ -67,9 +67,6 @@ To use the background colors, images, and data of your choice, start with a blan .. In the *Width* and *Height* fields, specify the size, or select one of default layouts. .. Click the *Background* color picker, then select the color for your workpad. -+ -[role="screenshot"] -image::images/canvas-background-color-picker.png[Canvas color picker] [float] [[create-workpads-from-templates]] @@ -133,9 +130,6 @@ Each element can display a different data source, and pages and workpads often c . To save, use the following options: * To save a single element, select the element, then click *Edit > Save as new element*. -+ -[role="screenshot"] -image::images/canvas_save_element.png[] * To save a group of elements, press and hold Shift, select the elements you want to save, then click *Edit > Save as new element*. @@ -150,9 +144,6 @@ Add a panel that you saved in *Visualize Library* to your workpad. . Click *Add element > Add from {kib}*. . Select the panel you want to add. -+ -[role="screenshot"] -image::images/canvas-map-embed.gif[] . To use the customization options, open the panel menu, then select one of the following options: @@ -177,9 +168,6 @@ To personalize your workpad, add your own logos and graphics. . On the *Manage workpad assets* window, drag and drop your images. . To add the image to the workpad, click the *Create image element* icon. -+ -[role="screenshot"] -image::images/canvas-add-image.gif[Add image to Canvas] [float] [[add-more-pages]] diff --git a/docs/user/reporting/images/shareable-container.png b/docs/user/reporting/images/shareable-container.png index 829fe15706a52c3..2bf3812e94c2d4b 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 457be5b038c12ce..cb999ed189d7731 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -17,7 +17,7 @@ You access the options from the *Share* menu in the toolbar. The sharing options * *PNG Reports* — Generate and download a PNG file of a dashboard or visualization. -* *CSV Reports* — Generate and download a CSV file of a saved search. +* *CSV Reports* — Generate and download a CSV file of a *Discover* saved search. * *Permalinks* — Share a direct link to a *Discover* saved search, dashboard, or visualization. @@ -90,7 +90,7 @@ Share a direct link to a saved search, dashboard, or visualization. To access th [role="screenshot"] image::images/permalink-public-url.png[Permalink share menu with Public URL option highlighted] + -NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to share. +NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to share. For more information, refer to <>. . Click *Copy link*. @@ -156,7 +156,7 @@ Some users might not have access to the dashboard or visualization. For more inf [role="screenshot"] image::images/embed-code-public-url.png[Embed code share menu with Public URL option highlighted] + -NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to embed. +NOTE: *Public URL* is available only when anonymous access is configured and your anonymous service account has privileges to access what you want to embed. For more information, refer to <>. . Click *Copy iFrame code*. diff --git a/docs/user/security/images/reporting-privileges-example.png b/docs/user/security/images/reporting-privileges-example.png index d108fe6634fa2b7..49e58b9c1a008eb 100644 Binary files a/docs/user/security/images/reporting-privileges-example.png and b/docs/user/security/images/reporting-privileges-example.png differ diff --git a/package.json b/package.json index f7856b9f92e749e..3a16330bdf970fe 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,6 @@ "@elastic/request-crypto": "1.1.4", "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.6.0", - "@elastic/ui-ace": "0.2.3", "@emotion/react": "^11.4.0", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.1", @@ -194,7 +193,6 @@ "angular-route": "^1.8.0", "angular-sanitize": "^1.8.0", "angular-sortable-view": "^0.0.17", - "angular-ui-ace": "0.2.3", "antlr4ts": "^0.5.0-alpha.3", "archiver": "^5.2.0", "axios": "^0.21.1", diff --git a/packages/kbn-analytics/src/application_usage_tracker.ts b/packages/kbn-analytics/src/application_usage_tracker.ts index ddc59fe1c53b381..b1cc47334541a30 100644 --- a/packages/kbn-analytics/src/application_usage_tracker.ts +++ b/packages/kbn-analytics/src/application_usage_tracker.ts @@ -156,8 +156,9 @@ export class ApplicationUsageTracker { const appKey = this.createKey(this.currentAppId, viewId); const serializedKey = ApplicationUsageTracker.serializeKey(appKey); const appViewMetric = this.trackedApplicationViews[serializedKey]; - this.sendMetricsToReporter([appViewMetric]); - - delete this.trackedApplicationViews[serializedKey]; + if (appViewMetric) { + this.sendMetricsToReporter([appViewMetric]); + delete this.trackedApplicationViews[serializedKey]; + } } } diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 0bf15d236bc9c71..b135f2d65c6fa33 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -114,5 +114,7 @@ pageLoadAssetSize: cases: 144442 expressionError: 22127 expressionRepeatImage: 22341 + expressionImage: 19288 + expressionMetric: 22238 expressionShape: 30033 userSetup: 18532 diff --git a/src/core/server/deprecations/deprecations_service.test.ts b/src/core/server/deprecations/deprecations_service.test.ts index d1ed7a83402cb39..a0235b478b8f007 100644 --- a/src/core/server/deprecations/deprecations_service.test.ts +++ b/src/core/server/deprecations/deprecations_service.test.ts @@ -91,6 +91,7 @@ describe('DeprecationsService', () => { "documentationUrl": "testDocUrl", "level": "critical", "message": "testMessage", + "requireRestart": true, }, ] `); diff --git a/src/core/server/deprecations/deprecations_service.ts b/src/core/server/deprecations/deprecations_service.ts index ede7f859ffd0d40..e65752c9286ad6d 100644 --- a/src/core/server/deprecations/deprecations_service.ts +++ b/src/core/server/deprecations/deprecations_service.ts @@ -156,6 +156,7 @@ export class DeprecationsService implements CoreService { expect.anything() ); }); + + it('does not increment counter when incrementBy is 0', async () => { + await incrementCounterSuccess(type, id, [{ fieldName: counterFields[0], incrementBy: 0 }]); + + expect(client.update).toBeCalledTimes(1); + expect(client.update).toBeCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + script: expect.objectContaining({ + params: expect.objectContaining({ + counterFieldNames: [counterFields[0]], + counts: [0], + }), + }), + }), + }), + expect.anything() + ); + }); }); }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 986467c917dd25c..6899f8613b07feb 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -1705,8 +1705,20 @@ export class SavedObjectsRepository { } = options; const normalizedCounterFields = counterFields.map((counterField) => { - const fieldName = typeof counterField === 'string' ? counterField : counterField.fieldName; - const incrementBy = typeof counterField === 'string' ? 1 : counterField.incrementBy || 1; + /** + * no counterField configs provided, instead a field name string was passed. + * ie `incrementCounter(so_type, id, ['my_field_name'])` + * Using the default of incrementing by 1 + */ + if (typeof counterField === 'string') { + return { + fieldName: counterField, + incrementBy: initialize ? 0 : 1, + }; + } + + const { incrementBy = 1, fieldName } = counterField; + return { fieldName, incrementBy: initialize ? 0 : incrementBy, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 52548c760e30bb3..8b6574ccc8e0691 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -915,6 +915,8 @@ export interface DeprecationsDetails { level: 'warning' | 'critical' | 'fetch_error'; // (undocumented) message: string; + // (undocumented) + requireRestart?: boolean; } // @public diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index 7aca25d2013d2c6..965a716098f3373 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -18,6 +18,8 @@ export const storybookAliases = { data_enhanced: 'x-pack/plugins/data_enhanced/.storybook', embeddable: 'src/plugins/embeddable/.storybook', expression_error: 'src/plugins/expression_error/.storybook', + expression_image: 'src/plugins/expression_image/.storybook', + expression_metric: 'src/plugins/expression_metric/.storybook', expression_repeat_image: 'src/plugins/expression_repeat_image/.storybook', expression_reveal_image: 'src/plugins/expression_reveal_image/.storybook', expression_shape: 'src/plugins/expression_shape/.storybook', diff --git a/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts b/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts index 11f6954af97b0e6..8dad40d373f31c7 100644 --- a/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts +++ b/src/plugins/discover/public/application/apps/main/services/discover_state.test.ts @@ -202,5 +202,11 @@ describe('createSearchSessionRestorationDataProvider', () => { expect(initialState.timeRange).toBe(relativeTime); expect(restoreState.timeRange).toBe(absoluteTime); }); + + test('restoreState has paused autoRefresh', async () => { + const { initialState, restoreState } = await searchSessionInfoProvider.getUrlGeneratorData(); + expect(initialState.refreshInterval).toBe(undefined); + expect(restoreState.refreshInterval?.pause).toBe(true); + }); }); }); diff --git a/src/plugins/discover/public/application/apps/main/services/discover_state.ts b/src/plugins/discover/public/application/apps/main/services/discover_state.ts index 03705bac8f16c30..e57e3bb029f3106 100644 --- a/src/plugins/discover/public/application/apps/main/services/discover_state.ts +++ b/src/plugins/discover/public/application/apps/main/services/discover_state.ts @@ -392,6 +392,12 @@ function createUrlGeneratorState({ sort: appState.sort, savedQuery: appState.savedQuery, interval: appState.interval, + refreshInterval: shouldRestoreSearchSession + ? { + pause: true, // force pause refresh interval when restoring a session + value: 0, + } + : undefined, useHash: false, }; } diff --git a/src/plugins/embeddable/public/__snapshots__/plugin.test.ts.snap b/src/plugins/embeddable/public/__snapshots__/plugin.test.ts.snap new file mode 100644 index 000000000000000..6ef25188283e5f9 --- /dev/null +++ b/src/plugins/embeddable/public/__snapshots__/plugin.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`embeddable factory migrateToLatest returns list of all migrations 1`] = ` +Object { + "7.11.0": [Function], + "7.12.0": [Function], +} +`; diff --git a/src/plugins/embeddable/public/plugin.test.ts b/src/plugins/embeddable/public/plugin.test.ts index b93dc02ebb5a85e..a616b559d323645 100644 --- a/src/plugins/embeddable/public/plugin.test.ts +++ b/src/plugins/embeddable/public/plugin.test.ts @@ -165,6 +165,19 @@ describe('embeddable factory', () => { start.getAllMigrations!()['7.11.0']!(containerState); expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState); }); + + test('migrateToLatest returns list of all migrations', () => { + const migrations = start.getAllMigrations(); + expect(migrations).toMatchSnapshot(); + }); + + test('migrateToLatest calls correct migrate functions', () => { + start.migrateToLatest!({ + state: embeddableState, + version: '7.11.0', + }); + expect(embeddableFactory.migrations['7.11.0']).toBeCalledWith(embeddableState); + }); }); describe('embeddable enhancements', () => { diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 62ec9e15f564cef..cfb16da7b46b88a 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -39,7 +39,11 @@ import { import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; import { EmbeddableStateTransfer } from './lib/state_transfer'; import { Storage } from '../../kibana_utils/public'; -import { PersistableStateService, SerializableState } from '../../kibana_utils/common'; +import { + migrateToLatest, + PersistableStateService, + SerializableState, +} from '../../kibana_utils/common'; import { ATTRIBUTE_SERVICE_KEY, AttributeService } from './lib/attribute_service'; import { AttributeServiceOptions } from './lib/attribute_service/attribute_service'; import { EmbeddableStateWithType } from '../common/types'; @@ -181,6 +185,13 @@ export class EmbeddablePublicPlugin implements Plugin + getAllMigrations( + Array.from(this.embeddableFactories.values()), + Array.from(this.enhancements.values()), + getMigrateFunction(commonContract) + ); + return { getEmbeddableFactory: this.getEmbeddableFactory, getEmbeddableFactories: this.getEmbeddableFactories, @@ -206,12 +217,10 @@ export class EmbeddablePublicPlugin implements Plugin - getAllMigrations( - Array.from(this.embeddableFactories.values()), - Array.from(this.enhancements.values()), - getMigrateFunction(commonContract) - ), + getAllMigrations: getAllMigrationsFn, + migrateToLatest: (state) => { + return migrateToLatest(getAllMigrationsFn(), state) as EmbeddableStateWithType; + }, }; } diff --git a/src/plugins/expression_image/.storybook/main.js b/src/plugins/expression_image/.storybook/main.js new file mode 100644 index 000000000000000..742239e638b8ac0 --- /dev/null +++ b/src/plugins/expression_image/.storybook/main.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line import/no-commonjs +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/src/plugins/expression_image/README.md b/src/plugins/expression_image/README.md new file mode 100755 index 000000000000000..b02c9fd39b3d21a --- /dev/null +++ b/src/plugins/expression_image/README.md @@ -0,0 +1,9 @@ +# expressionRevealImage + +Expression Image plugin adds an `image` renderer to the expression plugin. The renderer will display the given image. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_image/__fixtures__/function_specs.ts b/src/plugins/expression_image/__fixtures__/function_specs.ts new file mode 100644 index 000000000000000..2117a8fc4b72616 --- /dev/null +++ b/src/plugins/expression_image/__fixtures__/function_specs.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { imageFunction } from '../common/expression_functions'; +import { ExpressionFunction } from '../../../../src/plugins/expressions'; + +export const functionSpecs = [imageFunction].map((fn) => new ExpressionFunction(fn())); diff --git a/src/plugins/expression_image/__fixtures__/index.ts b/src/plugins/expression_image/__fixtures__/index.ts new file mode 100644 index 000000000000000..b1087597ec172e9 --- /dev/null +++ b/src/plugins/expression_image/__fixtures__/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { functionSpecs } from './function_specs'; +export { imageFunction } from '../common/expression_functions'; diff --git a/src/plugins/expression_image/common/constants.ts b/src/plugins/expression_image/common/constants.ts new file mode 100644 index 000000000000000..0ac8bec2e1f7d75 --- /dev/null +++ b/src/plugins/expression_image/common/constants.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const PLUGIN_ID = 'expressionImage'; +export const PLUGIN_NAME = 'expressionImage'; + +export const CONTEXT = '_context_'; +export const BASE64 = '`base64`'; +export const URL = 'URL'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js b/src/plugins/expression_image/common/expression_functions/image_function.test.ts similarity index 59% rename from x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js rename to src/plugins/expression_image/common/expression_functions/image_function.test.ts index 862560e5643d7eb..7deaeb90b411b9a 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.test.js +++ b/src/plugins/expression_image/common/expression_functions/image_function.test.ts @@ -1,72 +1,78 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import expect from '@kbn/expect'; +import { ExecutionContext } from 'src/plugins/expressions'; import { + functionWrapper, getElasticLogo, getElasticOutline, - functionWrapper, -} from '../../../../../../src/plugins/presentation_util/common/lib'; -import { image } from './image'; +} from '../../../presentation_util/common/lib'; +import { imageFunction as image } from './image_function'; -// TODO: the test was not running and is not up to date describe('image', () => { const fn = functionWrapper(image); - let elasticLogo; - let elasticOutline; + let elasticLogo: string; + let elasticOutline: string; + beforeEach(async () => { - elasticLogo = (await getElasticLogo()).elasticLogo; - elasticOutline = (await getElasticOutline()).elasticOutline; + elasticLogo = (await getElasticLogo())?.elasticLogo; + elasticOutline = (await getElasticOutline())?.elasticOutline; }); it('returns an image object using a dataUrl', async () => { - const result = await fn(null, { dataurl: elasticOutline, mode: 'cover' }); + const result = await fn( + null, + { dataurl: elasticOutline, mode: 'cover' }, + {} as ExecutionContext + ); expect(result).to.have.property('type', 'image'); }); describe('args', () => { describe('dataurl', () => { it('sets the source of the image using dataurl', async () => { - const result = await fn(null, { dataurl: elasticOutline }); + const result = await fn(null, { dataurl: elasticOutline }, {} as ExecutionContext); expect(result).to.have.property('dataurl', elasticOutline); }); it.skip('sets the source of the image using url', async () => { // This is skipped because functionWrapper doesn't use the actual // interpreter and doesn't resolve aliases - const result = await fn(null, { url: elasticOutline }); + const result = await fn(null, { url: elasticOutline }, {} as ExecutionContext); expect(result).to.have.property('dataurl', elasticOutline); }); it('defaults to the elasticLogo if not provided', async () => { - const result = await fn(null); + const result = await fn(null, {}, {} as ExecutionContext); expect(result).to.have.property('dataurl', elasticLogo); }); }); describe('sets the mode', () => { it('to contain', async () => { - const result = await fn(null, { mode: 'contain' }); + const result = await fn(null, { mode: 'contain' }, {} as ExecutionContext); expect(result).to.have.property('mode', 'contain'); }); it('to cover', async () => { - const result = await fn(null, { mode: 'cover' }); + const result = await fn(null, { mode: 'cover' }, {} as ExecutionContext); expect(result).to.have.property('mode', 'cover'); }); it('to stretch', async () => { - const result = await fn(null, { mode: 'stretch' }); + const result = await fn(null, { mode: 'stretch' }, {} as ExecutionContext); expect(result).to.have.property('mode', '100% 100%'); }); it("defaults to 'contain' if not provided", async () => { - const result = await fn(null); + const result = await fn(null, {}, {} as ExecutionContext); expect(result).to.have.property('mode', 'contain'); }); }); diff --git a/src/plugins/expression_image/common/expression_functions/image_function.ts b/src/plugins/expression_image/common/expression_functions/image_function.ts new file mode 100644 index 000000000000000..8394681e1b10bb6 --- /dev/null +++ b/src/plugins/expression_image/common/expression_functions/image_function.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { getElasticLogo, resolveWithMissingImage } from '../../../presentation_util/common/lib'; +import { BASE64, URL } from '../constants'; +import { ExpressionImageFunction, ImageMode } from '../types'; + +export const strings = { + help: i18n.translate('expressionImage.functions.imageHelpText', { + defaultMessage: + 'Displays an image. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.', + values: { + BASE64, + URL, + }, + }), + args: { + dataurl: i18n.translate('expressionImage.functions.image.args.dataurlHelpText', { + defaultMessage: 'The {https} {URL} or {BASE64} data {URL} of an image.', + values: { + BASE64, + https: 'HTTP(S)', + URL, + }, + }), + mode: i18n.translate('expressionImage.functions.image.args.modeHelpText', { + defaultMessage: + '{contain} shows the entire image, scaled to fit. ' + + '{cover} fills the container with the image, cropping from the sides or bottom as needed. ' + + '{stretch} resizes the height and width of the image to 100% of the container.', + values: { + contain: `\`"${ImageMode.CONTAIN}"\``, + cover: `\`"${ImageMode.COVER}"\``, + stretch: `\`"${ImageMode.STRETCH}"\``, + }, + }), + }, +}; + +const errors = { + invalidImageMode: () => + i18n.translate('expressionImage.functions.image.invalidImageModeErrorMessage', { + defaultMessage: '"mode" must be "{contain}", "{cover}", or "{stretch}"', + values: { + contain: ImageMode.CONTAIN, + cover: ImageMode.COVER, + stretch: ImageMode.STRETCH, + }, + }), +}; + +export const imageFunction: ExpressionImageFunction = () => { + const { help, args: argHelp } = strings; + + return { + name: 'image', + aliases: [], + type: 'image', + inputTypes: ['null'], + help, + args: { + dataurl: { + // This was accepting dataurl, but there was no facility in fn for checking type and handling a dataurl type. + types: ['string', 'null'], + help: argHelp.dataurl, + aliases: ['_', 'url'], + default: null, + }, + mode: { + types: ['string'], + help: argHelp.mode, + default: 'contain', + options: Object.values(ImageMode), + }, + }, + fn: async (input, { dataurl, mode }) => { + if (!mode || !Object.values(ImageMode).includes(mode)) { + throw new Error(errors.invalidImageMode()); + } + + const modeStyle = mode === 'stretch' ? '100% 100%' : mode; + const { elasticLogo } = await getElasticLogo(); + return { + type: 'image', + mode: modeStyle, + dataurl: resolveWithMissingImage(dataurl, elasticLogo) as string, + }; + }, + }; +}; diff --git a/src/plugins/vis_type_timeseries/public/application/contexts/default_index_context.ts b/src/plugins/expression_image/common/expression_functions/index.ts similarity index 67% rename from src/plugins/vis_type_timeseries/public/application/contexts/default_index_context.ts rename to src/plugins/expression_image/common/expression_functions/index.ts index a8770d86fba9b06..5274069d3d17ea5 100644 --- a/src/plugins/vis_type_timeseries/public/application/contexts/default_index_context.ts +++ b/src/plugins/expression_image/common/expression_functions/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import React from 'react'; -import { IIndexPattern } from '../../../../data/public'; +import { imageFunction } from './image_function'; -export const DefaultIndexPatternContext = React.createContext(null); +export const functions = [imageFunction]; + +export { imageFunction }; diff --git a/src/plugins/expression_image/common/index.ts b/src/plugins/expression_image/common/index.ts new file mode 100755 index 000000000000000..f251b9cf01cb3f1 --- /dev/null +++ b/src/plugins/expression_image/common/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './constants'; +export * from './types'; diff --git a/src/plugins/expression_image/common/types/expression_functions.ts b/src/plugins/expression_image/common/types/expression_functions.ts new file mode 100644 index 000000000000000..5ee9ed93cc87b0f --- /dev/null +++ b/src/plugins/expression_image/common/types/expression_functions.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ExpressionFunctionDefinition } from '../../../expressions'; + +export enum ImageMode { + CONTAIN = 'contain', + COVER = 'cover', + STRETCH = 'stretch', +} + +interface Arguments { + dataurl: string | null; + mode: ImageMode | null; +} + +export interface Return { + type: 'image'; + mode: string; + dataurl: string; +} + +export type ExpressionImageFunction = () => ExpressionFunctionDefinition< + 'image', + null, + Arguments, + Promise +>; diff --git a/src/plugins/expression_image/common/types/expression_renderers.ts b/src/plugins/expression_image/common/types/expression_renderers.ts new file mode 100644 index 000000000000000..c4ff7a7f18ae9da --- /dev/null +++ b/src/plugins/expression_image/common/types/expression_renderers.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ImageMode } from './expression_functions'; + +export type OriginString = 'bottom' | 'left' | 'top' | 'right'; + +export interface ImageRendererConfig { + dataurl: string | null; + mode: ImageMode | null; +} + +export interface NodeDimensions { + width: number; + height: number; +} diff --git a/src/plugins/expression_image/common/types/index.ts b/src/plugins/expression_image/common/types/index.ts new file mode 100644 index 000000000000000..ec934e7affe88b4 --- /dev/null +++ b/src/plugins/expression_image/common/types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/expression_image/jest.config.js b/src/plugins/expression_image/jest.config.js new file mode 100644 index 000000000000000..3d5bc9f184c6a47 --- /dev/null +++ b/src/plugins/expression_image/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/expression_image'], +}; diff --git a/src/plugins/expression_image/kibana.json b/src/plugins/expression_image/kibana.json new file mode 100755 index 000000000000000..13b4e989b8f7046 --- /dev/null +++ b/src/plugins/expression_image/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "expressionImage", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["expressions", "presentationUtil"], + "optionalPlugins": [] +} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/image.stories.storyshot b/src/plugins/expression_image/public/expression_renderers/__stories__/__snapshots__/image.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/__snapshots__/image.stories.storyshot rename to src/plugins/expression_image/public/expression_renderers/__stories__/__snapshots__/image.stories.storyshot diff --git a/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx b/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx new file mode 100644 index 000000000000000..d75aa1a4263eba9 --- /dev/null +++ b/src/plugins/expression_image/public/expression_renderers/__stories__/image_renderer.stories.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; +import { storiesOf } from '@storybook/react'; +import { Render, waitFor } from '../../../../presentation_util/public/__stories__'; +import { imageRenderer } from '../image_renderer'; +import { getElasticLogo } from '../../../../../../src/plugins/presentation_util/common/lib'; +import { ImageMode } from '../../../common'; + +const Renderer = ({ elasticLogo }: { elasticLogo: string }) => { + const config = { + dataurl: elasticLogo, + mode: ImageMode.COVER, + }; + + return ; +}; + +storiesOf('renderers/image', module).add( + 'default', + (_, props) => { + return ; + }, + { decorators: [waitFor(getElasticLogo())] } +); diff --git a/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx new file mode 100644 index 000000000000000..3d542a9978a8389 --- /dev/null +++ b/src/plugins/expression_image/public/expression_renderers/image_renderer.tsx @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { i18n } from '@kbn/i18n'; +import { getElasticLogo, isValidUrl } from '../../../presentation_util/public'; +import { ImageRendererConfig } from '../../common/types'; + +const strings = { + getDisplayName: () => + i18n.translate('expressionImage.renderer.image.displayName', { + defaultMessage: 'Image', + }), + getHelpDescription: () => + i18n.translate('expressionImage.renderer.image.helpDescription', { + defaultMessage: 'Render an image', + }), +}; + +export const imageRenderer = (): ExpressionRenderDefinition => ({ + name: 'image', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: ImageRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + const { elasticLogo } = await getElasticLogo(); + const dataurl = isValidUrl(config.dataurl ?? '') ? config.dataurl : elasticLogo; + + const style = { + height: '100%', + backgroundImage: `url(${dataurl})`, + backgroundRepeat: 'no-repeat', + backgroundPosition: 'center center', + backgroundSize: config.mode as string, + }; + + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render(
, domNode, () => handlers.done()); + }, +}); diff --git a/src/plugins/expression_image/public/expression_renderers/index.ts b/src/plugins/expression_image/public/expression_renderers/index.ts new file mode 100644 index 000000000000000..96c274f05a7a94d --- /dev/null +++ b/src/plugins/expression_image/public/expression_renderers/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { imageRenderer } from './image_renderer'; + +export const renderers = [imageRenderer]; + +export { imageRenderer }; diff --git a/src/plugins/expression_image/public/index.ts b/src/plugins/expression_image/public/index.ts new file mode 100755 index 000000000000000..522418640bd1fd9 --- /dev/null +++ b/src/plugins/expression_image/public/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionImagePlugin } from './plugin'; + +export type { ExpressionImagePluginSetup, ExpressionImagePluginStart } from './plugin'; + +export function plugin() { + return new ExpressionImagePlugin(); +} + +export * from './expression_renderers'; diff --git a/src/plugins/expression_image/public/plugin.ts b/src/plugins/expression_image/public/plugin.ts new file mode 100755 index 000000000000000..44feea412163788 --- /dev/null +++ b/src/plugins/expression_image/public/plugin.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; +import { imageRenderer } from './expression_renderers'; +import { imageFunction } from '../common/expression_functions'; + +interface SetupDeps { + expressions: ExpressionsSetup; +} + +interface StartDeps { + expression: ExpressionsStart; +} + +export type ExpressionImagePluginSetup = void; +export type ExpressionImagePluginStart = void; + +export class ExpressionImagePlugin + implements Plugin { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionImagePluginSetup { + expressions.registerFunction(imageFunction); + expressions.registerRenderer(imageRenderer); + } + + public start(core: CoreStart): ExpressionImagePluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_image/server/index.ts b/src/plugins/expression_image/server/index.ts new file mode 100755 index 000000000000000..a4c6ee888d08634 --- /dev/null +++ b/src/plugins/expression_image/server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionImagePlugin } from './plugin'; + +export type { ExpressionImagePluginSetup, ExpressionImagePluginStart } from './plugin'; + +export function plugin() { + return new ExpressionImagePlugin(); +} diff --git a/src/plugins/expression_image/server/plugin.ts b/src/plugins/expression_image/server/plugin.ts new file mode 100755 index 000000000000000..d3259d45107e559 --- /dev/null +++ b/src/plugins/expression_image/server/plugin.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server'; +import { imageFunction } from '../common/expression_functions'; + +interface SetupDeps { + expressions: ExpressionsServerSetup; +} + +interface StartDeps { + expression: ExpressionsServerStart; +} + +export type ExpressionImagePluginSetup = void; +export type ExpressionImagePluginStart = void; + +export class ExpressionImagePlugin + implements Plugin { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionImagePluginSetup { + expressions.registerFunction(imageFunction); + } + + public start(core: CoreStart): ExpressionImagePluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_image/tsconfig.json b/src/plugins/expression_image/tsconfig.json new file mode 100644 index 000000000000000..5fab51496c97e75 --- /dev/null +++ b/src/plugins/expression_image/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "__fixtures__/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" }, + { "path": "../expressions/tsconfig.json" }, + ] +} diff --git a/src/plugins/expression_metric/.storybook/main.js b/src/plugins/expression_metric/.storybook/main.js new file mode 100644 index 000000000000000..742239e638b8ac0 --- /dev/null +++ b/src/plugins/expression_metric/.storybook/main.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line import/no-commonjs +module.exports = require('@kbn/storybook').defaultConfig; diff --git a/src/plugins/expression_metric/README.md b/src/plugins/expression_metric/README.md new file mode 100755 index 000000000000000..ae02ebd51256edb --- /dev/null +++ b/src/plugins/expression_metric/README.md @@ -0,0 +1,9 @@ +# expressionMetric + +Expression Metric plugin adds a `metric` renderer and function to the expression plugin. + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/src/plugins/expression_metric/__fixtures__/function_specs.ts b/src/plugins/expression_metric/__fixtures__/function_specs.ts new file mode 100644 index 000000000000000..3d36840b47e0d24 --- /dev/null +++ b/src/plugins/expression_metric/__fixtures__/function_specs.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { metricFunction } from '../common/expression_functions'; +import { ExpressionFunction } from '../../../../src/plugins/expressions'; + +export const functionSpecs = [metricFunction].map((fn) => new ExpressionFunction(fn())); diff --git a/src/plugins/expression_metric/__fixtures__/index.ts b/src/plugins/expression_metric/__fixtures__/index.ts new file mode 100644 index 000000000000000..048c916b21b2519 --- /dev/null +++ b/src/plugins/expression_metric/__fixtures__/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './function_specs'; diff --git a/src/plugins/expression_metric/common/constants.ts b/src/plugins/expression_metric/common/constants.ts new file mode 100644 index 000000000000000..62753ac728c4187 --- /dev/null +++ b/src/plugins/expression_metric/common/constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const PLUGIN_ID = 'expressionMetric'; +export const PLUGIN_NAME = 'expressionMetric'; + +export const FONT_FAMILY = '`font-family`'; +export const FONT_WEIGHT = '`font-weight`'; +export const CSS = 'CSS'; +export const NUMERALJS = 'Numeral pattern'; diff --git a/src/plugins/expression_metric/common/expression_functions/index.ts b/src/plugins/expression_metric/common/expression_functions/index.ts new file mode 100644 index 000000000000000..53dd9c731168493 --- /dev/null +++ b/src/plugins/expression_metric/common/expression_functions/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { metricFunction } from './metric_function'; + +export const functions = [metricFunction]; + +export { metricFunction }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js b/src/plugins/expression_metric/common/expression_functions/metric_function.test.ts similarity index 57% rename from x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js rename to src/plugins/expression_metric/common/expression_functions/metric_function.test.ts index 3f2d0ad2cb76e7d..88249728526d0d1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.test.js +++ b/src/plugins/expression_metric/common/expression_functions/metric_function.test.ts @@ -1,39 +1,44 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ -import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; -import { fontStyle } from './__fixtures__/test_styles'; -import { metric } from './metric'; +import { ExecutionContext } from 'src/plugins/expressions'; +import { functionWrapper, fontStyle } from '../../../presentation_util/common/lib'; +import { metricFunction } from './metric_function'; describe('metric', () => { - const fn = functionWrapper(metric); + const fn = functionWrapper(metricFunction); it('returns a render as metric', () => { - const result = fn(null); + const result = fn(null, {}, {} as ExecutionContext); expect(result).toHaveProperty('type', 'render'); expect(result).toHaveProperty('as', 'metric'); }); it('sets the metric to context', () => { - const result = fn('2'); + const result = fn('2', {}, {} as ExecutionContext); expect(result.value).toHaveProperty('metric', '2'); }); it(`defaults metric to '?' when context is missing`, () => { - const result = fn(null); + const result = fn(null, {}, {} as ExecutionContext); expect(result.value).toHaveProperty('metric', '?'); }); describe('args', () => { describe('label', () => { it('sets the label of the metric', () => { - const result = fn(null, { - label: 'My Label', - }); + const result = fn( + null, + { + label: 'My Label', + }, + {} as ExecutionContext + ); expect(result.value).toHaveProperty('label', 'My Label'); }); @@ -41,9 +46,13 @@ describe('metric', () => { describe('metricFont', () => { it('sets the font style for the metric', () => { - const result = fn(null, { - metricFont: fontStyle, - }); + const result = fn( + null, + { + metricFont: fontStyle, + }, + {} as ExecutionContext + ); expect(result.value).toHaveProperty('metricFont', fontStyle); }); @@ -54,9 +63,13 @@ describe('metric', () => { describe('labelFont', () => { it('sets the font style for the label', () => { - const result = fn(null, { - labelFont: fontStyle, - }); + const result = fn( + null, + { + labelFont: fontStyle, + }, + {} as ExecutionContext + ); expect(result.value).toHaveProperty('labelFont', fontStyle); }); @@ -67,9 +80,13 @@ describe('metric', () => { describe('metricFormat', () => { it('sets the number format of the metric value', () => { - const result = fn(null, { - metricFormat: '0.0%', - }); + const result = fn( + null, + { + metricFormat: '0.0%', + }, + {} as ExecutionContext + ); expect(result.value).toHaveProperty('metricFormat', '0.0%'); }); diff --git a/src/plugins/expression_metric/common/expression_functions/metric_function.ts b/src/plugins/expression_metric/common/expression_functions/metric_function.ts new file mode 100644 index 000000000000000..184ef37834e01f6 --- /dev/null +++ b/src/plugins/expression_metric/common/expression_functions/metric_function.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import { openSans } from '../../../expressions/common/fonts'; +import { FONT_FAMILY, FONT_WEIGHT, CSS, NUMERALJS } from '../constants'; +import { ExpressionMetricFunction } from '../types'; + +export const strings = { + help: i18n.translate('expressionMetric.functions.metricHelpText', { + defaultMessage: 'Displays a number over a label.', + }), + args: { + label: i18n.translate('expressionMetric.functions.metric.args.labelHelpText', { + defaultMessage: 'The text describing the metric.', + }), + labelFont: i18n.translate('expressionMetric.functions.metric.args.labelFontHelpText', { + defaultMessage: + 'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.', + values: { + CSS, + FONT_FAMILY, + FONT_WEIGHT, + }, + }), + metricFont: i18n.translate('expressionMetric.functions.metric.args.metricFontHelpText', { + defaultMessage: + 'The {CSS} font properties for the metric. For example, {FONT_FAMILY} or {FONT_WEIGHT}.', + values: { + CSS, + FONT_FAMILY, + FONT_WEIGHT, + }, + }), + // TODO: Find a way to generate the docs URL here + metricFormat: i18n.translate('expressionMetric.functions.metric.args.metricFormatHelpText', { + defaultMessage: 'A {NUMERALJS} format string. For example, {example1} or {example2}.', + values: { + example1: '`"0.0a"`', + example2: '`"0%"`', + NUMERALJS, + }, + }), + }, +}; + +export const metricFunction: ExpressionMetricFunction = () => { + const { help, args: argHelp } = strings; + + return { + name: 'metric', + aliases: [], + type: 'render', + inputTypes: ['number', 'string', 'null'], + help, + args: { + label: { + types: ['string'], + aliases: ['_', 'text', 'description'], + help: argHelp.label, + default: '""', + }, + labelFont: { + types: ['style'], + help: argHelp.labelFont, + default: `{font size=14 family="${openSans.value}" color="#000000" align=center}`, + }, + metricFont: { + types: ['style'], + help: argHelp.metricFont, + default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`, + }, + metricFormat: { + types: ['string'], + aliases: ['format'], + help: argHelp.metricFormat, + }, + }, + fn: (input, { label, labelFont, metricFont, metricFormat }) => { + return { + type: 'render', + as: 'metric', + value: { + metric: input === null ? '?' : input, + label, + labelFont, + metricFont, + metricFormat, + }, + }; + }, + }; +}; diff --git a/src/plugins/expression_metric/common/index.ts b/src/plugins/expression_metric/common/index.ts new file mode 100755 index 000000000000000..1b7668c49def502 --- /dev/null +++ b/src/plugins/expression_metric/common/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './constants'; +export * from './types'; +export * from './expression_functions'; diff --git a/src/plugins/expression_metric/common/types/expression_functions.ts b/src/plugins/expression_metric/common/types/expression_functions.ts new file mode 100644 index 000000000000000..69573eec26b8f9f --- /dev/null +++ b/src/plugins/expression_metric/common/types/expression_functions.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { ExpressionFunctionDefinition, ExpressionValueRender, Style } from '../../../expressions'; + +export type Input = number | string | null; + +export interface Arguments { + label: string; + metricFont: Style; + metricFormat: string; + labelFont: Style; +} + +export type ExpressionMetricFunction = () => ExpressionFunctionDefinition< + 'metric', + Input, + Arguments, + ExpressionValueRender +>; diff --git a/src/plugins/expression_metric/common/types/expression_renderers.ts b/src/plugins/expression_metric/common/types/expression_renderers.ts new file mode 100644 index 000000000000000..0f8635edd7f95b9 --- /dev/null +++ b/src/plugins/expression_metric/common/types/expression_renderers.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Style } from '../../../expressions/common'; + +export interface MetricRendererConfig { + /** The text to display under the metric */ + label: string; + /** Font settings for the label */ + labelFont: Style; + /** Value of the metric to display */ + metric: string | number | null; + /** Font settings for the metric */ + metricFont: Style; + /** NumeralJS format string */ + metricFormat: string; +} + +export interface NodeDimensions { + width: number; + height: number; +} diff --git a/src/plugins/expression_metric/common/types/index.ts b/src/plugins/expression_metric/common/types/index.ts new file mode 100644 index 000000000000000..ec934e7affe88b4 --- /dev/null +++ b/src/plugins/expression_metric/common/types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +export * from './expression_functions'; +export * from './expression_renderers'; diff --git a/src/plugins/expression_metric/jest.config.js b/src/plugins/expression_metric/jest.config.js new file mode 100644 index 000000000000000..517409460895e6b --- /dev/null +++ b/src/plugins/expression_metric/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/expression_metric'], +}; diff --git a/src/plugins/expression_metric/kibana.json b/src/plugins/expression_metric/kibana.json new file mode 100755 index 000000000000000..c83a3fcb2668755 --- /dev/null +++ b/src/plugins/expression_metric/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "expressionMetric", + "version": "1.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": true, + "requiredPlugins": ["expressions", "presentationUtil"], + "optionalPlugins": [] +} diff --git a/src/plugins/expression_metric/public/components/index.ts b/src/plugins/expression_metric/public/components/index.ts new file mode 100644 index 000000000000000..cb7865e5f0b8cc5 --- /dev/null +++ b/src/plugins/expression_metric/public/components/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './metric_component'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/metric.tsx b/src/plugins/expression_metric/public/components/metric_component.tsx similarity index 76% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/metric.tsx rename to src/plugins/expression_metric/public/components/metric_component.tsx index b0e2266c6a3605a..9955dc5fa99f89d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/metric.tsx +++ b/src/plugins/expression_metric/public/components/metric_component.tsx @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ import React, { FunctionComponent, CSSProperties } from 'react'; @@ -21,7 +22,7 @@ interface Props { metricFormat?: string; } -export const Metric: FunctionComponent = ({ +const Metric: FunctionComponent = ({ label, metric, labelFont, @@ -39,3 +40,6 @@ export const Metric: FunctionComponent = ({ )}
); + +// eslint-disable-next-line import/no-default-export +export { Metric as default }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/__stories__/__snapshots__/metric.stories.storyshot b/src/plugins/expression_metric/public/expression_renderers/__stories__/__snapshots__/metric.stories.storyshot similarity index 100% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/__stories__/__snapshots__/metric.stories.storyshot rename to src/plugins/expression_metric/public/expression_renderers/__stories__/__snapshots__/metric.stories.storyshot diff --git a/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx b/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx new file mode 100644 index 000000000000000..0e04c32f52ba20a --- /dev/null +++ b/src/plugins/expression_metric/public/expression_renderers/__stories__/metric_renderer.stories.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { CSSProperties } from 'react'; +import { storiesOf } from '@storybook/react'; +import { Style } from 'src/plugins/expressions'; +import { metricRenderer } from '../metric_renderer'; +import { Render } from '../../../../presentation_util/public/__stories__'; +import { MetricRendererConfig } from '../../../common'; + +const labelFontSpec: CSSProperties = { + fontFamily: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", + fontWeight: 'normal', + fontStyle: 'italic', + textDecoration: 'none', + textAlign: 'center', + fontSize: '24px', + lineHeight: '1', + color: '#000000', +}; + +const metricFontSpec: CSSProperties = { + fontFamily: + "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", + fontWeight: 'bold', + fontStyle: 'normal', + textDecoration: 'none', + textAlign: 'center', + fontSize: '48px', + lineHeight: '1', + color: '#b83c6f', +}; + +storiesOf('renderers/Metric', module) + .add('with null metric', () => { + const config: MetricRendererConfig = { + metric: null, + metricFont: {} as Style, + labelFont: {} as Style, + label: '', + metricFormat: '', + }; + return ; + }) + .add('with number metric', () => { + const config: MetricRendererConfig = { + metric: '12345.6789', + metricFont: metricFontSpec as Style, + labelFont: {} as Style, + label: '', + metricFormat: '', + }; + return ; + }) + .add('with string metric', () => { + const config: MetricRendererConfig = { + metric: '$12.34', + metricFont: metricFontSpec as Style, + labelFont: labelFontSpec as Style, + label: '', + metricFormat: '', + }; + return ; + }) + .add('with label', () => { + const config: MetricRendererConfig = { + metric: '$12.34', + metricFont: metricFontSpec as Style, + labelFont: labelFontSpec as Style, + label: 'Average price', + metricFormat: '', + }; + return ; + }) + .add('with number metric and a specified format', () => { + const config: MetricRendererConfig = { + metric: '-0.0024', + metricFont: metricFontSpec as Style, + labelFont: labelFontSpec as Style, + label: 'Average price', + metricFormat: '0.00%', + }; + return ; + }) + .add('with formatted string metric and a specified format', () => { + const config: MetricRendererConfig = { + metric: '$10000000.00', + metricFont: metricFontSpec as Style, + labelFont: labelFontSpec as Style, + label: 'Total Revenue', + metricFormat: '$0a', + }; + return ; + }) + .add('with invalid metricFont', () => { + const config: MetricRendererConfig = { + metric: '$10000000.00', + metricFont: metricFontSpec as Style, + labelFont: labelFontSpec as Style, + label: 'Total Revenue', + metricFormat: '$0a', + }; + return ; + }); diff --git a/src/plugins/expression_metric/public/expression_renderers/index.ts b/src/plugins/expression_metric/public/expression_renderers/index.ts new file mode 100644 index 000000000000000..b77e0bb76f1fdf3 --- /dev/null +++ b/src/plugins/expression_metric/public/expression_renderers/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { metricRenderer } from './metric_renderer'; + +export const renderers = [metricRenderer]; + +export { metricRenderer }; diff --git a/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx b/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx new file mode 100644 index 000000000000000..02c910640edeba9 --- /dev/null +++ b/src/plugins/expression_metric/public/expression_renderers/metric_renderer.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { CSSProperties, lazy } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { ExpressionRenderDefinition, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { i18n } from '@kbn/i18n'; +import { withSuspense } from '../../../presentation_util/public'; +import { MetricRendererConfig } from '../../common/types'; + +const strings = { + getDisplayName: () => + i18n.translate('expressionMetric.renderer.metric.displayName', { + defaultMessage: 'Metric', + }), + getHelpDescription: () => + i18n.translate('expressionMetric.renderer.metric.helpDescription', { + defaultMessage: 'Render a number over a label', + }), +}; + +const LazyMetricComponent = lazy(() => import('../components/metric_component')); +const MetricComponent = withSuspense(LazyMetricComponent); + +export const metricRenderer = (): ExpressionRenderDefinition => ({ + name: 'metric', + displayName: strings.getDisplayName(), + help: strings.getHelpDescription(), + reuseDomNode: true, + render: async ( + domNode: HTMLElement, + config: MetricRendererConfig, + handlers: IInterpreterRenderHandlers + ) => { + handlers.onDestroy(() => { + unmountComponentAtNode(domNode); + }); + + render( + , + domNode, + () => handlers.done() + ); + }, +}); diff --git a/src/plugins/expression_metric/public/index.ts b/src/plugins/expression_metric/public/index.ts new file mode 100755 index 000000000000000..cd3eacaba04ae1f --- /dev/null +++ b/src/plugins/expression_metric/public/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionMetricPlugin } from './plugin'; + +export type { ExpressionMetricPluginSetup, ExpressionMetricPluginStart } from './plugin'; + +export function plugin() { + return new ExpressionMetricPlugin(); +} + +export * from './expression_renderers'; diff --git a/src/plugins/expression_metric/public/plugin.ts b/src/plugins/expression_metric/public/plugin.ts new file mode 100755 index 000000000000000..c11a5a4cdcadec8 --- /dev/null +++ b/src/plugins/expression_metric/public/plugin.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsStart, ExpressionsSetup } from '../../expressions/public'; +import { metricFunction } from '../common/expression_functions'; +import { metricRenderer } from './expression_renderers'; + +interface SetupDeps { + expressions: ExpressionsSetup; +} + +interface StartDeps { + expression: ExpressionsStart; +} + +export type ExpressionMetricPluginSetup = void; +export type ExpressionMetricPluginStart = void; + +export class ExpressionMetricPlugin + implements + Plugin { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionMetricPluginSetup { + expressions.registerFunction(metricFunction); + expressions.registerRenderer(metricRenderer); + } + + public start(core: CoreStart): ExpressionMetricPluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_metric/server/index.ts b/src/plugins/expression_metric/server/index.ts new file mode 100755 index 000000000000000..0caafe1b6926215 --- /dev/null +++ b/src/plugins/expression_metric/server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionMetricPlugin } from './plugin'; + +export type { ExpressionMetricPluginSetup, ExpressionMetricPluginStart } from './plugin'; + +export function plugin() { + return new ExpressionMetricPlugin(); +} diff --git a/src/plugins/expression_metric/server/plugin.ts b/src/plugins/expression_metric/server/plugin.ts new file mode 100755 index 000000000000000..81c87f5c0558096 --- /dev/null +++ b/src/plugins/expression_metric/server/plugin.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsServerStart, ExpressionsServerSetup } from '../../expressions/server'; +import { metricFunction } from '../common'; + +interface SetupDeps { + expressions: ExpressionsServerSetup; +} + +interface StartDeps { + expression: ExpressionsServerStart; +} + +export type ExpressionMetricPluginSetup = void; +export type ExpressionMetricPluginStart = void; + +export class ExpressionMetricPlugin + implements + Plugin { + public setup(core: CoreSetup, { expressions }: SetupDeps): ExpressionMetricPluginSetup { + expressions.registerFunction(metricFunction); + } + + public start(core: CoreStart): ExpressionMetricPluginStart {} + + public stop() {} +} diff --git a/src/plugins/expression_metric/tsconfig.json b/src/plugins/expression_metric/tsconfig.json new file mode 100644 index 000000000000000..5fab51496c97e75 --- /dev/null +++ b/src/plugins/expression_metric/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "__fixtures__/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../presentation_util/tsconfig.json" }, + { "path": "../expressions/tsconfig.json" }, + ] +} diff --git a/src/plugins/expressions/common/executor/__snapshots__/executor.test.ts.snap b/src/plugins/expressions/common/executor/__snapshots__/executor.test.ts.snap new file mode 100644 index 000000000000000..1d4a8614b292168 --- /dev/null +++ b/src/plugins/expressions/common/executor/__snapshots__/executor.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Executor .inject .getAllMigrations returns list of all registered migrations 1`] = ` +Object { + "7.10.0": [Function], + "7.10.1": [Function], +} +`; diff --git a/src/plugins/expressions/common/executor/executor.test.ts b/src/plugins/expressions/common/executor/executor.test.ts index 3c24a3c24e01bc5..d3837272fb50ff0 100644 --- a/src/plugins/expressions/common/executor/executor.test.ts +++ b/src/plugins/expressions/common/executor/executor.test.ts @@ -200,12 +200,21 @@ describe('Executor', () => { }); }); - describe('.migrate', () => { + describe('.getAllMigrations', () => { + test('returns list of all registered migrations', () => { + const migrations = executor.getAllMigrations(); + expect(migrations).toMatchSnapshot(); + }); + }); + + describe('.migrateToLatest', () => { test('calls migrate function for every expression function in expression', () => { - executor.migrate( - parseExpression('foo bar="baz" | foo bar={foo bar="baz" | foo bar={foo bar="baz"}}'), - '7.10.0' - ); + executor.migrateToLatest({ + state: parseExpression( + 'foo bar="baz" | foo bar={foo bar="baz" | foo bar={foo bar="baz"}}' + ), + version: '7.10.0', + }); expect(migrateFn).toBeCalledTimes(5); }); }); diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index 7ca5a005991bd43..ec6ca0323ea5c90 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -21,7 +21,13 @@ import { ExpressionAstExpression, ExpressionAstFunction } from '../ast'; import { ExpressionValueError, typeSpecs } from '../expression_types/specs'; import { getByAlias } from '../util'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableStateService, SerializableState } from '../../../kibana_utils/common'; +import { + MigrateFunctionsObject, + migrateToLatest, + PersistableStateService, + SerializableState, + VersionedState, +} from '../../../kibana_utils/common'; import { ExpressionExecutionParams } from '../service'; export interface ExpressionExecOptions { @@ -244,7 +250,28 @@ export class Executor = Record Object.keys(fn.migrations)) + .flat(1) + ); + + const migrations: MigrateFunctionsObject = {}; + uniqueVersions.forEach((version) => { + migrations[version] = (state) => ({ + ...this.migrate(state, version), + }); + }); + + return migrations; + } + + public migrateToLatest(state: VersionedState) { + return migrateToLatest(this.getAllMigrations(), state) as ExpressionAstExpression; + } + + private migrate(ast: SerializableState, version: string) { return this.walkAst(cloneDeep(ast) as ExpressionAstExpression, (fn, link) => { if (!fn.migrations[version]) return link; const updatedAst = fn.migrations[version](link) as ExpressionAstFunction; diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index 7802617f3a251b2..cd52c8c3239de1f 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -18,7 +18,11 @@ import { ExecutionContract, ExecutionResult } from '../execution'; import { AnyExpressionTypeDefinition, ExpressionValueError } from '../expression_types'; import { AnyExpressionFunctionDefinition } from '../expression_functions'; import { SavedObjectReference } from '../../../../core/types'; -import { PersistableStateService, SerializableState } from '../../../kibana_utils/common'; +import { + PersistableStateService, + SerializableState, + VersionedState, +} from '../../../kibana_utils/common'; import { Adapters } from '../../../inspector/common/adapters'; import { clog, @@ -323,13 +327,18 @@ export class ExpressionsService implements PersistableStateService { + return this.executor.getAllMigrations(); + }; + + /** + * migrates an old expression to latest version + * @param state */ - public readonly migrate = (state: SerializableState, version: string) => { - return this.executor.migrate(state, version); + public migrateToLatest = (state: VersionedState) => { + return this.executor.migrateToLatest(state); }; /** diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index 91906b67615e336..67c69f76be914a7 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -213,6 +213,10 @@ export class Executor = Record; // @deprecated (undocumented) readonly functions: FunctionsRegistry; + // Warning: (ae-forgotten-export) The symbol "MigrateFunctionsObject" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getAllMigrations(): MigrateFunctionsObject; // (undocumented) getFunction(name: string): ExpressionFunction | undefined; // (undocumented) @@ -225,10 +229,10 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) @@ -599,6 +603,7 @@ export class ExpressionsService implements PersistableStateService ExpressionsService; + getAllMigrations: () => import("../../../kibana_utils/common").MigrateFunctionsObject; // (undocumented) readonly getFunction: ExpressionsServiceStart['getFunction']; readonly getFunctions: () => ReturnType; @@ -609,7 +614,7 @@ export class ExpressionsService implements PersistableStateService ReturnType; readonly inject: (state: ExpressionAstExpression, references: SavedObjectReference[]) => ExpressionAstExpression; - readonly migrate: (state: SerializableState, version: string) => ExpressionAstExpression; + migrateToLatest: (state: VersionedState) => ExpressionAstExpression; readonly registerFunction: (functionDefinition: AnyExpressionFunctionDefinition | (() => AnyExpressionFunctionDefinition)) => void; // (undocumented) readonly registerRenderer: (definition: AnyExpressionRenderDefinition | (() => AnyExpressionRenderDefinition)) => void; @@ -1188,6 +1193,7 @@ export type UnmappedTypeStrings = 'date' | 'filter'; // Warnings were encountered during analysis: // // src/plugins/expressions/common/ast/types.ts:29:3 - (ae-forgotten-export) The symbol "ExpressionAstFunctionDebug" needs to be exported by the entry point index.d.ts +// src/plugins/expressions/common/expression_functions/expression_function.ts:68:5 - (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // src/plugins/expressions/common/expression_types/specs/error.ts:20:5 - (ae-forgotten-export) The symbol "ErrorLike" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index cbe134548f8e6a4..1783c6c72c6863a 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -195,6 +195,10 @@ export class Executor = Record; // @deprecated (undocumented) readonly functions: FunctionsRegistry; + // Warning: (ae-forgotten-export) The symbol "MigrateFunctionsObject" needs to be exported by the entry point index.d.ts + // + // (undocumented) + getAllMigrations(): MigrateFunctionsObject; // (undocumented) getFunction(name: string): ExpressionFunction | undefined; // (undocumented) @@ -207,10 +211,10 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) @@ -943,6 +947,7 @@ export type UnmappedTypeStrings = 'date' | 'filter'; // Warnings were encountered during analysis: // // src/plugins/expressions/common/ast/types.ts:29:3 - (ae-forgotten-export) The symbol "ExpressionAstFunctionDebug" needs to be exported by the entry point index.d.ts +// src/plugins/expressions/common/expression_functions/expression_function.ts:68:5 - (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // src/plugins/expressions/common/expression_types/specs/error.ts:20:5 - (ae-forgotten-export) The symbol "ErrorLike" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.test.ts b/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.test.ts index 2ae376e787d2f7e..32fb652d41632df 100644 --- a/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.test.ts +++ b/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.test.ts @@ -50,14 +50,11 @@ test('returns the same object if there are no migrations to be applied', () => { } ); - expect(migrated).toEqual({ - state: { name: 'Foo' }, - version: '0.0.1', - }); + expect(migrated).toEqual({ name: 'Foo' }); }); test('applies a single migration', () => { - const { state: newState, version: newVersion } = migrateToLatest( + const newState = migrateToLatest( { '0.0.2': (migrationV2 as unknown) as MigrateFunction, }, @@ -71,11 +68,10 @@ test('applies a single migration', () => { firstName: 'Foo', lastName: '', }); - expect(newVersion).toEqual('0.0.2'); }); test('does not apply migration if it has the same version as state', () => { - const { state: newState, version: newVersion } = migrateToLatest( + const newState = migrateToLatest( { '0.0.54': (migrationV2 as unknown) as MigrateFunction, }, @@ -88,11 +84,10 @@ test('does not apply migration if it has the same version as state', () => { expect(newState).toEqual({ name: 'Foo', }); - expect(newVersion).toEqual('0.0.54'); }); test('does not apply migration if it has lower version', () => { - const { state: newState, version: newVersion } = migrateToLatest( + const newState = migrateToLatest( { '0.2.2': (migrationV2 as unknown) as MigrateFunction, }, @@ -105,11 +100,10 @@ test('does not apply migration if it has lower version', () => { expect(newState).toEqual({ name: 'Foo', }); - expect(newVersion).toEqual('0.3.1'); }); test('applies two migrations consecutively', () => { - const { state: newState, version: newVersion } = migrateToLatest( + const newState = migrateToLatest( { '7.14.0': (migrationV2 as unknown) as MigrateFunction, '7.14.2': (migrationV3 as unknown) as MigrateFunction, @@ -126,11 +120,10 @@ test('applies two migrations consecutively', () => { isAdmin: false, age: 0, }); - expect(newVersion).toEqual('7.14.2'); }); test('applies only migrations which are have higher semver version', () => { - const { state: newState, version: newVersion } = migrateToLatest( + const newState = migrateToLatest( { '7.14.0': (migrationV2 as unknown) as MigrateFunction, // not applied '7.14.1': (() => ({})) as MigrateFunction, // not applied @@ -148,5 +141,4 @@ test('applies only migrations which are have higher semver version', () => { isAdmin: false, age: 0, }); - expect(newVersion).toEqual('7.14.2'); }); diff --git a/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.ts b/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.ts index c16392164e3e4af..6f81d0a7b9b634f 100644 --- a/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.ts +++ b/src/plugins/kibana_utils/common/persistable_state/migrate_to_latest.ts @@ -12,19 +12,16 @@ import { SerializableState, VersionedState, MigrateFunctionsObject } from './typ export function migrateToLatest( migrations: MigrateFunctionsObject, { state, version: oldVersion }: VersionedState -): VersionedState { +): S { const versions = Object.keys(migrations || {}) .filter((v) => compare(v, oldVersion) > 0) .sort(compare); - if (!versions.length) return { state, version: oldVersion } as VersionedState; + if (!versions.length) return state as S; for (const version of versions) { state = migrations[version]!(state); } - return { - state: state as S, - version: versions[versions.length - 1], - }; + return state as S; } diff --git a/src/plugins/kibana_utils/common/persistable_state/types.ts b/src/plugins/kibana_utils/common/persistable_state/types.ts index 8d808dcfec2a4be..a2d1751297a9f33 100644 --- a/src/plugins/kibana_utils/common/persistable_state/types.ts +++ b/src/plugins/kibana_utils/common/persistable_state/types.ts @@ -174,5 +174,5 @@ export interface PersistableStateService

MigrateFunctionsObject; + getAllMigrations: () => MigrateFunctionsObject; } diff --git a/src/plugins/presentation_util/common/lib/test_helpers/index.ts b/src/plugins/presentation_util/common/lib/test_helpers/index.ts index a6ea8da6ac6e918..486df287bf16bad 100644 --- a/src/plugins/presentation_util/common/lib/test_helpers/index.ts +++ b/src/plugins/presentation_util/common/lib/test_helpers/index.ts @@ -7,3 +7,4 @@ */ export * from './function_wrapper'; +export * from './test_styles'; diff --git a/src/plugins/presentation_util/common/lib/test_helpers/test_styles.ts b/src/plugins/presentation_util/common/lib/test_helpers/test_styles.ts new file mode 100644 index 000000000000000..99b7ce6c38d2b06 --- /dev/null +++ b/src/plugins/presentation_util/common/lib/test_helpers/test_styles.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const fontStyle = { + type: 'style', + spec: { + fontFamily: 'Chalkboard, serif', + fontWeight: 'bolder', + fontStyle: 'normal', + textDecoration: 'underline', + color: 'pink', + textAlign: 'center', + fontSize: '14px', + lineHeight: '21px', + }, + css: + 'font-family:Chalkboard, serif;font-weight:bolder;font-style:normal;text-decoration:underline;color:pink;text-align:center;font-size:14px;line-height:21px', +}; diff --git a/src/plugins/presentation_util/public/services/create/factory.ts b/src/plugins/presentation_util/public/services/create/factory.ts index 4e2d1dd291073dd..ddc2e5845b03751 100644 --- a/src/plugins/presentation_util/public/services/create/factory.ts +++ b/src/plugins/presentation_util/public/services/create/factory.ts @@ -7,7 +7,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { CoreStart, AppUpdater } from 'src/core/public'; +import { CoreStart, AppUpdater, PluginInitializerContext } from 'src/core/public'; /** * A factory function for creating a service. @@ -28,6 +28,7 @@ export interface KibanaPluginServiceParams { coreStart: CoreStart; startPlugins: Start; appUpdater?: BehaviorSubject; + initContext?: PluginInitializerContext; } /** diff --git a/src/plugins/share/public/url_service/redirect/redirect_manager.ts b/src/plugins/share/public/url_service/redirect/redirect_manager.ts index 6148249f5a047a9..ad99be43f678a44 100644 --- a/src/plugins/share/public/url_service/redirect/redirect_manager.ts +++ b/src/plugins/share/public/url_service/redirect/redirect_manager.ts @@ -68,7 +68,7 @@ export class RedirectManager { throw error; } - const { state: migratedParams } = migrateToLatest(locator.migrations, { + const migratedParams = migrateToLatest(locator.migrations, { state: options.params, version: options.version, }); diff --git a/src/plugins/usage_collection/public/components/track_application_view/track_application_view.test.tsx b/src/plugins/usage_collection/public/components/track_application_view/track_application_view.test.tsx index 23072dd0875517f..1cb32280d52f670 100644 --- a/src/plugins/usage_collection/public/components/track_application_view/track_application_view.test.tsx +++ b/src/plugins/usage_collection/public/components/track_application_view/track_application_view.test.tsx @@ -7,22 +7,21 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; import { ApplicationUsageContext, TrackApplicationView } from './track_application_view'; import { IApplicationUsageTracker } from '../../plugin'; -import { fireEvent } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; describe('TrackApplicationView', () => { test('it renders the internal component even when no tracker has been set', () => { - const component = mountWithIntl( + const { unmount } = render(

Hello

); - component.unmount(); + unmount(); }); - test('it tracks the component while it is rendered', () => { + test('it tracks the component while it is rendered', async () => { const applicationUsageTrackerMock: jest.Mocked = { trackApplicationViewUsage: jest.fn(), flushTrackedView: jest.fn(), @@ -30,7 +29,7 @@ describe('TrackApplicationView', () => { }; expect(applicationUsageTrackerMock.trackApplicationViewUsage).not.toHaveBeenCalled(); const viewId = 'testView'; - const component = mountWithIntl( + const { findByText, unmount } = render(

Hello

@@ -39,10 +38,11 @@ describe('TrackApplicationView', () => { ); expect(applicationUsageTrackerMock.trackApplicationViewUsage).toHaveBeenCalledWith(viewId); expect(applicationUsageTrackerMock.updateViewClickCounter).not.toHaveBeenCalled(); - fireEvent.click(component.getDOMNode()); + const element = await findByText('Hello'); + fireEvent.click(element); expect(applicationUsageTrackerMock.updateViewClickCounter).toHaveBeenCalledWith(viewId); expect(applicationUsageTrackerMock.flushTrackedView).not.toHaveBeenCalled(); - component.unmount(); + unmount(); expect(applicationUsageTrackerMock.flushTrackedView).toHaveBeenCalledWith(viewId); }); }); diff --git a/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.test.tsx b/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.test.tsx index c4e2752d3349b11..3131f2d52763724 100644 --- a/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.test.tsx +++ b/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.test.tsx @@ -7,22 +7,21 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; import { TrackApplicationViewComponent } from './track_application_view_component'; import { IApplicationUsageTracker } from '../../plugin'; -import { fireEvent } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; describe('TrackApplicationViewComponent', () => { test('it renders the internal component even when no tracker is provided', () => { - const component = mountWithIntl( + const { unmount } = render(

Hello

); - component.unmount(); + unmount(); }); - test('it tracks the component while it is rendered', () => { + test('it tracks the component while it is rendered', async () => { const applicationUsageTrackerMock: jest.Mocked = { trackApplicationViewUsage: jest.fn(), flushTrackedView: jest.fn(), @@ -30,7 +29,7 @@ describe('TrackApplicationViewComponent', () => { }; expect(applicationUsageTrackerMock.trackApplicationViewUsage).not.toHaveBeenCalled(); const viewId = 'testView'; - const component = mountWithIntl( + const { findByText, unmount } = render( { ); expect(applicationUsageTrackerMock.trackApplicationViewUsage).toHaveBeenCalledWith(viewId); expect(applicationUsageTrackerMock.updateViewClickCounter).not.toHaveBeenCalled(); - fireEvent.click(component.getDOMNode()); + const element = await findByText('Hello'); + fireEvent.click(element); expect(applicationUsageTrackerMock.updateViewClickCounter).toHaveBeenCalledWith(viewId); expect(applicationUsageTrackerMock.flushTrackedView).not.toHaveBeenCalled(); - component.unmount(); + unmount(); expect(applicationUsageTrackerMock.flushTrackedView).toHaveBeenCalledWith(viewId); }); }); diff --git a/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.tsx b/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.tsx index f18b37df484fc77..a1feb93b55db284 100644 --- a/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.tsx +++ b/src/plugins/usage_collection/public/components/track_application_view/track_application_view_component.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Component } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import { IApplicationUsageTracker } from '../../plugin'; import { TrackApplicationViewProps } from './types'; @@ -15,17 +15,22 @@ interface Props extends TrackApplicationViewProps { applicationUsageTracker?: IApplicationUsageTracker; } -export class TrackApplicationViewComponent extends Component { - onClick = () => { +export class TrackApplicationViewComponent extends React.Component { + private parentNode: (Node & ParentNode) | null | undefined; + + onClick = (e: MouseEvent) => { const { applicationUsageTracker, viewId } = this.props; - applicationUsageTracker?.updateViewClickCounter(viewId); + this.parentNode = this.parentNode || ReactDOM.findDOMNode(this)?.parentNode; + if (this.parentNode === e.target || this.parentNode?.contains(e.target as Node | null)) { + applicationUsageTracker?.updateViewClickCounter(viewId); + } }; componentDidMount() { const { applicationUsageTracker, viewId } = this.props; if (applicationUsageTracker) { applicationUsageTracker.trackApplicationViewUsage(viewId); - ReactDOM.findDOMNode(this)?.parentNode?.addEventListener('click', this.onClick); + document.addEventListener('click', this.onClick); } } @@ -33,8 +38,8 @@ export class TrackApplicationViewComponent extends Component { const { applicationUsageTracker, viewId } = this.props; if (applicationUsageTracker) { applicationUsageTracker.flushTrackedView(viewId); - ReactDOM.findDOMNode(this)?.parentNode?.removeEventListener('click', this.onClick); } + document.removeEventListener('click', this.onClick); } render() { diff --git a/src/plugins/vis_type_pie/public/utils/get_legend_actions.tsx b/src/plugins/vis_type_pie/public/utils/get_legend_actions.tsx index 9f1d5e0db4583ab..19164d0aefe5286 100644 --- a/src/plugins/vis_type_pie/public/utils/get_legend_actions.tsx +++ b/src/plugins/vis_type_pie/public/utils/get_legend_actions.tsx @@ -81,6 +81,9 @@ export const getLegendActions = ( const Button = (
undefined} + onKeyPress={() => setPopoverOpen(!popoverOpen)} onClick={() => setPopoverOpen(!popoverOpen)} > diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts index a601da234e07829..f8dd206f8f4d5ef 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts @@ -44,7 +44,7 @@ describe('extractIndexPatterns', () => { }); test('should return index patterns', () => { - expect(extractIndexPatternValues(panel, null)).toEqual([ + expect(extractIndexPatternValues(panel, undefined)).toEqual([ '*', 'example-1-*', 'example-2-*', diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts index 1224fd33daee347..1a8c277efbf7c50 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts @@ -8,7 +8,7 @@ import { uniq } from 'lodash'; import type { Panel, IndexPatternValue, FetchedIndexPattern } from '../common/types'; -import { IIndexPattern, IndexPatternsService } from '../../data/common'; +import { IndexPatternsService } from '../../data/common'; export const isStringTypeIndexPattern = ( indexPatternValue: IndexPatternValue @@ -17,31 +17,27 @@ export const isStringTypeIndexPattern = ( export const getIndexPatternKey = (indexPatternValue: IndexPatternValue) => isStringTypeIndexPattern(indexPatternValue) ? indexPatternValue : indexPatternValue?.id ?? ''; -export const extractIndexPatternValues = (panel: Panel, defaultIndex: IIndexPattern | null) => { +export const extractIndexPatternValues = (panel: Panel, defaultIndexId?: string) => { const patterns: IndexPatternValue[] = []; - if (panel.index_pattern) { - patterns.push(panel.index_pattern); - } + const addIndex = (value?: IndexPatternValue) => { + if (value) { + patterns.push(value); + } else if (defaultIndexId) { + patterns.push({ id: defaultIndexId }); + } + }; + + addIndex(panel.index_pattern); panel.series.forEach((series) => { - const indexPattern = series.series_index_pattern; - if (indexPattern && series.override_index_pattern) { - patterns.push(indexPattern); + if (series.override_index_pattern) { + addIndex(series.series_index_pattern); } }); if (panel.annotations) { - panel.annotations.forEach((item) => { - const indexPattern = item.index_pattern; - if (indexPattern) { - patterns.push(indexPattern); - } - }); - } - - if (patterns.length === 0 && defaultIndex?.id) { - patterns.push({ id: defaultIndex.id }); + panel.annotations.forEach((item) => addIndex(item.index_pattern)); } return uniq(patterns).sort(); diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx index 715cf4d6709dae5..cc737561bedac3c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx @@ -26,7 +26,7 @@ import { KBN_FIELD_TYPES, Query } from '../../../../../plugins/data/public'; import { AddDeleteButtons } from './add_delete_buttons'; import { ColorPicker } from './color_picker'; import { FieldSelect } from './aggs/field_select'; -import { IndexPatternSelect } from './lib/index_pattern_select'; +import { IndexPatternSelect, IndexPatternSelectProps } from './lib/index_pattern_select'; import { QueryBarWrapper } from './query_bar_wrapper'; import { YesNo } from './yes_no'; import { fetchIndexPattern } from '../../../common/index_patterns_utils'; @@ -35,7 +35,7 @@ import { getDefaultQueryLanguage } from './lib/get_default_query_language'; // @ts-expect-error not typed yet import { IconSelect } from './icon_select/icon_select'; -import type { Annotation, FetchedIndexPattern, IndexPatternValue } from '../../../common/types'; +import type { Annotation, IndexPatternValue } from '../../../common/types'; import type { VisFields } from '../lib/fetch_fields'; const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; @@ -68,20 +68,28 @@ export const AnnotationRow = ({ const model = useMemo(() => ({ ...getAnnotationDefaults(), ...annotation }), [annotation]); const htmlId = htmlIdGenerator(model.id); - const [fetchedIndex, setFetchedIndex] = useState(null); + const [fetchedIndex, setFetchedIndex] = useState(null); useEffect(() => { const updateFetchedIndex = async (index: IndexPatternValue) => { const { indexPatterns } = getDataStart(); + let fetchedIndexPattern: IndexPatternSelectProps['fetchedIndex'] = { + indexPattern: undefined, + indexPatternString: undefined, + }; - setFetchedIndex( - index + try { + fetchedIndexPattern = index ? await fetchIndexPattern(index, indexPatterns) : { - indexPattern: undefined, - indexPatternString: undefined, - } - ); + ...fetchedIndexPattern, + defaultIndex: await indexPatterns.getDefault(), + }; + } catch { + // nothing to be here + } + + setFetchedIndex(fetchedIndexPattern); }; updateFetchedIndex(model.index_pattern); @@ -124,7 +132,6 @@ export const AnnotationRow = ({ @@ -233,7 +241,12 @@ export const AnnotationRow = ({ } fullWidth > - + @@ -260,6 +273,7 @@ export const AnnotationRow = ({ onChange={handleChange('template')} value={model.template} fullWidth + data-test-subj="annotationRowTemplateInput" /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx index b3b4993d2ca061d..b3683602aa345f0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx @@ -41,7 +41,7 @@ const NoContent = ({ handleAdd }: { handleAdd: () => void }) => ( defaultMessage="Click the button below to create an annotation data source." />

- + { @@ -144,17 +139,25 @@ export const IndexPattern = ({ useEffect(() => { async function fetchIndex() { const { indexPatterns } = getDataStart(); + let fetchedIndexPattern = { + indexPattern: undefined, + indexPatternString: undefined, + }; - setFetchedIndex( - index + try { + fetchedIndexPattern = index ? await fetchIndexPattern(index, indexPatterns, { fetchKibanaIndexForStringIndexes: true, }) : { - indexPattern: undefined, - indexPatternString: undefined, - } - ); + ...fetchedIndexPattern, + defaultIndex: await indexPatterns.getDefault(), + }; + } catch { + // nothing to be here + } + + setFetchedIndex(fetchedIndexPattern); } fetchIndex(); @@ -165,16 +168,6 @@ export const IndexPattern = ({ [model.hide_last_value_indicator, onChange] ); - const getTimefieldPlaceholder = () => { - if (!model[indexPatternName]) { - return defaultIndex?.timeFieldName; - } - - if (useKibanaIndices) { - return fetchedIndex?.indexPattern?.timeFieldName ?? undefined; - } - }; - return (
{!isTimeSeries && ( @@ -245,7 +238,6 @@ export const IndexPattern = ({ diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts index 584f13e7a025b7c..4920677a04a2eef 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { IndexPatternSelect } from './index_pattern_select'; +export { IndexPatternSelect, IndexPatternSelectProps } from './index_pattern_select'; diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx index 07edfc2e6e0d706..927b3c608c16c89 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/index_pattern_select.tsx @@ -14,22 +14,23 @@ import { EuiFormRow, EuiText, EuiLink, htmlIdGenerator } from '@elastic/eui'; import { getCoreStart } from '../../../../services'; import { PanelModelContext } from '../../../contexts/panel_model_context'; -import { isStringTypeIndexPattern } from '../../../../../common/index_patterns_utils'; - import { FieldTextSelect } from './field_text_select'; import { ComboBoxSelect } from './combo_box_select'; import type { IndexPatternValue, FetchedIndexPattern } from '../../../../../common/types'; -import { DefaultIndexPatternContext } from '../../../contexts/default_index_context'; import { USE_KIBANA_INDEXES_KEY } from '../../../../../common/constants'; +import { IndexPattern } from '../../../../../../data/common'; -interface IndexPatternSelectProps { - value: IndexPatternValue; +export interface IndexPatternSelectProps { indexPatternName: string; onChange: Function; disabled?: boolean; allowIndexSwitchingMode?: boolean; - fetchedIndex: FetchedIndexPattern | null; + fetchedIndex: + | (FetchedIndexPattern & { + defaultIndex?: IndexPattern | null; + }) + | null; } const defaultIndexPatternHelpText = i18n.translate( @@ -51,7 +52,6 @@ const indexPatternLabel = i18n.translate('visTypeTimeseries.indexPatternSelect.l }); export const IndexPatternSelect = ({ - value, indexPatternName, onChange, disabled, @@ -60,7 +60,6 @@ export const IndexPatternSelect = ({ }: IndexPatternSelectProps) => { const htmlId = htmlIdGenerator(); const panelModel = useContext(PanelModelContext); - const defaultIndex = useContext(DefaultIndexPatternContext); const useKibanaIndices = Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY]); const Component = useKibanaIndices ? ComboBoxSelect : FieldTextSelect; @@ -105,13 +104,11 @@ export const IndexPatternSelect = ({ id={htmlId('indexPattern')} label={indexPatternLabel} helpText={ - !value && defaultIndexPatternHelpText + (!useKibanaIndices ? queryAllIndexesHelpText : '') + fetchedIndex.defaultIndex && + defaultIndexPatternHelpText + (!useKibanaIndices ? queryAllIndexesHelpText : '') } labelAppend={ - value && - allowIndexSwitchingMode && - isStringTypeIndexPattern(value) && - !fetchedIndex.indexPattern ? ( + fetchedIndex.indexPatternString && !fetchedIndex.indexPattern ? ( diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts index 93b15402e3c24ba..18288f75d4c90aa 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/index_pattern_select/types.ts @@ -7,10 +7,13 @@ */ import type { Assign } from '@kbn/utility-types'; import type { FetchedIndexPattern, IndexPatternValue } from '../../../../../common/types'; +import type { IndexPattern } from '../../../../../../data/common'; /** @internal **/ export interface SelectIndexComponentProps { - fetchedIndex: FetchedIndexPattern; + fetchedIndex: FetchedIndexPattern & { + defaultIndex?: IndexPattern | null; + }; onIndexChange: (value: IndexPatternValue) => void; onModeChange: (useKibanaIndexes: boolean, index?: FetchedIndexPattern) => void; 'data-test-subj': string; diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index e1d94f3bf3ebeff..cdad8c1aeff4b11 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -208,6 +208,7 @@ export class TimeseriesPanelConfig extends Component< this.props.onChange({ filter }); }} indexPatterns={[model.index_pattern]} + data-test-subj="panelFilterQueryBar" /> @@ -442,6 +443,7 @@ export class TimeseriesPanelConfig extends Component< this.switchTab(PANEL_CONFIG_TABS.ANNOTATIONS)} + data-test-subj="timeSeriesEditorAnnotationsBtn" > ([]); const coreStartContext = useContext(CoreStartContext); - const defaultIndex = useContext(DefaultIndexPatternContext); useEffect(() => { async function fetchIndexes() { @@ -48,15 +46,19 @@ export function QueryBarWrapper({ i.push(indexPattern); } } - } else if (defaultIndex) { - i.push(defaultIndex); + } else { + const defaultIndex = await indexPatternsService.getDefault(); + + if (defaultIndex) { + i.push(defaultIndex); + } } } setIndexes(i); } fetchIndexes(); - }, [indexPatterns, indexPatternsService, defaultIndex]); + }, [indexPatterns, indexPatternsService]); return ( onChange({ filter })} indexPatterns={[indexPatternForQuery]} + data-test-subj="seriesConfigQueryBar" /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js b/src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js index 2b4324f95adad33..739988b0c21070c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/filter_items.js @@ -73,6 +73,7 @@ class FilterItemsUi extends Component { }} onChange={(query) => this.handleQueryChange(model, query)} indexPatterns={[indexPatterns]} + data-test-subj="filterItemsQueryBar" /> @@ -88,6 +89,7 @@ class FilterItemsUi extends Component { onChange={this.handleChange(model, 'label')} value={model.label} fullWidth + data-test-subj="filterItemsLabel" /> @@ -96,6 +98,7 @@ class FilterItemsUi extends Component { onDelete={handleDelete} disableDelete={items.length < 2} responsive={false} + testSubj="filterRow" /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js b/src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js index 7381e66adcd0c7f..68367b0ea55ebd8 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/group_by_select.js @@ -61,6 +61,7 @@ function GroupBySelectUi(props) { selectedOptions={[selectedOption]} onChange={props.onChange} singleSelection={{ asPlainText: true }} + data-test-subj="groupBySelect" /> ); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx index 424b39feff83643..4faa0110b971130 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.tsx @@ -19,10 +19,9 @@ import { VisualizeEmbeddableContract, } from '../../../../../plugins/visualizations/public'; import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -import { DefaultIndexPatternContext } from '../contexts/default_index_context'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import type { IIndexPattern, TimeRange } from '../../../../../plugins/data/public'; +import type { TimeRange } from '../../../../../plugins/data/public'; import type { IndexPatternValue, TimeseriesVisData } from '../../../common/types'; // @ts-expect-error @@ -51,7 +50,6 @@ export interface TimeseriesEditorProps { interface TimeseriesEditorState { autoApply: boolean; dirty: boolean; - defaultIndex: IIndexPattern | null; extractedIndexPatterns: IndexPatternValue[]; model: TimeseriesVisParams; visFields?: VisFields; @@ -69,7 +67,6 @@ export class VisEditor extends Component { this.setState({ @@ -180,53 +179,46 @@ export class VisEditor extends Component - -
- {!this.props.vis.params.use_kibana_indexes && } -
- -
- + {!this.props.vis.params.use_kibana_indexes && } +
+ +
+ +
+ -
- -
- +
); } - componentDidMount() { - const dataStart = getDataStart(); - - dataStart.indexPatterns.getDefault().then(async (index) => { - const indexPatterns = extractIndexPatternValues(this.props.vis.params, index); - const visFields = await fetchFields(indexPatterns); + async componentDidMount() { + const indexPatterns = extractIndexPatternValues(this.props.vis.params, this.getDefaultIndex()); + const visFields = await fetchFields(indexPatterns); - this.setState({ - defaultIndex: index, - visFields, - }); + this.setState({ + visFields, }); this.props.eventEmitter.on('updateEditor', this.updateModel); @@ -236,6 +228,10 @@ export class VisEditor extends Component('defaultIndex') ?? ''; + } } // default export required for React.Lazy diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js index 6f4b0c4cf56cb0d..81b8626261f4367 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/timeseries/config.js @@ -159,6 +159,7 @@ export const TimeseriesConfig = injectI18n(function (props) { selectedOptions={selectedChartTypeOption ? [selectedChartTypeOption] : []} onChange={handleSelectChange('chart_type')} singleSelection={{ asPlainText: true }} + data-test-subj="seriesChartTypeComboBox" /> @@ -261,6 +262,7 @@ export const TimeseriesConfig = injectI18n(function (props) { selectedOptions={selectedChartTypeOption ? [selectedChartTypeOption] : []} onChange={handleSelectChange('chart_type')} singleSelection={{ asPlainText: true }} + data-test-subj="seriesChartTypeComboBox" /> @@ -530,6 +532,7 @@ export const TimeseriesConfig = injectI18n(function (props) { value={model.override_index_pattern} name="override_index_pattern" onChange={props.onChange} + data-test-subj="seriesOverrideIndexPattern" /> diff --git a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts index 7b31ff3296425ef..71e38be30257947 100644 --- a/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts +++ b/src/plugins/vis_type_timeseries/public/application/lib/fetch_fields.ts @@ -22,9 +22,9 @@ export async function fetchFields( const patterns = Array.isArray(indexes) ? indexes : [indexes]; const coreStart = getCoreStart(); const dataStart = getDataStart(); + const defaultIndex = coreStart.uiSettings.get('defaultIndex'); try { - const defaultIndexPattern = await dataStart.indexPatterns.getDefault(); const indexFields = await Promise.all( patterns.map(async (pattern) => { if (typeof pattern !== 'string' && pattern?.id) { @@ -42,17 +42,14 @@ export async function fetchFields( }) ); - const fields: VisFields = patterns.reduce( - (cumulatedFields, currentPattern, index) => ({ + const fields: VisFields = patterns.reduce((cumulatedFields, currentPattern, index) => { + const key = getIndexPatternKey(currentPattern); + return { ...cumulatedFields, - [getIndexPatternKey(currentPattern)]: indexFields[index], - }), - {} - ); - - if (defaultIndexPattern) { - fields[''] = toSanitizedFieldType(await defaultIndexPattern.getNonScriptedFields()); - } + [key]: indexFields[index], + ...(key === defaultIndex ? { '': indexFields[index] } : {}), + }; + }, {}); return fields; } catch (error) { 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 ed62c0909e51ba5..76bf3ca9d1b6eec 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 @@ -143,6 +143,7 @@ export const TimeSeries = ({ return ( undefined} + onKeyPress={() => setPopoverOpen(!popoverOpen)} onClick={() => setPopoverOpen(!popoverOpen)} > diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index c82036449d17384..9a0adf265e5a23d 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -136,6 +136,9 @@ export class VisualizeEmbeddable this.deps = deps; this.timefilter = timefilter; this.syncColors = this.input.syncColors; + this.searchSessionId = this.input.searchSessionId; + this.query = this.input.query; + this.vis = vis; this.vis.uiState.on('change', this.uiStateChangeHandler); this.vis.uiState.on('reload', this.reload); @@ -149,7 +152,7 @@ export class VisualizeEmbeddable } this.subscriptions.push( - this.getUpdated$().subscribe((value) => { + this.getInput$().subscribe(() => { const isDirty = this.handleChanges(); if (isDirty && this.handler) { diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index cc57d5834818069..df6a879f5a0a929 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -185,6 +185,253 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(hasMachineRawFilter).to.be(true); }); }); + + describe('Elastic charts', () => { + beforeEach(async () => { + await visualBuilder.toggleNewChartsLibraryWithDebug(true); + await visualBuilder.clickPanelOptions('timeSeries'); + await visualBuilder.setIntervalValue('12h'); + await visualBuilder.clickDataTab('timeSeries'); + }); + + it('should display correct chart data for average aggregation', async () => { + const expectedChartData = [ + [1442707200000, 5765.324917218543], + [1442750400000, 5635.074754378471], + [1442793600000, 5798.3942307692305], + [1442836800000, 5721.522355975924], + [1442880000000, 5639.770887166236], + ]; + await visualBuilder.selectAggType('Average'); + await visualBuilder.setFieldForAggregation('bytes'); + + const chartData = await visualBuilder.getAreaChartData(); + expect(chartData).to.eql(expectedChartData); + }); + + it('should display correct chart data for percentile aggregation', async () => { + const expectedChartData = [ + [1442707200000, 157580], + [1442750400000, 226400], + [1442793600000, 200920], + [1442836800000, 202320], + [1442880000000, 171720], + ]; + await visualBuilder.selectAggType('Percentile'); + await visualBuilder.setFieldForAggregation('Memory'); + + const chartData = await visualBuilder.getAreaChartData(); + expect(chartData).to.eql(expectedChartData); + }); + + it('should display correct chart data, label names and area colors for sum aggregation when split by terms', async () => { + const firstAreaExpectedChartData = [ + [1442620800000, 0], + [1442664000000, 0], + [1442707200000, 11121455], + [1442750400000, 10611145], + [1442793600000, 10511084], + [1442836800000, 10512452], + [1442880000000, 10444101], + ]; + const secondAreaExpectedChartData = [ + [1442620800000, 0], + [1442664000000, 0], + [1442707200000, 2807570], + [1442750400000, 2580565], + [1442793600000, 2755642], + [1442836800000, 2795809], + [1442880000000, 2651447], + ]; + await visualBuilder.selectAggType('Sum'); + await visualBuilder.setFieldForAggregation('bytes'); + await visualBuilder.setMetricsGroupByTerms('type'); + + const chartDebugData = await visualBuilder.getChartDebugState(); + const areasCount = (await visualBuilder.getChartItems(chartDebugData))?.length; + const legendNames = await visualBuilder.getLegendNames(chartDebugData); + const areaColors = await visualBuilder.getAreaChartColors(chartDebugData); + const firstAreaChartData = await visualBuilder.getAreaChartData(chartDebugData); + const secondAreaChartData = await visualBuilder.getAreaChartData(chartDebugData, 1); + + expect(areasCount).to.be(2); + expect(legendNames).to.eql(['apache', 'nginx']); + expect(areaColors).to.eql(['#54b399', '#6092c0']); + expect(firstAreaChartData).to.eql(firstAreaExpectedChartData); + expect(secondAreaChartData).to.eql(secondAreaExpectedChartData); + }); + + it('should display correct chart data, label names and area colors for min aggregation when split by filters', async () => { + const firstAreaExpectedChartData = [ + [1442707200000, 219120], + [1442750400000, 209840], + [1442793600000, 200920], + [1442836800000, 202320], + [1442880000000, 201080], + ]; + const secondAreaExpectedChartData = [ + [1442707200000, 293120], + [1442750400000, 289960], + [1442793600000, 297800], + [1442836800000, 281040], + [1442880000000, 282080], + ]; + await visualBuilder.selectAggType('Min'); + await visualBuilder.setFieldForAggregation('memory'); + await visualBuilder.setMetricsGroupBy('filters'); + await visualBuilder.addGroupByFilterRow(); + await visualBuilder.setGroupByFilterQuery('bytes > 5000'); + await visualBuilder.setGroupByFilterQuery('bytes > 7000', 1); + await visualBuilder.setGroupByFilterLabel('second', 1); + await visualBuilder.setColorPickerValue('#00BCA3', 1); + await visualBuilder.setColorPickerValue('#72CFC2', 2); + + const chartDebugData = await visualBuilder.getChartDebugState(); + const areasCount = (await visualBuilder.getChartItems(chartDebugData))?.length; + const legendNames = await visualBuilder.getLegendNames(chartDebugData); + const areaColors = await visualBuilder.getAreaChartColors(chartDebugData); + const firstAreaChartData = await visualBuilder.getAreaChartData(chartDebugData); + const secondAreaChartData = await visualBuilder.getAreaChartData(chartDebugData, 1); + + expect(areasCount).to.be(2); + expect(legendNames).to.eql(['bytes > 5000', 'second']); + expect(areaColors).to.eql(['rgba(0,188,163,1)', 'rgba(114,207,194,1)']); + expect(firstAreaChartData).to.eql(firstAreaExpectedChartData); + expect(secondAreaChartData).to.eql(secondAreaExpectedChartData); + }); + + it('should display cloned series and then change its chart type to bar', async () => { + let areasCount = (await visualBuilder.getChartItems())?.length; + expect(areasCount).to.be(1); + + await visualBuilder.cloneSeries(); + areasCount = (await visualBuilder.getChartItems())?.length; + expect(areasCount).to.be(2); + + await visualBuilder.clickSeriesOption(); + await visualBuilder.setChartType('Bar'); + + const chartDebugData = await visualBuilder.getChartDebugState(); + areasCount = (await visualBuilder.getChartItems(chartDebugData))?.length; + const barsCount = (await visualBuilder.getChartItems(chartDebugData))?.length; + expect(areasCount).to.be(1); + expect(barsCount).to.be(1); + }); + + it('should display correct chart data for overridden index pattern', async () => { + const expectedChartData = [ + [1442620800000, 4], + [1442664000000, 3], + [1442707200000, 5], + [1442750400000, 2], + [1442793600000, 6], + [1442836800000, 1], + [1442880000000, 6], + [1442923200000, 1], + ]; + await visualBuilder.clickSeriesOption(); + await visualBuilder.setOverrideIndexPattern(true); + await visualBuilder.setIndexPatternValue('long-window-logstash-*'); + await visualBuilder.setIntervalValue('12h'); + + const chartData = await visualBuilder.getAreaChartData(); + expect(chartData).to.eql(expectedChartData); + }); + + it('should display correct data for the selected interval', async () => { + const expectedChartData = [ + [1442534400000, 0], + [1442707200000, 9371], + ]; + await visualBuilder.clickPanelOptions('timeSeries'); + await visualBuilder.setIntervalValue('2d'); + + const chartDebugData = await visualBuilder.getChartDebugState(); + const title = await visualBuilder.getXAxisTitle(chartDebugData); + const chartData = await visualBuilder.getAreaChartData(chartDebugData); + + expect(title).to.be('per 2 days'); + expect(chartData).to.eql(expectedChartData); + }); + + describe('Query filter', () => { + it('should display correct chart data for applied series filter', async () => { + const expectedChartData = [ + [1442620800000, 0], + [1442664000000, 0], + [1442707200000, 31], + [1442750400000, 24], + [1442793600000, 27], + [1442836800000, 22], + [1442880000000, 24], + ]; + await visualBuilder.clickSeriesOption(); + await visualBuilder.setSeriesFilter('machine.os.raw : "win 7" and bytes > 10000'); + + const chartData = await visualBuilder.getAreaChartData(); + expect(chartData).to.eql(expectedChartData); + }); + + it('should display correct chart data for applied panel filter', async () => { + const expectedChartData = [ + [1442620800000, 0], + [1442664000000, 0], + [1442707200000, 472], + [1442750400000, 474], + [1442793600000, 450], + [1442836800000, 439], + [1442880000000, 458], + ]; + await visualBuilder.clickPanelOptions('timeSeries'); + await visualBuilder.setPanelFilter('machine.os.raw: "ios"'); + + const chartData = await visualBuilder.getAreaChartData(); + expect(chartData).to.eql(expectedChartData); + }); + }); + + describe('Annotations', () => { + it('should display correct annotations amount with tooltip data for the selected annotation', async () => { + await visualBuilder.clickAnnotationsTab(); + await visualBuilder.clickAnnotationsAddDataSourceButton(); + await visualBuilder.setAnnotationFilter('bytes = 0'); + await visualBuilder.setAnnotationFields('machine.os.raw, memory'); + await visualBuilder.setAnnotationRowTemplate( + 'OS: {{machine.os.raw}}, memory: {{memory}}' + ); + + const annotationsCount = await visualBuilder.getAnnotationsCount(); + await visualBuilder.clickAnnotationIcon(3); + const annotationTooltipHeader = await visualBuilder.getAnnotationTooltipHeader(); + const annotationTooltipDetails = await visualBuilder.getAnnotationTooltipDetails(); + + expect(annotationsCount).to.be(6); + expect(annotationTooltipHeader).to.be('2015-09-21 06:00'); + expect(annotationTooltipDetails).to.be('OS: ios, memory: 0'); + }); + + it('should display correct annotations amount with tooltip data for the selected annotation when using runtime field', async () => { + await visualBuilder.clickAnnotationsTab(); + await visualBuilder.clickAnnotationsAddDataSourceButton(); + await visualBuilder.setAnnotationFilter('memory > 300000'); + await visualBuilder.setAnnotationFields('hello_world_runtime_field, geo.dest'); + await visualBuilder.setAnnotationRowTemplate( + '{{hello_world_runtime_field}} from {{geo.dest}}!' + ); + + const annotationsCount = await visualBuilder.getAnnotationsCount(); + await visualBuilder.clickAnnotationIcon(5); + const annotationTooltipHeader = await visualBuilder.getAnnotationTooltipHeader(); + const annotationTooltipDetails = await visualBuilder.getAnnotationTooltipDetails(); + + expect(annotationsCount).to.be(30); + expect(annotationTooltipHeader).to.be('2015-09-20 11:00'); + expect(annotationTooltipDetails).to.be('hello world from GA!'); + }); + }); + + after(async () => await visualBuilder.toggleNewChartsLibraryWithDebug(false)); + }); }); }); } diff --git a/test/functional/config.js b/test/functional/config.js index 1c0c519f21e4c87..19a628be10f52ba 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -106,6 +106,12 @@ export default async function ({ readConfigFile }) { observabilityCases: { pathname: '/app/observability/cases', }, + fleet: { + pathname: '/app/fleet', + }, + integrations: { + pathname: '/app/integrations', + }, }, junit: { reportName: 'Chrome UI Functional Tests', diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 40a70efd93efdd7..ea9e23da8256fa3 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import type { DebugState } from '@elastic/charts'; import { FtrService } from '../ftr_provider_context'; import { WebElementWrapper } from '../services/lib/web_element_wrapper'; @@ -28,6 +29,8 @@ export class VisualBuilderPageObject extends FtrService { private readonly retry = this.ctx.getService('retry'); private readonly testSubjects = this.ctx.getService('testSubjects'); private readonly comboBox = this.ctx.getService('comboBox'); + private readonly elasticChart = this.ctx.getService('elasticChart'); + private readonly kibanaServer = this.ctx.getService('kibanaServer'); private readonly common = this.ctx.getPageObject('common'); private readonly header = this.ctx.getPageObject('header'); private readonly timePicker = this.ctx.getPageObject('timePicker'); @@ -454,6 +457,51 @@ export class VisualBuilderPageObject extends FtrService { await this.header.waitUntilLoadingHasFinished(); } + public async clickAnnotationsTab() { + await this.testSubjects.click('timeSeriesEditorAnnotationsBtn'); + await this.header.waitUntilLoadingHasFinished(); + } + + public async clickAnnotationsAddDataSourceButton() { + await this.testSubjects.click('addDataSourceButton'); + } + + public async setAnnotationFilter(query: string) { + const annotationQueryBar = await this.testSubjects.find('annotationQueryBar'); + await annotationQueryBar.type(query); + await this.header.waitUntilLoadingHasFinished(); + } + + public async setAnnotationFields(fields: string) { + const annotationFieldsInput = await this.testSubjects.find('annotationFieldsInput'); + await annotationFieldsInput.type(fields); + } + + public async setAnnotationRowTemplate(template: string) { + const annotationRowTemplateInput = await this.testSubjects.find('annotationRowTemplateInput'); + await annotationRowTemplateInput.type(template); + } + + public async getAnnotationsCount() { + const annotationsIcons = (await this.find.allByCssSelector('.echAnnotation')) ?? []; + return annotationsIcons.length; + } + + public async clickAnnotationIcon(nth: number = 0) { + const annotationsIcons = (await this.find.allByCssSelector('.echAnnotation')) ?? []; + await annotationsIcons[nth].click(); + } + + public async getAnnotationTooltipHeader() { + const annotationTooltipHeader = await this.find.byClassName('echAnnotation__header'); + return await annotationTooltipHeader.getVisibleText(); + } + + public async getAnnotationTooltipDetails() { + const annotationTooltipDetails = await this.find.byClassName('echAnnotation__details'); + return await annotationTooltipDetails.getVisibleText(); + } + public async switchIndexPatternSelectionMode(useKibanaIndices: boolean) { await this.testSubjects.click('switchIndexPatternSelectionModePopover'); await this.testSubjects.setEuiSwitch( @@ -489,7 +537,7 @@ export class VisualBuilderPageObject extends FtrService { public async setIntervalValue(value: string) { const el = await this.testSubjects.find('metricsIndexPatternInterval'); - await el.clearValue(); + await el.clearValueWithKeyboard(); await el.type(value); await this.header.waitUntilLoadingHasFinished(); } @@ -500,6 +548,14 @@ export class VisualBuilderPageObject extends FtrService { await this.header.waitUntilLoadingHasFinished(); } + public async setOverrideIndexPattern(value: boolean) { + const option = await this.testSubjects.find( + `seriesOverrideIndexPattern-${value ? 'yes' : 'no'}` + ); + (await option.findByCssSelector('label')).click(); + await this.header.waitUntilLoadingHasFinished(); + } + public async waitForIndexPatternTimeFieldOptionsLoaded() { await this.retry.waitFor('combobox options loaded', async () => { const options = await this.comboBox.getOptions('metricsIndexPatternFieldsSelect'); @@ -689,14 +745,16 @@ export class VisualBuilderPageObject extends FtrService { return await this.find.allByCssSelector('.tvbSeriesEditor'); } + public async setMetricsGroupBy(option: string) { + const groupBy = await this.testSubjects.find('groupBySelect'); + await this.comboBox.setElement(groupBy, option, { clickWithMouse: true }); + } + public async setMetricsGroupByTerms( field: string, filtering: { include?: string; exclude?: string } = {} ) { - const groupBy = await this.find.byCssSelector( - '.tvbAggRow--split [data-test-subj="comboBoxInput"]' - ); - await this.comboBox.setElement(groupBy, 'Terms', { clickWithMouse: true }); + await this.setMetricsGroupBy('terms'); await this.common.sleep(1000); const byField = await this.testSubjects.find('groupByField'); await this.comboBox.setElement(byField, field); @@ -725,6 +783,38 @@ export class VisualBuilderPageObject extends FtrService { return await this.comboBox.isOptionSelected(groupBy, value); } + public async addGroupByFilterRow() { + const addButton = await this.testSubjects.find('filterRowAddBtn'); + await addButton.click(); + } + + public async setGroupByFilterQuery(query: string, nth: number = 0) { + const filterQueryInput = await this.testSubjects.findAll('filterItemsQueryBar'); + await filterQueryInput[nth].type(query); + } + + public async setGroupByFilterLabel(label: string, nth: number = 0) { + const filterLabelInput = await this.testSubjects.findAll('filterItemsLabel'); + await filterLabelInput[nth].type(label); + } + + public async setChartType(type: string, nth: number = 0) { + const seriesChartTypeComboBoxes = await this.testSubjects.findAll('seriesChartTypeComboBox'); + return await this.comboBox.setElement(seriesChartTypeComboBoxes[nth], type); + } + + public async setSeriesFilter(query: string) { + const seriesFilterQueryInput = await this.testSubjects.find('seriesConfigQueryBar'); + await seriesFilterQueryInput.type(query); + await this.header.waitUntilLoadingHasFinished(); + } + + public async setPanelFilter(query: string) { + const panelFilterQueryInput = await this.testSubjects.find('panelFilterQueryBar'); + await panelFilterQueryInput.type(query); + await this.header.waitUntilLoadingHasFinished(); + } + public async setMetricsDataTimerangeMode(value: string) { const dataTimeRangeMode = await this.testSubjects.find('dataTimeRangeMode'); return await this.comboBox.setElement(dataTimeRangeMode, value); @@ -750,4 +840,39 @@ export class VisualBuilderPageObject extends FtrService { const optionInput = await this.testSubjects.find(`filterRatio${optionType}Input`); await optionInput.type(query); } + + public async toggleNewChartsLibraryWithDebug(enabled: boolean) { + await this.kibanaServer.uiSettings.update({ + 'visualization:visualize:legacyChartsLibrary': !enabled, + }); + await this.elasticChart.setNewChartUiDebugFlag(enabled); + } + + public async getChartDebugState(chartData?: DebugState) { + return chartData ?? (await this.elasticChart.getChartDebugData())!; + } + + public async getXAxisTitle(chartData?: DebugState, nth: number = 0) { + const debugState = await this.getChartDebugState(chartData); + return debugState?.axes?.x[nth]?.title; + } + + public async getLegendNames(chartData?: DebugState) { + const legendItems = (await this.getChartDebugState(chartData))?.legend?.items ?? []; + return legendItems.map(({ name }) => name); + } + + public async getChartItems(chartData?: DebugState, itemType: 'areas' | 'bars' = 'areas') { + return (await this.getChartDebugState(chartData))?.[itemType]; + } + + public async getAreaChartColors(chartData?: DebugState) { + const areas = (await this.getChartItems(chartData)) as DebugState['areas']; + return areas?.map(({ color }) => color); + } + + public async getAreaChartData(chartData?: DebugState, nth: number = 0) { + const areas = (await this.getChartItems(chartData)) as DebugState['areas']; + return areas?.[nth]?.lines.y1.points.map(({ x, y }) => [x, y]); + } } diff --git a/test/package/deb.yml b/test/package/deb.yml index 294657e99473f73..9133402d3eee3c2 100644 --- a/test/package/deb.yml +++ b/test/package/deb.yml @@ -6,6 +6,7 @@ - assert_keystore_cli - assert_kibana_yml - assert_kibana_listening - - assert_kibana_available + # flaky https://github.com/elastic/kibana/issues/106749 + # - assert_kibana_available - assert_kibana_log - assert_kibana_data diff --git a/test/package/docker.yml b/test/package/docker.yml index 6da20360c174ad0..141f18e2c222a71 100644 --- a/test/package/docker.yml +++ b/test/package/docker.yml @@ -4,4 +4,5 @@ - install_docker - install_kibana_docker - assert_kibana_listening - - assert_kibana_available + # flaky https://github.com/elastic/kibana/issues/106749 + # - assert_kibana_available \ No newline at end of file diff --git a/test/package/rpm.yml b/test/package/rpm.yml index 456c2bdf18b72f1..5fb1d37a969181a 100644 --- a/test/package/rpm.yml +++ b/test/package/rpm.yml @@ -6,6 +6,7 @@ - assert_keystore_cli - assert_kibana_yml - assert_kibana_listening - - assert_kibana_available + # flaky https://github.com/elastic/kibana/issues/106749 + # - assert_kibana_available - assert_kibana_log - assert_kibana_data diff --git a/test/plugin_functional/test_suites/core/deprecations.ts b/test/plugin_functional/test_suites/core/deprecations.ts index 112b29a896a0001..2bdd3cf59549baa 100644 --- a/test/plugin_functional/test_suites/core/deprecations.ts +++ b/test/plugin_functional/test_suites/core/deprecations.ts @@ -29,6 +29,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }, deprecationType: 'config', domainId: 'corePluginDeprecations', + requireRestart: true, }, { level: 'critical', @@ -40,6 +41,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }, deprecationType: 'config', domainId: 'corePluginDeprecations', + requireRestart: true, }, { level: 'critical', @@ -53,6 +55,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide documentationUrl: 'config-secret-doc-url', deprecationType: 'config', domainId: 'corePluginDeprecations', + requireRestart: true, }, { message: 'CorePluginDeprecationsPlugin is a deprecated feature for testing.', diff --git a/test/scripts/checks/type_check_plugin_public_api_docs.sh b/test/scripts/checks/type_check_plugin_public_api_docs.sh index d32343fcd173de3..24be42d63207a08 100755 --- a/test/scripts/checks/type_check_plugin_public_api_docs.sh +++ b/test/scripts/checks/type_check_plugin_public_api_docs.sh @@ -14,4 +14,4 @@ checks-reporter-with-killswitch "Check Types" \ node scripts/type_check echo " -- building api docs" -node scripts/build_api_docs +node --max-old-space-size=8000 scripts/build_api_docs diff --git a/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx b/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx index a9a0b824cc82898..f77a6591270c411 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/schema/schema_overview.tsx @@ -7,6 +7,7 @@ import { EuiButton, + EuiCallOut, EuiCard, EuiFlexGroup, EuiFlexItem, @@ -271,6 +272,30 @@ export function SchemaOverviewHeading() { /> + + + + + {i18n.translate( + 'xpack.apm.settings.schema.descriptionText.betaCalloutMessage', + { + defaultMessage: + 'This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features.', + } + )} + + + + + ); } diff --git a/x-pack/plugins/canvas/__fixtures__/function_specs.ts b/x-pack/plugins/canvas/__fixtures__/function_specs.ts index ff6c56439208035..a307d420e888a98 100644 --- a/x-pack/plugins/canvas/__fixtures__/function_specs.ts +++ b/x-pack/plugins/canvas/__fixtures__/function_specs.ts @@ -9,8 +9,10 @@ import { functions as browserFns } from '../canvas_plugin_src/functions/browser' import { ExpressionFunction } from '../../../../src/plugins/expressions'; import { initFunctions } from '../public/functions'; import { functionSpecs as shapeFunctionSpecs } from '../../../../src/plugins/expression_shape/__fixtures__'; +import { functionSpecs as imageFunctionSpecs } from '../../../../src/plugins/expression_image/__fixtures__'; +import { functionSpecs as metricFunctionSpecs } from '../../../../src/plugins/expression_metric/__fixtures__'; export const functionSpecs = browserFns .concat(...(initFunctions({} as any) as any)) .map((fn) => new ExpressionFunction(fn())) - .concat(...shapeFunctionSpecs); + .concat(...shapeFunctionSpecs, ...imageFunctionSpecs, ...metricFunctionSpecs); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js index dcb6035dbb687b7..823ee074a9ddc18 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.test.js @@ -5,9 +5,11 @@ * 2.0. */ -import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; +import { + functionWrapper, + fontStyle, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from '../common/__fixtures__/test_tables'; -import { fontStyle } from '../common/__fixtures__/test_styles'; import { markdown } from './markdown'; describe('markdown', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts deleted file mode 100644 index e661a15cea3aef4..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; - -import { - getElasticLogo, - resolveWithMissingImage, -} from '../../../../../../src/plugins/presentation_util/common/lib'; - -export enum ImageMode { - CONTAIN = 'contain', - COVER = 'cover', - STRETCH = 'stretch', -} - -interface Arguments { - dataurl: string | null; - mode: ImageMode | null; -} - -export interface Return { - type: 'image'; - mode: string; - dataurl: string; -} - -export function image(): ExpressionFunctionDefinition<'image', null, Arguments, Promise> { - const { help, args: argHelp } = getFunctionHelp().image; - const errors = getFunctionErrors().image; - return { - name: 'image', - aliases: [], - type: 'image', - inputTypes: ['null'], - help, - args: { - dataurl: { - // This was accepting dataurl, but there was no facility in fn for checking type and handling a dataurl type. - types: ['string', 'null'], - help: argHelp.dataurl, - aliases: ['_', 'url'], - default: null, - }, - mode: { - types: ['string'], - help: argHelp.mode, - default: 'contain', - options: Object.values(ImageMode), - }, - }, - fn: async (input, { dataurl, mode }) => { - if (!mode || !Object.values(ImageMode).includes(mode)) { - throw errors.invalidImageMode(); - } - const { elasticLogo } = await getElasticLogo(); - - if (dataurl === null) { - dataurl = elasticLogo; - } - - const modeStyle = mode === 'stretch' ? '100% 100%' : mode; - return { - type: 'image', - mode: modeStyle, - dataurl: resolveWithMissingImage(dataurl, elasticLogo) as string, - }; - }, - }; -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts index 6ab7abac985cca8..a34930a5d18a867 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.ts @@ -29,12 +29,10 @@ import { gt } from './gt'; import { gte } from './gte'; import { head } from './head'; import { ifFn } from './if'; -import { image } from './image'; import { joinRows } from './join_rows'; import { lt } from './lt'; import { lte } from './lte'; import { mapCenter } from './map_center'; -import { metric } from './metric'; import { neq } from './neq'; import { ply } from './ply'; import { progress } from './progress'; @@ -79,12 +77,10 @@ export const functions = [ gte, head, ifFn, - image, lt, lte, joinRows, mapCenter, - metric, neq, ply, progress, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.ts deleted file mode 100644 index 3b9d5216dd76bfd..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/metric.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { openSans } from '../../../common/lib/fonts'; -import { Render, Style, ExpressionFunctionDefinition } from '../../../types'; -import { getFunctionHelp } from '../../../i18n'; - -type Input = number | string | null; - -interface Arguments { - label: string; - metricFont: Style; - metricFormat: string; - labelFont: Style; -} - -export function metric(): ExpressionFunctionDefinition< - 'metric', - Input, - Arguments, - Render -> { - const { help, args: argHelp } = getFunctionHelp().metric; - - return { - name: 'metric', - aliases: [], - type: 'render', - inputTypes: ['number', 'string', 'null'], - help, - args: { - label: { - types: ['string'], - aliases: ['_', 'text', 'description'], - help: argHelp.label, - default: '""', - }, - labelFont: { - types: ['style'], - help: argHelp.labelFont, - default: `{font size=14 family="${openSans.value}" color="#000000" align=center}`, - }, - metricFont: { - types: ['style'], - help: argHelp.metricFont, - default: `{font size=48 family="${openSans.value}" color="#000000" align=center lHeight=48}`, - }, - metricFormat: { - types: ['string'], - aliases: ['format'], - help: argHelp.metricFormat, - }, - }, - fn: (input, { label, labelFont, metricFont, metricFormat }) => { - return { - type: 'render', - as: 'metric', - value: { - metric: input === null ? '?' : input, - label, - labelFont, - metricFont, - metricFormat, - }, - }; - }, - }; -} diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js index 5f44428abf03a88..567421a969d92e2 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/progress.test.js @@ -6,10 +6,12 @@ */ import expect from '@kbn/expect'; -import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; +import { + functionWrapper, + fontStyle, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { getFunctionErrors } from '../../../i18n'; import { progress } from './progress'; -import { fontStyle } from './__fixtures__/test_styles'; const errors = getFunctionErrors().progress; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js index 42e5150b03637b0..e04b4a4cc14696d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/table.test.js @@ -5,9 +5,11 @@ * 2.0. */ -import { functionWrapper } from '../../../../../../src/plugins/presentation_util/common/lib'; +import { + functionWrapper, + fontStyle, +} from '../../../../../../src/plugins/presentation_util/common/lib'; import { testTable } from './__fixtures__/test_tables'; -import { fontStyle } from './__fixtures__/test_styles'; import { table } from './table'; describe('table', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx deleted file mode 100644 index a8ac14768bfa5af..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/__stories__/image.stories.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { storiesOf } from '@storybook/react'; -import { image } from '../image'; -import { getElasticLogo } from '../../../../../../src/plugins/presentation_util/common/lib'; -import { waitFor } from '../../../../../../src/plugins/presentation_util/public/__stories__'; -import { Render } from './render'; - -const Renderer = ({ elasticLogo }: { elasticLogo: string }) => { - const config = { - type: 'image' as 'image', - mode: 'cover', - dataurl: elasticLogo, - }; - - return ; -}; - -storiesOf('renderers/image', module).add( - 'default', - (_, props) => , - { decorators: [waitFor(getElasticLogo())] } -); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts index 8eabae4c661d2ad..5ec2af8d7773faf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/core.ts @@ -5,15 +5,13 @@ * 2.0. */ -import { image } from './image'; import { markdown } from './markdown'; -import { metric } from './metric'; import { pie } from './pie'; import { plot } from './plot'; import { progress } from './progress'; import { text } from './text'; import { table } from './table'; -export const renderFunctions = [image, markdown, metric, pie, plot, progress, table, text]; +export const renderFunctions = [markdown, pie, plot, progress, table, text]; export const renderFunctionFactories = []; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts index 0c824fb3dd25edf..57d5ae69c99bb21 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/external.ts @@ -5,15 +5,19 @@ * 2.0. */ +import { imageRenderer } from '../../../../../src/plugins/expression_image/public'; +import { metricRenderer } from '../../../../../src/plugins/expression_metric/public'; import { errorRenderer, debugRenderer } from '../../../../../src/plugins/expression_error/public'; import { repeatImageRenderer } from '../../../../../src/plugins/expression_repeat_image/public'; import { revealImageRenderer } from '../../../../../src/plugins/expression_reveal_image/public'; import { shapeRenderer } from '../../../../../src/plugins/expression_shape/public'; export const renderFunctions = [ - revealImageRenderer, debugRenderer, errorRenderer, + imageRenderer, + metricRenderer, + revealImageRenderer, shapeRenderer, repeatImageRenderer, ]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx deleted file mode 100644 index 78e3ecb7a4c9525..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/image.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import ReactDOM from 'react-dom'; -import React from 'react'; -import { - getElasticLogo, - isValidUrl, -} from '../../../../../src/plugins/presentation_util/common/lib'; -import { Return as Arguments } from '../functions/common/image'; -import { RendererStrings } from '../../i18n'; -import { RendererFactory } from '../../types'; - -const { image: strings } = RendererStrings; - -export const image: RendererFactory = () => ({ - name: 'image', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render: async (domNode, config, handlers) => { - const { elasticLogo } = await getElasticLogo(); - const dataurl = isValidUrl(config.dataurl) ? config.dataurl : elasticLogo; - - const style = { - height: '100%', - backgroundImage: `url(${dataurl})`, - backgroundRepeat: 'no-repeat', - backgroundPosition: 'center center', - backgroundSize: config.mode, - }; - - ReactDOM.render(
, domNode, () => handlers.done()); - - handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/__stories__/metric.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/__stories__/metric.stories.tsx deleted file mode 100644 index e127018411166ea..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/__stories__/metric.stories.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { storiesOf } from '@storybook/react'; -import React, { CSSProperties } from 'react'; -import { Metric } from '../metric'; - -const labelFontSpec: CSSProperties = { - fontFamily: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", - fontWeight: 'normal', - fontStyle: 'italic', - textDecoration: 'none', - textAlign: 'center', - fontSize: '24px', - lineHeight: '1', - color: '#000000', -}; - -const metricFontSpec: CSSProperties = { - fontFamily: - "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", - fontWeight: 'bold', - fontStyle: 'normal', - textDecoration: 'none', - textAlign: 'center', - fontSize: '48px', - lineHeight: '1', - color: '#b83c6f', -}; - -storiesOf('renderers/Metric', module) - .addDecorator((story) => ( -
- {story()} -
- )) - .add('with null metric', () => ) - .add('with number metric', () => ( - - )) - .add('with string metric', () => ( - - )) - .add('with label', () => ( - - )) - .add('with number metric and a specified format', () => ( - - )) - .add('with formatted string metric and a specified format', () => ( - - )) - .add('with invalid metricFont', () => ( - - )); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/index.tsx b/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/index.tsx deleted file mode 100644 index b3c0830c3849d54..000000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { CSSProperties } from 'react'; -import ReactDOM from 'react-dom'; -import { RendererFactory, Style } from '../../../types'; -import { RendererStrings } from '../../../i18n'; -import { Metric } from './component/metric'; - -const { metric: strings } = RendererStrings; - -export interface Config { - /** The text to display under the metric */ - label: string; - /** Font settings for the label */ - labelFont: Style; - /** Value of the metric to display */ - metric: string | number | null; - /** Font settings for the metric */ - metricFont: Style; - /** NumeralJS format string */ - metricFormat: string; -} - -export const metric: RendererFactory = () => ({ - name: 'metric', - displayName: strings.getDisplayName(), - help: strings.getHelpDescription(), - reuseDomNode: true, - render(domNode, config, handlers) { - ReactDOM.render( - , - domNode, - () => handlers.done() - ); - - handlers.onDestroy(() => ReactDOM.unmountComponentAtNode(domNode)); - }, -}); diff --git a/x-pack/plugins/canvas/i18n/functions/dict/image.ts b/x-pack/plugins/canvas/i18n/functions/dict/image.ts deleted file mode 100644 index b619d550f9efd77..000000000000000 --- a/x-pack/plugins/canvas/i18n/functions/dict/image.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { image } from '../../../canvas_plugin_src/functions/common/image'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; -import { - URL, - BASE64, - IMAGE_MODE_CONTAIN, - IMAGE_MODE_COVER, - IMAGE_MODE_STRETCH, -} from '../../constants'; - -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.imageHelpText', { - defaultMessage: - 'Displays an image. Provide an image asset as a {BASE64} data {URL}, or pass in a sub-expression.', - values: { - BASE64, - URL, - }, - }), - args: { - dataurl: i18n.translate('xpack.canvas.functions.image.args.dataurlHelpText', { - defaultMessage: 'The {https} {URL} or {BASE64} data {URL} of an image.', - values: { - BASE64, - https: 'HTTP(S)', - URL, - }, - }), - mode: i18n.translate('xpack.canvas.functions.image.args.modeHelpText', { - defaultMessage: - '{contain} shows the entire image, scaled to fit. ' + - '{cover} fills the container with the image, cropping from the sides or bottom as needed. ' + - '{stretch} resizes the height and width of the image to 100% of the container.', - values: { - contain: `\`"${IMAGE_MODE_CONTAIN}"\``, - cover: `\`"${IMAGE_MODE_COVER}"\``, - stretch: `\`"${IMAGE_MODE_STRETCH}"\``, - }, - }), - }, -}; - -export const errors = { - invalidImageMode: () => - new Error( - i18n.translate('xpack.canvas.functions.image.invalidImageModeErrorMessage', { - defaultMessage: '"mode" must be "{contain}", "{cover}", or "{stretch}"', - values: { - contain: IMAGE_MODE_CONTAIN, - cover: IMAGE_MODE_COVER, - stretch: IMAGE_MODE_STRETCH, - }, - }) - ), -}; diff --git a/x-pack/plugins/canvas/i18n/functions/dict/metric.ts b/x-pack/plugins/canvas/i18n/functions/dict/metric.ts deleted file mode 100644 index 44b9311fb3543ee..000000000000000 --- a/x-pack/plugins/canvas/i18n/functions/dict/metric.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { i18n } from '@kbn/i18n'; -import { metric } from '../../../canvas_plugin_src/functions/common/metric'; -import { FunctionHelp } from '../function_help'; -import { FunctionFactory } from '../../../types'; -import { FONT_FAMILY, FONT_WEIGHT, CSS, NUMERALJS } from '../../constants'; - -export const help: FunctionHelp> = { - help: i18n.translate('xpack.canvas.functions.metricHelpText', { - defaultMessage: 'Displays a number over a label.', - }), - args: { - label: i18n.translate('xpack.canvas.functions.metric.args.labelHelpText', { - defaultMessage: 'The text describing the metric.', - }), - labelFont: i18n.translate('xpack.canvas.functions.metric.args.labelFontHelpText', { - defaultMessage: - 'The {CSS} font properties for the label. For example, {FONT_FAMILY} or {FONT_WEIGHT}.', - values: { - CSS, - FONT_FAMILY, - FONT_WEIGHT, - }, - }), - metricFont: i18n.translate('xpack.canvas.functions.metric.args.metricFontHelpText', { - defaultMessage: - 'The {CSS} font properties for the metric. For example, {FONT_FAMILY} or {FONT_WEIGHT}.', - values: { - CSS, - FONT_FAMILY, - FONT_WEIGHT, - }, - }), - // TODO: Find a way to generate the docs URL here - metricFormat: i18n.translate('xpack.canvas.functions.metric.args.metricFormatHelpText', { - defaultMessage: 'A {NUMERALJS} format string. For example, {example1} or {example2}.', - values: { - example1: '`"0.0a"`', - example2: '`"0%"`', - NUMERALJS, - }, - }), - }, -}; diff --git a/x-pack/plugins/canvas/i18n/functions/function_errors.ts b/x-pack/plugins/canvas/i18n/functions/function_errors.ts index a01cb09a38347f6..1e515ece6356984 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_errors.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_errors.ts @@ -14,7 +14,6 @@ import { errors as csv } from './dict/csv'; import { errors as date } from './dict/date'; import { errors as demodata } from './dict/demodata'; import { errors as getCell } from './dict/get_cell'; -import { errors as image } from './dict/image'; import { errors as joinRows } from './dict/join_rows'; import { errors as ply } from './dict/ply'; import { errors as pointseries } from './dict/pointseries'; @@ -32,7 +31,6 @@ export const getFunctionErrors = () => ({ date, demodata, getCell, - image, joinRows, ply, pointseries, diff --git a/x-pack/plugins/canvas/i18n/functions/function_help.ts b/x-pack/plugins/canvas/i18n/functions/function_help.ts index 0ca2c01718b49a6..08acecad377964d 100644 --- a/x-pack/plugins/canvas/i18n/functions/function_help.ts +++ b/x-pack/plugins/canvas/i18n/functions/function_help.ts @@ -40,14 +40,12 @@ import { help as gt } from './dict/gt'; import { help as gte } from './dict/gte'; import { help as head } from './dict/head'; import { help as ifFn } from './dict/if'; -import { help as image } from './dict/image'; import { help as joinRows } from './dict/join_rows'; import { help as location } from './dict/location'; import { help as lt } from './dict/lt'; import { help as lte } from './dict/lte'; import { help as mapCenter } from './dict/map_center'; import { help as markdown } from './dict/markdown'; -import { help as metric } from './dict/metric'; import { help as neq } from './dict/neq'; import { help as pie } from './dict/pie'; import { help as plot } from './dict/plot'; @@ -199,13 +197,11 @@ export const getFunctionHelp = (): FunctionHelpDict => ({ head, if: ifFn, joinRows, - image, location, lt, lte, mapCenter, markdown, - metric, neq, pie, plot, diff --git a/x-pack/plugins/canvas/i18n/renderers.ts b/x-pack/plugins/canvas/i18n/renderers.ts index 80f1a5aecc89e41..03e0ab3a19ae3d6 100644 --- a/x-pack/plugins/canvas/i18n/renderers.ts +++ b/x-pack/plugins/canvas/i18n/renderers.ts @@ -55,16 +55,6 @@ export const RendererStrings = { defaultMessage: 'Renders an embeddable Saved Object from other parts of Kibana', }), }, - image: { - getDisplayName: () => - i18n.translate('xpack.canvas.renderer.image.displayName', { - defaultMessage: 'Image', - }), - getHelpDescription: () => - i18n.translate('xpack.canvas.renderer.image.helpDescription', { - defaultMessage: 'Render an image', - }), - }, markdown: { getDisplayName: () => i18n.translate('xpack.canvas.renderer.markdown.displayName', { @@ -79,16 +69,6 @@ export const RendererStrings = { }, }), }, - metric: { - getDisplayName: () => - i18n.translate('xpack.canvas.renderer.metric.displayName', { - defaultMessage: 'Metric', - }), - getHelpDescription: () => - i18n.translate('xpack.canvas.renderer.metric.helpDescription', { - defaultMessage: 'Render a number over a label', - }), - }, pie: { getDisplayName: () => i18n.translate('xpack.canvas.renderer.pie.displayName', { diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json index 1692d90884a6245..263284564de8062 100644 --- a/x-pack/plugins/canvas/kibana.json +++ b/x-pack/plugins/canvas/kibana.json @@ -11,6 +11,8 @@ "data", "embeddable", "expressionError", + "expressionImage", + "expressionMetric", "expressionRepeatImage", "expressionRevealImage", "expressionShape", diff --git a/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js b/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js index 14f47553002accc..e47ccafe8b6e7be 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js +++ b/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js @@ -5,15 +5,12 @@ * 2.0. */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { compose, withProps, withPropsOnChange } from 'recompose'; import { EuiTextArea, EuiButton, EuiButtonEmpty, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { fromExpression, toExpression } from '@kbn/interpreter/common'; -import { createStatefulPropHoc } from '../../components/enhance/stateful_prop'; - const strings = { getApplyButtonLabel: () => i18n.translate('xpack.canvas.argFormAdvancedFailure.applyButtonLabel', { @@ -29,22 +26,30 @@ const strings = { }), }; -export const AdvancedFailureComponent = (props) => { - const { - onValueChange, - defaultValue, - argExpression, - updateArgExpression, - resetErrorState, - valid, - argId, - } = props; +const isValid = (argExpression) => { + try { + fromExpression(argExpression, 'argument'); + return true; + } catch (e) { + return false; + } +}; + +export const AdvancedFailure = (props) => { + const { onValueChange, defaultValue, argValue, resetErrorState, argId } = props; + + const [argExpression, setArgExpression] = useState(toExpression(argValue, 'argument')); + const [valid, setValid] = useState(isValid(argExpression)); + + useEffect(() => { + const argExpr = toExpression(argValue, 'argument'); + setArgExpression(argExpr); + setValid(isValid(argExpr)); + }, [argValue]); const valueChange = (ev) => { ev.preventDefault(); - resetErrorState(); // when setting a new value, attempt to reset the error state - if (valid) { return onValueChange(fromExpression(argExpression.trim(), 'argument')); } @@ -69,7 +74,7 @@ export const AdvancedFailureComponent = (props) => { isInvalid={!valid} value={argExpression} compressed - onChange={updateArgExpression} + onChange={(ev) => setArgExpression(ev.target.value)} rows={3} /> @@ -89,33 +94,10 @@ export const AdvancedFailureComponent = (props) => { ); }; -AdvancedFailureComponent.propTypes = { +AdvancedFailure.propTypes = { defaultValue: PropTypes.string, onValueChange: PropTypes.func.isRequired, - argExpression: PropTypes.string.isRequired, - updateArgExpression: PropTypes.func.isRequired, resetErrorState: PropTypes.func.isRequired, - valid: PropTypes.bool.isRequired, argId: PropTypes.string.isRequired, -}; - -export const AdvancedFailure = compose( - withProps(({ argValue }) => ({ - argExpression: toExpression(argValue, 'argument'), - })), - createStatefulPropHoc('argExpression', 'updateArgExpression'), - withPropsOnChange(['argExpression'], ({ argExpression }) => ({ - valid: (function () { - try { - fromExpression(argExpression, 'argument'); - return true; - } catch (e) { - return false; - } - })(), - })) -)(AdvancedFailureComponent); - -AdvancedFailure.propTypes = { argValue: PropTypes.any.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/arg_form/index.js b/x-pack/plugins/canvas/public/components/arg_form/index.js index 84a5975cbdfb5d2..5dbc6c33db70681 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/index.js +++ b/x-pack/plugins/canvas/public/components/arg_form/index.js @@ -5,29 +5,43 @@ * 2.0. */ +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { compose, withState, lifecycle } from 'recompose'; +import { useSelector } from 'react-redux'; import { getAssets } from '../../state/selectors/assets'; import { getWorkpadInfo } from '../../state/selectors/workpad'; import { ArgForm as Component } from './arg_form'; -export const ArgForm = compose( - withState('label', 'setLabel', ({ label, argTypeInstance }) => { - return label || argTypeInstance.displayName || argTypeInstance.name; - }), - withState('resolvedArgValue', 'setResolvedArgValue'), - withState('renderError', 'setRenderError', false), - lifecycle({ - componentDidUpdate(prevProps) { - if (prevProps.templateProps.argValue !== this.props.templateProps.argValue) { - this.props.setRenderError(false); - this.props.setResolvedArgValue(); - } - }, - }), - connect((state) => ({ workpad: getWorkpadInfo(state), assets: getAssets(state) })) -)(Component); +const getLabel = (label, argTypeInstance) => + label || argTypeInstance.displayName || argTypeInstance.name; + +export const ArgForm = (props) => { + const { argTypeInstance, label: labelFromProps, templateProps } = props; + const [label, setLabel] = useState(getLabel(labelFromProps, argTypeInstance)); + const [resolvedArgValue, setResolvedArgValue] = useState(null); + const [renderError, setRenderError] = useState(false); + const workpad = useSelector(getWorkpadInfo); + const assets = useSelector(getAssets); + + useEffect(() => { + setRenderError(false); + setResolvedArgValue(); + }, [templateProps?.argValue]); + + return ( + + ); +}; ArgForm.propTypes = { label: PropTypes.string, diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource.js b/x-pack/plugins/canvas/public/components/datasource/datasource.js index c2aa9b7f5c5ce90..acda812792c452e 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource.js @@ -5,20 +5,17 @@ * 2.0. */ -import { compose, branch, renderComponent } from 'recompose'; +import React from 'react'; import PropTypes from 'prop-types'; import { NoDatasource } from './no_datasource'; import { DatasourceComponent } from './datasource_component'; -const branches = [ - // rendered when there is no datasource in the expression - branch( - ({ datasource, stateDatasource }) => !datasource || !stateDatasource, - renderComponent(NoDatasource) - ), -]; +export const Datasource = (props) => { + const { datasource, stateDatasource } = props; + if (!datasource || !stateDatasource) return ; -export const Datasource = compose(...branches)(DatasourceComponent); + return ; +}; Datasource.propTypes = { args: PropTypes.object, diff --git a/x-pack/plugins/canvas/public/components/datasource/index.js b/x-pack/plugins/canvas/public/components/datasource/index.js index 91f34f4a127ecc1..5a0cbf6d05bd9a7 100644 --- a/x-pack/plugins/canvas/public/components/datasource/index.js +++ b/x-pack/plugins/canvas/public/components/datasource/index.js @@ -5,9 +5,9 @@ * 2.0. */ +import React, { useState, useCallback } from 'react'; import { PropTypes } from 'prop-types'; import { connect } from 'react-redux'; -import { withState, withHandlers, compose } from 'recompose'; import { get } from 'lodash'; import { datasourceRegistry } from '../../expression_types'; import { getServerFunctions } from '../../state/selectors/app'; @@ -15,6 +15,36 @@ import { getSelectedElement, getSelectedPage } from '../../state/selectors/workp import { setArgumentAtIndex, setAstAtIndex, flushContext } from '../../state/actions/elements'; import { Datasource as Component } from './datasource'; +const DatasourceComponent = (props) => { + const { args, datasource } = props; + const [stateArgs, updateArgs] = useState(args); + const [selecting, setSelecting] = useState(false); + const [previewing, setPreviewing] = useState(false); + const [isInvalid, setInvalid] = useState(false); + const [stateDatasource, selectDatasource] = useState(datasource); + + const resetArgs = useCallback(() => { + updateArgs(args); + }, [updateArgs, args]); + + return ( + + ); +}; + const mapStateToProps = (state) => ({ element: getSelectedElement(state), pageId: getSelectedPage(state), @@ -82,17 +112,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { }; }; -export const Datasource = compose( - connect(mapStateToProps, mapDispatchToProps, mergeProps), - withState('stateArgs', 'updateArgs', ({ args }) => args), - withState('selecting', 'setSelecting', false), - withState('previewing', 'setPreviewing', false), - withState('isInvalid', 'setInvalid', false), - withState('stateDatasource', 'selectDatasource', ({ datasource }) => datasource), - withHandlers({ - resetArgs: ({ updateArgs, args }) => () => updateArgs(args), - }) -)(Component); +export const Datasource = connect( + mapStateToProps, + mapDispatchToProps, + mergeProps +)(DatasourceComponent); Datasource.propTypes = { done: PropTypes.func, diff --git a/x-pack/plugins/canvas/public/components/element_content/element_content.js b/x-pack/plugins/canvas/public/components/element_content/element_content.js index ce5dd1a99026172..ee0ce5193102e8b 100644 --- a/x-pack/plugins/canvas/public/components/element_content/element_content.js +++ b/x-pack/plugins/canvas/public/components/element_content/element_content.js @@ -7,7 +7,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { pure, compose, branch, renderComponent } from 'recompose'; import Style from 'style-it'; import { getType } from '@kbn/interpreter/common'; import { Loading } from '../loading'; @@ -16,40 +15,34 @@ import { ElementShareContainer } from '../element_share_container'; import { InvalidExpression } from './invalid_expression'; import { InvalidElementType } from './invalid_element_type'; -/* - Branches - Short circut rendering of the element if the element isn't ready or isn't valid. -*/ -const branches = [ - // no renderable or renderable config value, render loading - branch( - ({ renderable, state }) => { - return !state || !renderable; - }, - renderComponent(({ backgroundColor }) => ) - ), +const isLoading = (renderable, state) => !state || !renderable; - // renderable is available, but no matching element is found, render invalid - branch(({ renderable, renderFunction }) => { - return renderable && getType(renderable) !== 'render' && !renderFunction; - }, renderComponent(InvalidElementType)), +const isNotValidForRendering = (renderable, renderFunction) => + renderable && getType(renderable) !== 'render' && !renderFunction; - // error state, render invalid expression notice - branch(({ renderable, renderFunction, state }) => { - return ( - state === 'error' || // The renderable has an error - getType(renderable) !== 'render' || // The renderable isn't, well, renderable - !renderFunction // We can't find an element in the registry for this - ); - }, renderComponent(InvalidExpression)), -]; +const isNotValidExpression = (renderable, renderFunction, state) => + state === 'error' || // The renderable has an error + getType(renderable) !== 'render' || // The renderable isn't, well, renderable + !renderFunction; // We can't find an element in the registry for this -export const ElementContent = compose( - pure, - ...branches -)(({ renderable, renderFunction, width, height, handlers }) => { +export const ElementContent = (props) => { + const { renderable, renderFunction, width, height, handlers, backgroundColor, state } = props; const { onComplete } = handlers; + if (isLoading(renderable, state)) { + return ; + } + + // renderable is available, but no matching element is found, render invalid + if (isNotValidForRendering(renderable, renderFunction)) { + return ; + } + + // error state, render invalid expression notice + if (isNotValidExpression(renderable, renderFunction, state)) { + return ; + } + return Style.it( renderable.css,
); -}); +}; ElementContent.propTypes = { renderable: PropTypes.shape({ diff --git a/x-pack/plugins/canvas/public/functions/pie.test.js b/x-pack/plugins/canvas/public/functions/pie.test.js index f1ce8786f2c669d..ef180181701c9d1 100644 --- a/x-pack/plugins/canvas/public/functions/pie.test.js +++ b/x-pack/plugins/canvas/public/functions/pie.test.js @@ -6,9 +6,8 @@ */ import { testPie } from '../../canvas_plugin_src/functions/common/__fixtures__/test_pointseries'; -import { functionWrapper } from '../../../../../src/plugins/presentation_util/public'; +import { functionWrapper, fontStyle } from '../../../../../src/plugins/presentation_util/public'; import { - fontStyle, grayscalePalette, seriesStyle, } from '../../canvas_plugin_src/functions/common/__fixtures__/test_styles'; diff --git a/x-pack/plugins/canvas/public/functions/plot.test.js b/x-pack/plugins/canvas/public/functions/plot.test.js index a2eef889aa07ee4..b354c4c02b2f644 100644 --- a/x-pack/plugins/canvas/public/functions/plot.test.js +++ b/x-pack/plugins/canvas/public/functions/plot.test.js @@ -5,10 +5,9 @@ * 2.0. */ -import { functionWrapper } from '../../../../../src/plugins/presentation_util/public'; +import { functionWrapper, fontStyle } from '../../../../../src/plugins/presentation_util/public'; import { testPlot } from '../../canvas_plugin_src/functions/common/__fixtures__/test_pointseries'; import { - fontStyle, grayscalePalette, yAxisConfig, xAxisConfig, diff --git a/x-pack/plugins/canvas/public/functions/plot/get_font_spec.test.js b/x-pack/plugins/canvas/public/functions/plot/get_font_spec.test.js index e795ce82e234400..7a142a02412c2bc 100644 --- a/x-pack/plugins/canvas/public/functions/plot/get_font_spec.test.js +++ b/x-pack/plugins/canvas/public/functions/plot/get_font_spec.test.js @@ -4,8 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { fontStyle } from '../../../canvas_plugin_src/functions/common/__fixtures__/test_styles'; +import { fontStyle } from '../../../../../../src/plugins/presentation_util/common/lib'; import { defaultSpec, getFontSpec } from './get_font_spec'; describe('getFontSpec', () => { diff --git a/x-pack/plugins/canvas/public/index.ts b/x-pack/plugins/canvas/public/index.ts index 3fc212a060cd84f..a15b15fcae3337f 100644 --- a/x-pack/plugins/canvas/public/index.ts +++ b/x-pack/plugins/canvas/public/index.ts @@ -18,4 +18,5 @@ export interface WithKibanaProps { }; } -export const plugin = (_initializerContext: PluginInitializerContext) => new CanvasPlugin(); +export const plugin = (initializerContext: PluginInitializerContext) => + new CanvasPlugin(initializerContext); diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index 101f64e53b685d3..2df5bf56e278239 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -15,6 +15,7 @@ import { AppMountParameters, AppUpdater, DEFAULT_APP_CATEGORIES, + PluginInitializerContext, } from '../../../../src/core/public'; import { HomePublicPluginSetup } from '../../../../src/plugins/home/public'; import { initLoadingIndicator } from './lib/loading_indicator'; @@ -73,6 +74,11 @@ export type CanvasStart = void; export class CanvasPlugin implements Plugin { private appUpdater = new BehaviorSubject(() => ({})); + private initContext: PluginInitializerContext; + + constructor(initContext: PluginInitializerContext) { + this.initContext = initContext; + } public setup(coreSetup: CoreSetup, setupPlugins: CanvasSetupDeps) { const { api: canvasApi, registries } = getPluginApi(setupPlugins.expressions); @@ -105,7 +111,9 @@ export class CanvasPlugin srcPlugin.start(coreStart, startPlugins); const { pluginServices } = await import('./services'); - pluginServices.setRegistry(pluginServiceRegistry.start({ coreStart, startPlugins })); + pluginServices.setRegistry( + pluginServiceRegistry.start({ coreStart, startPlugins, initContext: this.initContext }) + ); // Load application bundle const { renderApp, initializeCanvas, teardownCanvas } = await import('./application'); diff --git a/x-pack/plugins/canvas/public/services/kibana/platform.ts b/x-pack/plugins/canvas/public/services/kibana/platform.ts index 79eae8d8081bbad..f71d480b552f605 100644 --- a/x-pack/plugins/canvas/public/services/kibana/platform.ts +++ b/x-pack/plugins/canvas/public/services/kibana/platform.ts @@ -15,12 +15,17 @@ export type CanvaPlatformServiceFactory = KibanaPluginServiceFactory< CanvasStartDeps >; -export const platformServiceFactory: CanvaPlatformServiceFactory = ({ coreStart }) => { +export const platformServiceFactory: CanvaPlatformServiceFactory = ({ coreStart, initContext }) => { + if (!initContext) { + throw new Error('Canvas platform service requires init context'); + } + return { getBasePath: coreStart.http.basePath.get, getBasePathInterface: () => coreStart.http.basePath, getElasticWebsiteUrl: () => coreStart.docLinks.ELASTIC_WEBSITE_URL, getDocLinkVersion: () => coreStart.docLinks.DOC_LINK_VERSION, + getKibanaVersion: () => initContext.env.packageInfo.version, // TODO: is there a better type for this? The capabilities type allows for a Record, // though we don't do this. So this cast may be the best option. getHasWriteAccess: () => coreStart.application.capabilities.canvas.save as boolean, diff --git a/x-pack/plugins/canvas/public/services/platform.ts b/x-pack/plugins/canvas/public/services/platform.ts index 7a452d809a6146b..b622484c16fc339 100644 --- a/x-pack/plugins/canvas/public/services/platform.ts +++ b/x-pack/plugins/canvas/public/services/platform.ts @@ -19,6 +19,7 @@ export interface CanvasPlatformService { getBasePathInterface: () => IBasePath; getDocLinkVersion: () => string; getElasticWebsiteUrl: () => string; + getKibanaVersion: () => string; getHasWriteAccess: () => boolean; getUISetting: (key: string, defaultValue?: any) => any; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; diff --git a/x-pack/plugins/canvas/public/services/stubs/platform.ts b/x-pack/plugins/canvas/public/services/stubs/platform.ts index 181d355df8a1cef..1daaa6bd32f09c5 100644 --- a/x-pack/plugins/canvas/public/services/stubs/platform.ts +++ b/x-pack/plugins/canvas/public/services/stubs/platform.ts @@ -24,6 +24,7 @@ export const platformServiceFactory: CanvasPlatformServiceFactory = () => ({ getBasePathInterface: noop, getDocLinkVersion: () => 'dockLinkVersion', getElasticWebsiteUrl: () => 'https://elastic.co', + getKibanaVersion: () => 'kibanaVersion', getHasWriteAccess: () => true, getUISetting, setBreadcrumbs: noop, diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx index db74dd7514ee9fa..ee609f42f1cf9a4 100644 --- a/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx +++ b/x-pack/plugins/canvas/shareable_runtime/components/__stories__/rendered_element.stories.tsx @@ -9,7 +9,7 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { ExampleContext } from '../../test/context_example'; -import { image } from '../../../canvas_plugin_src/renderers/image'; +import { imageFunction } from '../../../../../../src/plugins/expression_image/__fixtures__'; import { sharedWorkpads } from '../../test'; import { RenderedElement, RenderedElementComponent } from '../rendered_element'; @@ -30,7 +30,7 @@ storiesOf('shareables/RenderedElement', module) ; } /** @@ -64,7 +65,7 @@ export class RenderedElementComponent extends PureComponent { try { fn.render(this.ref.current, value.value, createHandlers()); - } catch (e) { + } catch (e: any) { // eslint-disable-next-line no-console console.log(as, e.message); } diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js index d5f0a2196814edd..0217351af9cb635 100644 --- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js +++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js @@ -5,14 +5,13 @@ * 2.0. */ -import { image } from '../canvas_plugin_src/renderers/image'; import { markdown } from '../canvas_plugin_src/renderers/markdown'; -import { metric } from '../canvas_plugin_src/renderers/metric'; import { pie } from '../canvas_plugin_src/renderers/pie'; import { plot } from '../canvas_plugin_src/renderers/plot'; import { progress } from '../canvas_plugin_src/renderers/progress'; import { table } from '../canvas_plugin_src/renderers/table'; import { text } from '../canvas_plugin_src/renderers/text'; +import { imageRenderer as image } from '../../../../src/plugins/expression_image/public'; import { errorRenderer as error, debugRenderer as debug, @@ -20,6 +19,7 @@ import { import { repeatImageRenderer as repeatImage } from '../../../../src/plugins/expression_repeat_image/public'; import { revealImageRenderer as revealImage } from '../../../../src/plugins/expression_reveal_image/public'; import { shapeRenderer as shape } from '../../../../src/plugins/expression_shape/public'; +import { metricRenderer as metric } from '../../../../src/plugins/expression_metric/public'; /** * This is a collection of renderers which are bundled with the runtime. If diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index 6181df5abe4648c..384a7716657f5d5 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -32,6 +32,8 @@ { "path": "../../../src/plugins/embeddable/tsconfig.json" }, { "path": "../../../src/plugins/expressions/tsconfig.json" }, { "path": "../../../src/plugins/expression_error/tsconfig.json" }, + { "path": "../../../src/plugins/expression_image/tsconfig.json" }, + { "path": "../../../src/plugins/expression_metric/tsconfig.json" }, { "path": "../../../src/plugins/expression_repeat_image/tsconfig.json" }, { "path": "../../../src/plugins/expression_reveal_image/tsconfig.json" }, { "path": "../../../src/plugins/expression_shape/tsconfig.json" }, diff --git a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx index 6f02a209f22dca5..983e32ba508fbb0 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/__mock__/index.tsx @@ -5,14 +5,16 @@ * 2.0. */ -import { ConnectorTypes } from '../../../../common'; +import { ActionTypeConnector, ConnectorTypes } from '../../../../common'; import { ActionConnector } from '../../../containers/configure/types'; import { UseConnectorsResponse } from '../../../containers/configure/use_connectors'; import { ReturnUseCaseConfigure } from '../../../containers/configure/use_configure'; import { UseActionTypesResponse } from '../../../containers/configure/use_action_types'; import { connectorsMock, actionTypesMock } from '../../../containers/configure/mock'; export { mappings } from '../../../containers/configure/mock'; + export const connectors: ActionConnector[] = connectorsMock; +export const actionTypes: ActionTypeConnector[] = actionTypesMock; export const searchURL = '?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))'; diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx index e5b1c66d01e1a01..0bda6fe185093fa 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.test.tsx @@ -11,7 +11,7 @@ import { mount, ReactWrapper } from 'enzyme'; import { Connectors, Props } from './connectors'; import { TestProviders } from '../../common/mock'; import { ConnectorsDropdown } from './connectors_dropdown'; -import { connectors } from './__mock__'; +import { connectors, actionTypes } from './__mock__'; import { ConnectorTypes } from '../../../common'; import { useKibana } from '../../common/lib/kibana'; @@ -24,6 +24,7 @@ describe('Connectors', () => { const handleShowEditFlyout = jest.fn(); const props: Props = { + actionTypes, connectors, disabled: false, handleShowEditFlyout, diff --git a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx index 45be02e05e1f09c..40f314a653882ed 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/connectors.tsx @@ -21,7 +21,7 @@ import * as i18n from './translations'; import { ActionConnector, CaseConnectorMapping } from '../../containers/configure/types'; import { Mapping } from './mapping'; -import { ConnectorTypes } from '../../../common'; +import { ActionTypeConnector, ConnectorTypes } from '../../../common'; const EuiFormRowExtended = styled(EuiFormRow)` .euiFormRow__labelWrapper { @@ -32,6 +32,7 @@ const EuiFormRowExtended = styled(EuiFormRow)` `; export interface Props { + actionTypes: ActionTypeConnector[]; connectors: ActionConnector[]; disabled: boolean; handleShowEditFlyout: () => void; @@ -42,6 +43,7 @@ export interface Props { updateConnectorDisabled: boolean; } const ConnectorsComponent: React.FC = ({ + actionTypes, connectors, disabled, handleShowEditFlyout, @@ -56,6 +58,11 @@ const ConnectorsComponent: React.FC = ({ [connectors, selectedConnector.id] ); + const actionTypeName = useMemo( + () => actionTypes.find((c) => c.id === selectedConnector.type)?.name ?? 'Unknown', + [actionTypes, selectedConnector.type] + ); + const dropDownLabel = useMemo( () => ( @@ -103,7 +110,7 @@ const ConnectorsComponent: React.FC = ({ {selectedConnector.type !== ConnectorTypes.none ? ( diff --git a/x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx index 73f48654b229957..8d235f31277fe26 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.test.tsx @@ -12,24 +12,16 @@ import { FieldMapping, FieldMappingProps } from './field_mapping'; import { mappings } from './__mock__'; import { TestProviders } from '../../common/mock'; import { FieldMappingRowStatic } from './field_mapping_row_static'; -import { useKibana } from '../../common/lib/kibana'; - -jest.mock('../../common/lib/kibana'); -const useKibanaMock = useKibana as jest.Mocked; describe('FieldMappingRow', () => { let wrapper: ReactWrapper; const props: FieldMappingProps = { + actionTypeName: 'ServiceNow ITSM', isLoading: false, mappings, - connectorActionTypeId: '.servicenow', }; beforeAll(() => { - useKibanaMock().services.triggersActionsUi.actionTypeRegistry.get = jest.fn().mockReturnValue({ - actionTypeTitle: '.servicenow', - iconClass: 'logoSecurity', - }); wrapper = mount(, { wrappingComponent: TestProviders }); }); @@ -61,4 +53,13 @@ describe('FieldMappingRow', () => { expect(row.prop('selectedThirdParty')).toEqual(mappings[index].target); }); }); + + test('displays the label of the second column correctly', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-field-mappings-second-col-label"]') + .first() + .text() + ).toBe('ServiceNow ITSM field'); + }); }); diff --git a/x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx index b5ce978a1b481ad..fe99f718c140127 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/field_mapping.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useMemo } from 'react'; +import React from 'react'; import { EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import styled from 'styled-components'; @@ -13,7 +13,6 @@ import { FieldMappingRowStatic } from './field_mapping_row_static'; import * as i18n from './translations'; import { CaseConnectorMapping } from '../../containers/configure/types'; -import { useKibana } from '../../common/lib/kibana'; const FieldRowWrapper = styled.div` margin: 10px 0; @@ -21,22 +20,16 @@ const FieldRowWrapper = styled.div` `; export interface FieldMappingProps { - connectorActionTypeId: string; + actionTypeName: string; isLoading: boolean; mappings: CaseConnectorMapping[]; } const FieldMappingComponent: React.FC = ({ - connectorActionTypeId, + actionTypeName, isLoading, mappings, }) => { - const { triggersActionsUi } = useKibana().services; - const selectedConnector = useMemo( - () => triggersActionsUi.actionTypeRegistry.get(connectorActionTypeId) ?? { fields: {} }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [connectorActionTypeId] - ); return mappings.length ? ( @@ -45,10 +38,8 @@ const FieldMappingComponent: React.FC = ({ {i18n.FIELD_MAPPING_FIRST_COL} - - - {i18n.FIELD_MAPPING_SECOND_COL(selectedConnector.actionTypeTitle ?? '')} - + + {i18n.FIELD_MAPPING_SECOND_COL(actionTypeName)} {i18n.FIELD_MAPPING_THIRD_COL} diff --git a/x-pack/plugins/cases/public/components/configure_cases/index.tsx b/x-pack/plugins/cases/public/components/configure_cases/index.tsx index ac43ec05319a0be..d4e2ad36d49575a 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/index.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/index.tsx @@ -205,6 +205,7 @@ const ConfigureCasesComponent: React.FC> = ({ ; describe('Mapping', () => { const props: MappingProps = { - connectorActionTypeId: '.servicenow', + actionTypeName: 'ServiceNow ITSM', isLoading: false, mappings, }; - beforeEach(() => { - jest.clearAllMocks(); - useKibanaMock().services.triggersActionsUi.actionTypeRegistry.get = jest.fn().mockReturnValue({ - actionTypeTitle: 'ServiceNow ITSM', - iconClass: 'logoSecurity', - }); - }); - test('it shows mapping form group', () => { const wrapper = mount(, { wrappingComponent: TestProviders }); expect(wrapper.find('[data-test-subj="static-mappings"]').first().exists()).toBe(true); @@ -45,6 +33,21 @@ describe('Mapping', () => { 'short_description' ); }); + + test('displays the title correctly', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + expect(wrapper.find('[data-test-subj="field-mapping-text"] h4').first().text()).toBe( + 'ServiceNow ITSM field mappings' + ); + }); + + test('displays the description correctly', () => { + const wrapper = mount(, { wrappingComponent: TestProviders }); + expect(wrapper.find('[data-test-subj="field-mapping-desc"]').first().text()).toBe( + 'Map Case fields to ServiceNow ITSM fields when pushing data to ServiceNow ITSM. Field mappings require an established connection to ServiceNow ITSM.' + ); + }); + test('displays connection warning when isLoading: false and mappings: []', () => { const wrapper = mount(, { wrappingComponent: TestProviders, diff --git a/x-pack/plugins/cases/public/components/configure_cases/mapping.tsx b/x-pack/plugins/cases/public/components/configure_cases/mapping.tsx index 16c02606ae90b80..eb14c22b900c437 100644 --- a/x-pack/plugins/cases/public/components/configure_cases/mapping.tsx +++ b/x-pack/plugins/cases/public/components/configure_cases/mapping.tsx @@ -14,42 +14,32 @@ import * as i18n from './translations'; import { FieldMapping } from './field_mapping'; import { CaseConnectorMapping } from '../../containers/configure/types'; -import { useKibana } from '../../common/lib/kibana'; export interface MappingProps { - connectorActionTypeId: string; + actionTypeName: string; isLoading: boolean; mappings: CaseConnectorMapping[]; } -const MappingComponent: React.FC = ({ - connectorActionTypeId, - isLoading, - mappings, -}) => { - const { triggersActionsUi } = useKibana().services; - const selectedConnector = useMemo( - () => triggersActionsUi.actionTypeRegistry.get(connectorActionTypeId), - [connectorActionTypeId, triggersActionsUi] - ); +const MappingComponent: React.FC = ({ actionTypeName, isLoading, mappings }) => { const fieldMappingDesc: { desc: string; color: TextColor } = useMemo( () => mappings.length > 0 || isLoading ? { - desc: i18n.FIELD_MAPPING_DESC(selectedConnector.actionTypeTitle ?? ''), + desc: i18n.FIELD_MAPPING_DESC(actionTypeName), color: 'subdued', } : { - desc: i18n.FIELD_MAPPING_DESC_ERR(selectedConnector.actionTypeTitle ?? ''), + desc: i18n.FIELD_MAPPING_DESC_ERR(actionTypeName), color: 'danger', }, - [isLoading, mappings.length, selectedConnector.actionTypeTitle] + [isLoading, mappings.length, actionTypeName] ); return ( - -

{i18n.FIELD_MAPPING_TITLE(selectedConnector.actionTypeTitle ?? '')}

+ +

{i18n.FIELD_MAPPING_TITLE(actionTypeName)}

{fieldMappingDesc.desc} @@ -57,7 +47,7 @@ const MappingComponent: React.FC = ({
= memo( ({ children, startServices, config, history, kibanaVersion, extensions, routerHistory }) => { const isDarkMode = useObservable(startServices.uiSettings.get$('theme:darkMode')); - const [routerHistoryInstance] = useState(routerHistory || createHashHistory()); - // Sync our hash history with Kibana scoped history - useEffect(() => { - const unlistenParentHistory = history.listen(() => { - const newHash = createHashHistory(); - if (newHash.location.pathname !== routerHistoryInstance.location.pathname) { - routerHistoryInstance.replace(newHash.location.pathname + newHash.location.search || ''); - } - }); - - return unlistenParentHistory; - }, [history, routerHistoryInstance]); return ( - - - - - - - - - - + + + + + + + + + + {children} - - - - - - - - - + + + + + + + + + ); } ); @@ -277,7 +266,7 @@ const FleetTopNav = memo( defaultMessage: 'Fleet settings', }), iconType: 'gear', - run: () => (window.location.href = getModalHref('settings')), + run: () => services.application.navigateToUrl(getModalHref('settings')), }, ]; return ( @@ -327,7 +316,26 @@ export const AppRoutes = memo( - + { + // BWC < 7.15 Fleet was using a hash router: redirect old routes using hash + const shouldRedirectHash = location.pathname === '' && location.hash.length > 0; + if (!shouldRedirectHash) { + return ; + } + const pathname = location.hash.replace(/^#(\/fleet)?/, ''); + + return ( + + ); + }} + /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx index c0c425447e55678..3b0ab9c62ca11c0 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_breadcrumbs.tsx @@ -150,18 +150,30 @@ const breadcrumbGetters: { }; export function useBreadcrumbs(page: Page, values: DynamicPagePathValues = {}) { - const { chrome, http } = useStartServices(); + const { chrome, http, application } = useStartServices(); const breadcrumbs = - breadcrumbGetters[page]?.(values).map((breadcrumb) => ({ - ...breadcrumb, - href: breadcrumb.href + breadcrumbGetters[page]?.(values).map((breadcrumb) => { + const href = breadcrumb.href ? http.basePath.prepend( - `${breadcrumb.useIntegrationsBasePath ? INTEGRATIONS_BASE_PATH : FLEET_BASE_PATH}#${ + `${breadcrumb.useIntegrationsBasePath ? INTEGRATIONS_BASE_PATH : FLEET_BASE_PATH}${ breadcrumb.href }` ) - : undefined, - })) || []; + : undefined; + return { + ...breadcrumb, + href, + onClick: href + ? (ev: React.MouseEvent) => { + if (ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey) { + return; + } + ev.preventDefault(); + application.navigateToUrl(href); + } + : undefined, + }; + }) || []; const docTitle: string[] = [...breadcrumbs] .reverse() .map((breadcrumb) => breadcrumb.text as string); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx index 060c49a84c5aaec..476a5d8e029d341 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/layout.tsx @@ -43,7 +43,10 @@ export const CreatePackagePolicyPageLayout: React.FunctionComponent<{ 'data-test-subj': dataTestSubj, }) => { const pageTitle = useMemo(() => { - if ((from === 'package' || from === 'package-edit' || from === 'edit') && packageInfo) { + if ( + (from === 'package' || from === 'package-edit' || from === 'edit' || from === 'policy') && + packageInfo + ) { return ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx index a624d8ced918038..c115089cccb1e6c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.test.tsx @@ -31,7 +31,7 @@ describe('when on the package policy create page', () => { beforeEach(() => { testRenderer = createFleetTestRendererMock(); mockApiCalls(testRenderer.startServices.http); - testRenderer.history.push(createPageUrlPath); + testRenderer.mountHistory.push(createPageUrlPath); }); describe('and Route state is provided via Fleet HashRouter', () => { @@ -43,7 +43,7 @@ describe('when on the package policy create page', () => { onCancelNavigateTo: [PLUGIN_ID, { path: '/cancel/url/here' }], }; - testRenderer.history.replace({ + testRenderer.mountHistory.replace({ pathname: createPageUrlPath, state: expectedRouteState, }); @@ -72,18 +72,18 @@ describe('when on the package policy create page', () => { expect(cancelButton.href).toBe(expectedRouteState.onCancelUrl); }); - it('should redirect via Fleet HashRouter when cancel link is clicked', () => { + it('should redirect via history when cancel link is clicked', () => { act(() => { cancelLink.click(); }); - expect(testRenderer.history.location.pathname).toBe('/cancel/url/here'); + expect(testRenderer.mountHistory.location.pathname).toBe('/cancel/url/here'); }); - it('should redirect via Fleet HashRouter when cancel Button (button bar) is clicked', () => { + it('should redirect via history when cancel Button (button bar) is clicked', () => { act(() => { cancelButton.click(); }); - expect(testRenderer.history.location.pathname).toBe('/cancel/url/here'); + expect(testRenderer.mountHistory.location.pathname).toBe('/cancel/url/here'); }); }); }); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx index 39340a21d349bd8..e19cb7b1ca5e83e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/no_package_policies.tsx @@ -39,7 +39,7 @@ export const NoPackagePolicies = memo<{ policyId: string }>(({ policyId }) => { fill onClick={() => application.navigateToApp(INTEGRATIONS_PLUGIN_ID, { - path: `#${pagePathGetters.integrations_all()[1]}`, + path: pagePathGetters.integrations_all()[1], state: { forAgentPolicyId: policyId }, }) } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index 49af14b7234fa2a..0d2d8e188218367 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -199,7 +199,7 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ iconType="refresh" onClick={() => { application.navigateToApp(INTEGRATIONS_PLUGIN_ID, { - path: `#${pagePathGetters.integrations_all()[1]}`, + path: pagePathGetters.integrations_all()[1], state: { forAgentPolicyId: agentPolicy.id }, }); }} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx index d8db44e28e4af9c..19f0216a39e037c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/index.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { HashRouter as Router, Switch, Route } from 'react-router-dom'; +import { Router, Switch, Route, useHistory } from 'react-router-dom'; import { FLEET_ROUTING_PATHS } from '../../constants'; import { useBreadcrumbs } from '../../hooks'; @@ -20,9 +20,10 @@ import { EditPackagePolicyPage } from './edit_package_policy_page'; export const AgentPolicyApp: React.FunctionComponent = () => { useBreadcrumbs('policies'); + const history = useHistory(); return ( - + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index 52a4c9d17648b7d..494541a00fc0a4e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { HashRouter as Router, Route, Switch } from 'react-router-dom'; +import { Router, Route, Switch, useHistory } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPortal } from '@elastic/eui'; import { FLEET_ROUTING_PATHS } from '../../constants'; @@ -30,7 +30,7 @@ import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal export const AgentsApp: React.FunctionComponent = () => { useBreadcrumbs('agent_list'); - + const history = useHistory(); const { agents } = useConfig(); const capabilities = useCapabilities(); @@ -118,7 +118,7 @@ export const AgentsApp: React.FunctionComponent = () => { ) : undefined; return ( - + diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx index c660d3ed2976721..51c8346a665cf0e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/index.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { HashRouter as Router, Route, Switch } from 'react-router-dom'; +import { Router, Route, Switch, useHistory } from 'react-router-dom'; import { FLEET_ROUTING_PATHS } from '../../constants'; import { DefaultLayout } from '../../layouts'; @@ -14,8 +14,10 @@ import { DefaultLayout } from '../../layouts'; import { DataStreamListPage } from './list_page'; export const DataStreamApp: React.FunctionComponent = () => { + const history = useHistory(); + return ( - + diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index ae59d33e44b82f5..f0c94b51677eefb 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -9,7 +9,6 @@ import React, { memo, useEffect, useState } from 'react'; import type { AppMountParameters } from 'kibana/public'; import { EuiCode, EuiEmptyPrompt, EuiErrorBoundary, EuiPanel, EuiPortal } from '@elastic/eui'; import type { History } from 'history'; -import { createHashHistory } from 'history'; import { Router, Redirect, Route, Switch } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -26,7 +25,10 @@ import { import type { FleetConfigType, FleetStartServices } from '../../plugin'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { + KibanaContextProvider, + RedirectAppLinks, +} from '../../../../../../src/plugins/kibana_react/public'; import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { AgentPolicyContextProvider, useUrlModal } from './hooks'; @@ -39,7 +41,7 @@ import type { UIExtensionsStorage } from './types'; import { EPMApp } from './sections/epm'; import { DefaultLayout, WithoutHeaderLayout } from './layouts'; import { PackageInstallProvider } from './hooks'; -import { useBreadcrumbs, IntraAppStateProvider, UIExtensionsContext } from './hooks'; +import { useBreadcrumbs, UIExtensionsContext } from './hooks'; const ErrorLayout = ({ children }: { children: JSX.Element }) => ( @@ -185,25 +187,12 @@ export const IntegrationsAppContext: React.FC<{ kibanaVersion: string; extensions: UIExtensionsStorage; /** For testing purposes only */ - routerHistory?: History; -}> = memo( - ({ children, startServices, config, history, kibanaVersion, extensions, routerHistory }) => { - const isDarkMode = useObservable(startServices.uiSettings.get$('theme:darkMode')); - const [routerHistoryInstance] = useState(routerHistory || createHashHistory()); - - // Sync our hash history with Kibana scoped history - useEffect(() => { - const unlistenParentHistory = history.listen(() => { - const newHash = createHashHistory(); - if (newHash.location.pathname !== routerHistoryInstance.location.pathname) { - routerHistoryInstance.replace(newHash.location.pathname + newHash.location.search || ''); - } - }); - - return unlistenParentHistory; - }, [history, routerHistoryInstance]); + routerHistory?: History; // TODO remove +}> = memo(({ children, startServices, config, history, kibanaVersion, extensions }) => { + const isDarkMode = useObservable(startServices.uiSettings.get$('theme:darkMode')); - return ( + return ( + @@ -212,15 +201,13 @@ export const IntegrationsAppContext: React.FC<{ - - - - - {children} - - - - + + + + {children} + + + @@ -229,9 +216,9 @@ export const IntegrationsAppContext: React.FC<{ - ); - } -); + + ); +}); export const AppRoutes = memo(() => { const { modal, setModal } = useUrlModal(); @@ -250,7 +237,26 @@ export const AppRoutes = memo(() => { - + { + // BWC < 7.15 Fleet was using a hash router: redirect old routes using hash + const shouldRedirectHash = location.pathname === '' && location.hash.length > 0; + if (!shouldRedirectHash) { + return ; + } + const pathname = location.hash.replace(/^#/, ''); + + return ( + + ); + }} + /> ); diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx index 19f72fdc69bba52..63c8f1cbd318c4c 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_breadcrumbs.tsx @@ -51,14 +51,23 @@ const breadcrumbGetters: { }; export function useBreadcrumbs(page: Page, values: DynamicPagePathValues = {}) { - const { chrome, http } = useStartServices(); + const { chrome, http, application } = useStartServices(); const breadcrumbs: ChromeBreadcrumb[] = - breadcrumbGetters[page]?.(values).map((breadcrumb) => ({ - ...breadcrumb, - href: breadcrumb.href - ? http.basePath.prepend(`${INTEGRATIONS_BASE_PATH}#${breadcrumb.href}`) - : undefined, - })) || []; + breadcrumbGetters[page]?.(values).map((breadcrumb) => { + const href = breadcrumb.href + ? http.basePath.prepend(`${INTEGRATIONS_BASE_PATH}${breadcrumb.href}`) + : undefined; + return { + ...breadcrumb, + href, + onClick: href + ? (ev: React.MouseEvent) => { + ev.preventDefault(); + application.navigateToUrl(href); + } + : undefined, + }; + }) || []; const docTitle: string[] = [...breadcrumbs] .reverse() .map((breadcrumb) => breadcrumb.text as string); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx index f436c248abd3c35..31a3e2164a247c9 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.test.tsx @@ -45,22 +45,22 @@ describe('when on integration detail', () => { )); - beforeEach(() => { + beforeEach(async () => { testRenderer = createIntegrationsTestRendererMock(); mockedApi = mockApiCalls(testRenderer.startServices.http); - testRenderer.history.push(detailPageUrlPath); + act(() => testRenderer.mountHistory.push(detailPageUrlPath)); }); afterEach(() => { cleanup(); - window.location.hash = '#/'; }); describe('and the package is installed', () => { beforeEach(() => render()); it('should display agent policy usage count', async () => { - await mockedApi.waitForApi(); + await act(() => mockedApi.waitForApi()); + expect(renderResult.queryByTestId('agentPolicyCount')).not.toBeNull(); }); @@ -105,11 +105,11 @@ describe('when on integration detail', () => { it('should redirect if custom url is accessed', () => { act(() => { - testRenderer.history.push( + testRenderer.mountHistory.push( pagePathGetters.integration_details_custom({ pkgkey: 'nginx-0.3.7' })[1] ); }); - expect(testRenderer.history.location.pathname).toEqual('/detail/nginx-0.3.7/overview'); + expect(testRenderer.mountHistory.location.pathname).toEqual('/detail/nginx-0.3.7/overview'); }); }); @@ -153,7 +153,7 @@ describe('when on integration detail', () => { it('should display custom content when tab is clicked', async () => { act(() => { - testRenderer.history.push( + testRenderer.mountHistory.push( pagePathGetters.integration_details_custom({ pkgkey: 'nginx-0.3.7' })[1] ); }); @@ -200,7 +200,7 @@ describe('when on integration detail', () => { it('should display custom assets when tab is clicked', async () => { act(() => { - testRenderer.history.push( + testRenderer.mountHistory.push( pagePathGetters.integration_details_assets({ pkgkey: 'nginx-0.3.7' })[1] ); }); @@ -215,7 +215,7 @@ describe('when on integration detail', () => { it('should link to the create page', () => { const addButton = renderResult.getByTestId('addIntegrationPolicyButton') as HTMLAnchorElement; expect(addButton.href).toEqual( - 'http://localhost/mock/app/fleet#/integrations/nginx-0.3.7/add-integration' + 'http://localhost/mock/app/fleet/integrations/nginx-0.3.7/add-integration' ); }); }); @@ -223,7 +223,7 @@ describe('when on integration detail', () => { describe('and on the Policies Tab', () => { const policiesTabURLPath = pagePathGetters.integration_details_policies({ pkgkey })[1]; beforeEach(() => { - testRenderer.history.push(policiesTabURLPath); + testRenderer.mountHistory.push(policiesTabURLPath); render(); }); @@ -238,7 +238,7 @@ describe('when on integration detail', () => { 'integrationNameLink' )[0] as HTMLAnchorElement; expect(firstPolicy.href).toEqual( - 'http://localhost/mock/app/integrations#/edit-integration/e8a37031-2907-44f6-89d2-98bd493f60dc' + 'http://localhost/mock/app/integrations/edit-integration/e8a37031-2907-44f6-89d2-98bd493f60dc' ); }); diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx index 21a139ad11baab0..26869f8fea5747a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/index.tsx @@ -235,22 +235,18 @@ export function Detail() { redirectToPath = [ PLUGIN_ID, { - path: `#${ - pagePathGetters.policy_details({ - policyId: agentPolicyIdFromContext, - })[1] - }`, + path: pagePathGetters.policy_details({ + policyId: agentPolicyIdFromContext, + })[1], }, ]; } else { redirectToPath = [ INTEGRATIONS_PLUGIN_ID, { - path: `#${ - pagePathGetters.integration_details_policies({ - pkgkey, - })[1] - }`, + path: pagePathGetters.integration_details_policies({ + pkgkey, + })[1], }, ]; } @@ -260,16 +256,16 @@ export function Detail() { onCancelNavigateTo: [ INTEGRATIONS_PLUGIN_ID, { - path: currentPath, + path: pagePathGetters.integration_details_overview({ + pkgkey, + })[1], }, ], onCancelUrl: currentPath, }; services.application.navigateToApp(PLUGIN_ID, { - // Necessary because of Fleet's HashRouter. Can be changed when - // https://github.com/elastic/kibana/issues/96134 is resolved - path: `#${path}`, + path, state: redirectBackRouteState, }); }, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx index 8ec5fd83a125430..b7f044040e43f5f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx @@ -273,6 +273,7 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps agentPolicy={agentPolicy} packagePolicy={packagePolicy} viewDataStep={viewDataStep} + showAddAgent={true} /> ); }, diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx index 14f378bc379a67a..3b161a375e7ce0a 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/settings/settings.tsx @@ -53,7 +53,7 @@ const LatestVersionLink = ({ name, version }: { name: string; version: string }) pkgkey: `${name}-${version}`, }); return ( - + = ({ agentPolicy, packagePolicy, viewDataStep }) => { + showAddAgent?: boolean; +}> = ({ agentPolicy, packagePolicy, viewDataStep, showAddAgent }) => { const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); const { getHref } = useLink(); const hasWriteCapabilities = useCapabilities().write; @@ -47,19 +48,23 @@ export const PackagePolicyActionsMenu: React.FunctionComponent<{ // defaultMessage="View integration" // /> // , - { - setIsActionsMenuOpen(false); - setIsEnrollmentFlyoutOpen(true); - }} - key="addAgent" - > - - , + ...(showAddAgent + ? [ + { + setIsActionsMenuOpen(false); + setIsEnrollmentFlyoutOpen(true); + }} + key="addAgent" + > + + , + ] + : []), { - forRoute: string; - routeState?: S; -} - -const IntraAppStateContext = React.createContext({ forRoute: '' }); -const wasHandled = new WeakSet(); - -/** - * Provides a bridget between Kibana's ScopedHistory instance (normally used with BrowserRouter) - * and the Hash router used within the app in order to enable state to be used between kibana - * apps - */ -export const IntraAppStateProvider = memo<{ - kibanaScopedHistory: AppMountParameters['history']; - children: React.ReactNode; -}>(({ kibanaScopedHistory, children }) => { - const internalAppToAppState = useMemo(() => { - return { - forRoute: new URL(`${kibanaScopedHistory.location.hash.substr(1)}`, 'http://localhost') - .pathname, - routeState: kibanaScopedHistory.location.state as AnyIntraAppRouteState, - }; - }, [kibanaScopedHistory.location.state, kibanaScopedHistory.location.hash]); - return ( - - {children} - - ); -}); - /** * Retrieve UI Route state from the React Router History for the current URL location. * This state can be used by other Kibana Apps to influence certain behaviours in Ingest, for example, * redirecting back to an given Application after a craete action. */ -export function useIntraAppState(): - | IntraAppState['routeState'] - | undefined { +export function useIntraAppState(): S | undefined { const location = useLocation(); - const intraAppState = useContext(IntraAppStateContext); - if (!intraAppState) { - throw new Error('Hook called outside of IntraAppStateContext'); - } - return useMemo(() => { - // Due to the use of HashRouter in Ingest, we only want state to be returned - // once so that it does not impact navigation to the page from within the - // ingest app. side affect is that the browser back button would not work - // consistently either. - - if (location.pathname === intraAppState.forRoute && !wasHandled.has(intraAppState)) { - wasHandled.add(intraAppState); - return intraAppState.routeState as S; - } - // Default is to return the state in the Fleet HashRouter, in order to enable use of route state - // that is used via Kibana's ScopedHistory from within the Fleet HashRouter (ex. things like - // `core.application.navigateTo()` - // Once this https://github.com/elastic/kibana/issues/70358 is implemented (move to BrowserHistory - // using kibana's ScopedHistory), then this work-around can be removed. - return location.state as S; - }, [intraAppState, location.pathname, location.state]); + return location.state as S; } diff --git a/x-pack/plugins/fleet/public/hooks/use_link.ts b/x-pack/plugins/fleet/public/hooks/use_link.ts index 6917e0f5c3b8e71..846ca9d0fdafaac 100644 --- a/x-pack/plugins/fleet/public/hooks/use_link.ts +++ b/x-pack/plugins/fleet/public/hooks/use_link.ts @@ -27,7 +27,7 @@ export const useLink = () => { core.http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/${path}`), getHref: (page: StaticPage | DynamicPage, values?: DynamicPagePathValues) => { const [basePath, path] = getSeparatePaths(page, values); - return core.http.basePath.prepend(`${basePath}#${path}`); + return core.http.basePath.prepend(`${basePath}${path}`); }, }; }; diff --git a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx index 71c9650709ee2cd..d0724545ee90258 100644 --- a/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx +++ b/x-pack/plugins/fleet/public/mock/create_test_renderer.tsx @@ -6,7 +6,7 @@ */ import type { History } from 'history'; -import { createMemoryHistory, createHashHistory } from 'history'; +import { createMemoryHistory } from 'history'; import React, { memo } from 'react'; import type { RenderOptions, RenderResult } from '@testing-library/react'; import { render as reactRender, act } from '@testing-library/react'; @@ -47,9 +47,10 @@ export const createFleetTestRendererMock = (): TestRenderer => { const basePath = '/mock'; const extensions: UIExtensionsStorage = {}; const startServices = createStartServices(basePath); + const history = createMemoryHistory({ initialEntries: [basePath] }); const testRendererMocks: TestRenderer = { - history: createHashHistory(), - mountHistory: new ScopedHistory(createMemoryHistory({ initialEntries: [basePath] }), basePath), + history, + mountHistory: new ScopedHistory(history, basePath), startServices, config: createConfigurationMock(), startInterface: createStartMock(extensions), @@ -89,7 +90,7 @@ export const createIntegrationsTestRendererMock = (): TestRenderer => { const extensions: UIExtensionsStorage = {}; const startServices = createStartServices(basePath); const testRendererMocks: TestRenderer = { - history: createHashHistory(), + history: createMemoryHistory(), mountHistory: new ScopedHistory(createMemoryHistory({ initialEntries: [basePath] }), basePath), startServices, config: createConfigurationMock(), diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 0606334737a2a0f..2c723a326973704 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -104,6 +104,7 @@ export class FleetPlugin implements Plugin { const [coreStartServices, startDepsServices] = (await core.getStartServices()) as [ CoreStart, diff --git a/x-pack/plugins/fleet/public/search_provider.test.ts b/x-pack/plugins/fleet/public/search_provider.test.ts index 521337c9dda6be2..8eee18710d477fb 100644 --- a/x-pack/plugins/fleet/public/search_provider.test.ts +++ b/x-pack/plugins/fleet/public/search_provider.test.ts @@ -92,7 +92,7 @@ describe('Package search provider', () => { title: 'test', type: 'integration', url: { - path: 'undefined#/detail/test-test/overview', + path: 'undefined/detail/test-test/overview', prependBasePath: false, }, }, @@ -102,7 +102,7 @@ describe('Package search provider', () => { title: 'test1', type: 'integration', url: { - path: 'undefined#/detail/test1-test1/overview', + path: 'undefined/detail/test1-test1/overview', prependBasePath: false, }, }, @@ -175,7 +175,7 @@ describe('Package search provider', () => { title: 'test1', type: 'integration', url: { - path: 'undefined#/detail/test1-test1/overview', + path: 'undefined/detail/test1-test1/overview', prependBasePath: false, }, }, @@ -231,7 +231,7 @@ describe('Package search provider', () => { title: 'test', type: 'integration', url: { - path: 'undefined#/detail/test-test/overview', + path: 'undefined/detail/test-test/overview', prependBasePath: false, }, }, @@ -241,7 +241,7 @@ describe('Package search provider', () => { title: 'test1', type: 'integration', url: { - path: 'undefined#/detail/test1-test1/overview', + path: 'undefined/detail/test1-test1/overview', prependBasePath: false, }, }, @@ -274,7 +274,7 @@ describe('Package search provider', () => { title: 'test1', type: 'integration', url: { - path: 'undefined#/detail/test1-test1/overview', + path: 'undefined/detail/test1-test1/overview', prependBasePath: false, }, }, diff --git a/x-pack/plugins/fleet/public/search_provider.ts b/x-pack/plugins/fleet/public/search_provider.ts index 9705f4da50f94e4..5f53c0a8e44ba4d 100644 --- a/x-pack/plugins/fleet/public/search_provider.ts +++ b/x-pack/plugins/fleet/public/search_provider.ts @@ -45,10 +45,8 @@ const toSearchResult = ( title: pkg.title, score: 80, url: { - // TODO: See https://github.com/elastic/kibana/issues/96134 for details about why we use '#' here. Below should be updated - // as part of migrating to non-hash based router. // prettier-ignore - path: `${application.getUrlForApp(INTEGRATIONS_PLUGIN_ID)}#${pagePathGetters.integration_details_overview({ pkgkey })[1]}`, + path: `${application.getUrlForApp(INTEGRATIONS_PLUGIN_ID)}${pagePathGetters.integration_details_overview({ pkgkey })[1]}`, prependBasePath: false, }, }; diff --git a/x-pack/plugins/graph/public/angular/templates/index.html b/x-pack/plugins/graph/public/angular/templates/index.html index 10bbb2e8ec6c73a..14c37cab9d9fd42 100644 --- a/x-pack/plugins/graph/public/angular/templates/index.html +++ b/x-pack/plugins/graph/public/angular/templates/index.html @@ -3,51 +3,14 @@ -
-
-
- -
- http://host:port/{{ selectedIndex.name }}/_graph/explore - - -
-
-
-
-
+ + + +
{ @@ -119,7 +120,7 @@ const mainTemplate = (basePath: string) => `
const moduleName = 'app/graph'; -const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react', 'ui.bootstrap', 'ui.ace']; +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react', 'ui.bootstrap']; function mountGraphApp(appBasePath: string, element: HTMLElement) { const mountpoint = document.createElement('div'); diff --git a/x-pack/plugins/graph/public/components/inspect_panel/inspect_panel.tsx b/x-pack/plugins/graph/public/components/inspect_panel/inspect_panel.tsx new file mode 100644 index 000000000000000..2f29849bebcecca --- /dev/null +++ b/x-pack/plugins/graph/public/components/inspect_panel/inspect_panel.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo, useState } from 'react'; +import { EuiTab, EuiTabs, EuiText } from '@elastic/eui'; +import { monaco, XJsonLang } from '@kbn/monaco'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { IUiSettingsClient } from 'kibana/public'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; +import { + CodeEditor, + KibanaContextProvider, +} from '../../../../../../src/plugins/kibana_react/public'; + +interface InspectPanelProps { + showInspect?: boolean; + indexPattern?: IndexPattern; + uiSettings: IUiSettingsClient; + lastRequest?: string; + lastResponse?: string; +} + +const CODE_EDITOR_OPTIONS: monaco.editor.IStandaloneEditorConstructionOptions = { + automaticLayout: true, + fontSize: 12, + lineNumbers: 'on', + minimap: { + enabled: false, + }, + overviewRulerBorder: false, + readOnly: true, + scrollbar: { + alwaysConsumeMouseWheel: false, + }, + scrollBeyondLastLine: false, + wordWrap: 'on', + wrappingIndent: 'indent', +}; + +const dummyCallback = () => {}; + +export const InspectPanel = ({ + showInspect, + lastRequest, + lastResponse, + indexPattern, + uiSettings, +}: InspectPanelProps) => { + const [selectedTabId, setSelectedTabId] = useState('request'); + + const onRequestClick = () => setSelectedTabId('request'); + const onResponseClick = () => setSelectedTabId('response'); + + const services = useMemo(() => ({ uiSettings }), [uiSettings]); + + const editorContent = useMemo(() => (selectedTabId === 'request' ? lastRequest : lastResponse), [ + selectedTabId, + lastRequest, + lastResponse, + ]); + + if (showInspect) { + return ( + +
+
+
+ +
+ +
+ + http://host:port/{indexPattern?.id}/_graph/explore + + + + + + + + + + +
+
+
+
+ ); + } + + return null; +}; diff --git a/x-pack/plugins/graph/public/plugin.ts b/x-pack/plugins/graph/public/plugin.ts index ec19e639b91c96b..70671260ce5b9a4 100644 --- a/x-pack/plugins/graph/public/plugin.ts +++ b/x-pack/plugins/graph/public/plugin.ts @@ -110,6 +110,7 @@ export class GraphPlugin indexPatterns: pluginsStart.data!.indexPatterns, overlays: coreStart.overlays, savedObjects: pluginsStart.savedObjects, + uiSettings: core.uiSettings, }); }, }); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index a1bdbdfd14c1294..fc48bd8009cf214 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -67,8 +67,8 @@ type LogThresholdAlertInstance = AlertInstance< type LogThresholdAlertInstanceFactory = ( id: string, reason: string, - threshold: number, - value: number + value: number, + threshold: number ) => LogThresholdAlertInstance; const COMPOSITE_GROUP_SIZE = 2000; @@ -96,7 +96,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => >(async ({ services, params }) => { const { alertWithLifecycle, savedObjectsClient, scopedClusterClient } = services; const { sources } = libs; - const alertInstanceFactory: LogThresholdAlertInstanceFactory = (id, reason, threshold, value) => + const alertInstanceFactory: LogThresholdAlertInstanceFactory = (id, reason, value, threshold) => alertWithLifecycle({ id, fields: { @@ -251,8 +251,8 @@ export const processUngroupedResults = ( const alertInstance = alertInstanceFactory( UNGROUPED_FACTORY_KEY, getReasonMessageForUngroupedCountAlert(documentCount, count.value, count.comparator), - count.value, - documentCount + documentCount, + count.value ); alertInstaceUpdater(alertInstance, AlertStates.ALERT, [ { @@ -285,8 +285,8 @@ export const processUngroupedRatioResults = ( const alertInstance = alertInstanceFactory( UNGROUPED_FACTORY_KEY, getReasonMessageForUngroupedRatioAlert(ratio, count.value, count.comparator), - count.value, - ratio + ratio, + count.value ); alertInstaceUpdater(alertInstance, AlertStates.ALERT, [ { @@ -364,8 +364,8 @@ export const processGroupByResults = ( count.comparator, group.name ), - count.value, - documentCount + documentCount, + count.value ); alertInstaceUpdater(alertInstance, AlertStates.ALERT, [ { @@ -415,8 +415,8 @@ export const processGroupByRatioResults = ( count.comparator, numeratorGroup.name ), - count.value, - ratio + ratio, + count.value ); alertInstaceUpdater(alertInstance, AlertStates.ALERT, [ { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts b/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts similarity index 99% rename from x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts rename to x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts index 4b909489f3236b4..3e1a5ad8e396438 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/counter_rate.test.ts +++ b/x-pack/plugins/lens/common/expressions/counter_rate/counter_rate.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { counterRate, CounterRateArgs } from '../counter_rate'; +import { counterRate, CounterRateArgs } from './index'; import { Datatable } from 'src/plugins/expressions/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts b/x-pack/plugins/lens/common/expressions/counter_rate/index.ts similarity index 97% rename from x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts rename to x-pack/plugins/lens/common/expressions/counter_rate/index.ts index de59597ab6ba381..41f5547dff969ee 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/counter_rate/index.ts +++ b/x-pack/plugins/lens/common/expressions/counter_rate/index.ts @@ -6,11 +6,14 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; import { getBucketIdentifier, buildResultColumns, } from '../../../../../../src/plugins/expressions/common'; +import type { + ExpressionFunctionDefinition, + Datatable, +} from '../../../../../../src/plugins/expressions/common'; export interface CounterRateArgs { by?: string[]; diff --git a/x-pack/plugins/lens/common/expressions/datatable/datatable.ts b/x-pack/plugins/lens/common/expressions/datatable/datatable.ts new file mode 100644 index 000000000000000..d2db63a01793edc --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/datatable/datatable.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { cloneDeep } from 'lodash'; +import type { + DatatableColumnMeta, + ExpressionFunctionDefinition, +} from '../../../../../../src/plugins/expressions/common'; +import type { FormatFactory, LensMultiTable } from '../../types'; +import type { ColumnConfigArg } from './datatable_column'; +import { getSortingCriteria } from './sorting'; +import { computeSummaryRowForColumn } from './summary'; +import { transposeTable } from './transpose_helpers'; + +export interface SortingState { + columnId: string | undefined; + direction: 'asc' | 'desc' | 'none'; +} + +export interface DatatableProps { + data: LensMultiTable; + untransposedData?: LensMultiTable; + args: DatatableArgs; +} + +export interface DatatableRender { + type: 'render'; + as: 'lens_datatable_renderer'; + value: DatatableProps; +} + +export interface DatatableArgs { + title: string; + description?: string; + columns: ColumnConfigArg[]; + sortingColumnId: SortingState['columnId']; + sortingDirection: SortingState['direction']; +} + +function isRange(meta: { params?: { id?: string } } | undefined) { + return meta?.params?.id === 'range'; +} + +export const getDatatable = ({ + formatFactory, +}: { + formatFactory: FormatFactory; +}): ExpressionFunctionDefinition< + 'lens_datatable', + LensMultiTable, + DatatableArgs, + DatatableRender +> => ({ + name: 'lens_datatable', + type: 'render', + inputTypes: ['lens_multitable'], + help: i18n.translate('xpack.lens.datatable.expressionHelpLabel', { + defaultMessage: 'Datatable renderer', + }), + args: { + title: { + types: ['string'], + help: i18n.translate('xpack.lens.datatable.titleLabel', { + defaultMessage: 'Title', + }), + }, + description: { + types: ['string'], + help: '', + }, + columns: { + types: ['lens_datatable_column'], + help: '', + multi: true, + }, + sortingColumnId: { + types: ['string'], + help: '', + }, + sortingDirection: { + types: ['string'], + help: '', + }, + }, + fn(data, args, context) { + let untransposedData: LensMultiTable | undefined; + // do the sorting at this level to propagate it also at CSV download + const [firstTable] = Object.values(data.tables); + const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); + const formatters: Record> = {}; + + firstTable.columns.forEach((column) => { + formatters[column.id] = formatFactory(column.meta?.params); + }); + + const hasTransposedColumns = args.columns.some((c) => c.isTransposed); + if (hasTransposedColumns) { + // store original shape of data separately + untransposedData = cloneDeep(data); + // transposes table and args inplace + transposeTable(args, firstTable, formatters); + } + + const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; + + const columnsReverseLookup = firstTable.columns.reduce< + Record + >((memo, { id, name, meta }, i) => { + memo[id] = { name, index: i, meta }; + return memo; + }, {}); + + const columnsWithSummary = args.columns.filter((c) => c.summaryRow); + for (const column of columnsWithSummary) { + column.summaryRowValue = computeSummaryRowForColumn( + column, + firstTable, + formatters, + formatFactory({ id: 'number' }) + ); + } + + if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { + // Sort on raw values for these types, while use the formatted value for the rest + const sortingCriteria = getSortingCriteria( + isRange(columnsReverseLookup[sortBy]?.meta) + ? 'range' + : columnsReverseLookup[sortBy]?.meta?.type, + sortBy, + formatters[sortBy], + sortDirection + ); + // replace the table here + context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || []) + .slice() + .sort(sortingCriteria); + // replace also the local copy + firstTable.rows = context.inspectorAdapters.tables[layerId].rows; + } else { + args.sortingColumnId = undefined; + args.sortingDirection = 'none'; + } + return { + type: 'render', + as: 'lens_datatable_renderer', + value: { + data, + untransposedData, + args, + }, + }; + }, +}); diff --git a/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts b/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts new file mode 100644 index 000000000000000..892631c3b0f4552 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/datatable/datatable_column.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Direction } from '@elastic/eui'; +import type { + CustomPaletteState, + PaletteOutput, +} from '../../../../../../src/plugins/charts/common'; +import type { + ExpressionFunctionDefinition, + DatatableColumn, +} from '../../../../../../src/plugins/expressions/common'; +import type { CustomPaletteParams } from '../../types'; + +export type LensGridDirection = 'none' | Direction; + +export interface ColumnConfig { + columns: ColumnConfigArg[]; + sortingColumnId: string | undefined; + sortingDirection: LensGridDirection; +} + +export type ColumnConfigArg = Omit & { + type: 'lens_datatable_column'; + palette?: PaletteOutput; + summaryRowValue?: unknown; +}; + +export interface ColumnState { + columnId: string; + width?: number; + hidden?: boolean; + isTransposed?: boolean; + // These flags are necessary to transpose columns and map them back later + // They are set automatically and are not user-editable + transposable?: boolean; + originalColumnId?: string; + originalName?: string; + bucketValues?: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>; + alignment?: 'left' | 'right' | 'center'; + palette?: PaletteOutput; + colorMode?: 'none' | 'cell' | 'text'; + summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max'; + summaryLabel?: string; +} + +export type DatatableColumnResult = ColumnState & { type: 'lens_datatable_column' }; + +export const datatableColumn: ExpressionFunctionDefinition< + 'lens_datatable_column', + null, + ColumnState, + DatatableColumnResult +> = { + name: 'lens_datatable_column', + aliases: [], + type: 'lens_datatable_column', + help: '', + inputTypes: ['null'], + args: { + columnId: { types: ['string'], help: '' }, + alignment: { types: ['string'], help: '' }, + hidden: { types: ['boolean'], help: '' }, + width: { types: ['number'], help: '' }, + isTransposed: { types: ['boolean'], help: '' }, + transposable: { types: ['boolean'], help: '' }, + colorMode: { types: ['string'], help: '' }, + palette: { + types: ['palette'], + help: '', + }, + summaryRow: { types: ['string'], help: '' }, + summaryLabel: { types: ['string'], help: '' }, + }, + fn: function fn(input: unknown, args: ColumnState) { + return { + type: 'lens_datatable_column', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/datatable/index.ts b/x-pack/plugins/lens/common/expressions/datatable/index.ts new file mode 100644 index 000000000000000..2602aae252ca92c --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/datatable/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './datatable_column'; +export * from './datatable'; +export * from './summary'; +export * from './transpose_helpers'; +export * from './utils'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/sorting.test.tsx b/x-pack/plugins/lens/common/expressions/datatable/sorting.test.tsx similarity index 100% rename from x-pack/plugins/lens/public/datatable_visualization/sorting.test.tsx rename to x-pack/plugins/lens/common/expressions/datatable/sorting.test.tsx diff --git a/x-pack/plugins/lens/public/datatable_visualization/sorting.tsx b/x-pack/plugins/lens/common/expressions/datatable/sorting.tsx similarity index 98% rename from x-pack/plugins/lens/public/datatable_visualization/sorting.tsx rename to x-pack/plugins/lens/common/expressions/datatable/sorting.tsx index 0859ab5428c9ef9..13ca811b0b0823b 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/sorting.tsx +++ b/x-pack/plugins/lens/common/expressions/datatable/sorting.tsx @@ -7,7 +7,7 @@ import ipaddr from 'ipaddr.js'; import type { IPv4, IPv6 } from 'ipaddr.js'; -import { FieldFormat } from 'src/plugins/data/public'; +import type { FieldFormat } from '../../../../../../src/plugins/data/common'; function isIPv6Address(ip: IPv4 | IPv6): ip is IPv6 { return ip.kind() === 'ipv6'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/summary.test.ts b/x-pack/plugins/lens/common/expressions/datatable/summary.test.ts similarity index 98% rename from x-pack/plugins/lens/public/datatable_visualization/summary.test.ts rename to x-pack/plugins/lens/common/expressions/datatable/summary.test.ts index f92c83fbbfdc893..9f8f56cc927683d 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/summary.test.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/summary.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { IFieldFormat } from 'src/plugins/data/public'; +import { IFieldFormat } from 'src/plugins/data/common'; import { Datatable } from 'src/plugins/expressions'; import { computeSummaryRowForColumn, getFinalSummaryConfiguration } from './summary'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/summary.ts b/x-pack/plugins/lens/common/expressions/datatable/summary.ts similarity index 91% rename from x-pack/plugins/lens/public/datatable_visualization/summary.ts rename to x-pack/plugins/lens/common/expressions/datatable/summary.ts index 6c267445aab7619..aceade2a3a51391 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/summary.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/summary.ts @@ -6,11 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { FieldFormat } from 'src/plugins/data/public'; -import { Datatable } from 'src/plugins/expressions/public'; -import { ColumnConfigArg } from './datatable_visualization'; +import type { FieldFormat } from '../../../../../../src/plugins/data/common'; +import type { Datatable } from '../../../../../../src/plugins/expressions/common'; +import { ColumnConfigArg } from './datatable_column'; import { getOriginalId } from './transpose_helpers'; -import { isNumericField } from './utils'; +import { isNumericFieldForDatatable } from './utils'; type SummaryRowType = Extract; @@ -19,7 +19,7 @@ export function getFinalSummaryConfiguration( columnArgs: Pick | undefined, table: Datatable | undefined ) { - const isNumeric = isNumericField(table, columnId); + const isNumeric = isNumericFieldForDatatable(table, columnId); const summaryRow = isNumeric ? columnArgs?.summaryRow || 'none' : 'none'; const summaryLabel = columnArgs?.summaryLabel ?? getDefaultSummaryLabel(summaryRow); diff --git a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.test.ts b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts similarity index 99% rename from x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.test.ts rename to x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts index 91559a1778f4f68..7ac6b3d987c842e 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.test.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.test.ts @@ -7,8 +7,8 @@ import type { FieldFormat } from 'src/plugins/data/public'; import type { Datatable } from 'src/plugins/expressions'; +import { DatatableArgs } from './datatable'; -import { Args } from './expression'; import { transposeTable } from './transpose_helpers'; describe('transpose_helpes', () => { @@ -59,7 +59,7 @@ describe('transpose_helpes', () => { }; } - function buildArgs(): Args { + function buildArgs(): DatatableArgs { return { title: 'Table', sortingColumnId: undefined, diff --git a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts similarity index 95% rename from x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts rename to x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts index a35edf7499073a8..06798413c8f40e3 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/transpose_helpers.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/transpose_helpers.ts @@ -5,11 +5,14 @@ * 2.0. */ -import type { FieldFormat } from 'src/plugins/data/public'; -import type { Datatable, DatatableColumn, DatatableRow } from 'src/plugins/expressions'; -import { ColumnConfig } from './components/table_basic'; - -import { Args, ColumnConfigArg } from './expression'; +import type { + Datatable, + DatatableColumn, + DatatableRow, +} from '../../../../../../src/plugins/expressions'; +import type { FieldFormat } from '../../../../../../src/plugins/data/common'; +import type { DatatableArgs } from './datatable'; +import type { ColumnConfig, ColumnConfigArg } from './datatable_column'; const TRANSPOSE_SEPARATOR = '---'; @@ -42,7 +45,7 @@ export function getOriginalId(id: string) { * @param formatters Formatters for all columns to transpose columns by actual display values */ export function transposeTable( - args: Args, + args: DatatableArgs, firstTable: Datatable, formatters: Record ) { @@ -112,7 +115,7 @@ function transposeRows( * grouped by unique value */ function updateColumnArgs( - args: Args, + args: DatatableArgs, bucketsColumnArgs: ColumnConfig['columns'], transposedColumnGroups: Array ) { @@ -150,7 +153,7 @@ function getUniqueValues(table: Datatable, formatter: FieldFormat, columnId: str * @param uniqueValues */ function transposeColumns( - args: Args, + args: DatatableArgs, bucketsColumnArgs: ColumnConfig['columns'], metricColumns: ColumnConfig['columns'], firstTable: Datatable, diff --git a/x-pack/plugins/lens/public/datatable_visualization/utils.ts b/x-pack/plugins/lens/common/expressions/datatable/utils.ts similarity index 80% rename from x-pack/plugins/lens/public/datatable_visualization/utils.ts rename to x-pack/plugins/lens/common/expressions/datatable/utils.ts index 64fdee233e8304a..486ec7102e1ebaf 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/utils.ts +++ b/x-pack/plugins/lens/common/expressions/datatable/utils.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { Datatable } from 'src/plugins/expressions/public'; +import type { Datatable } from '../../../../../../src/plugins/expressions/common'; import { getOriginalId } from './transpose_helpers'; function isValidNumber(value: unknown): boolean { return typeof value === 'number' || value == null; } -export function isNumericField(currentData: Datatable | undefined, accessor: string) { +export function isNumericFieldForDatatable(currentData: Datatable | undefined, accessor: string) { const isNumeric = currentData?.columns.find((col) => col.id === accessor || getOriginalId(col.id) === accessor) ?.meta.type === 'number'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/format_column.test.ts b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts similarity index 98% rename from x-pack/plugins/lens/public/indexpattern_datasource/format_column.test.ts rename to x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts index 38643f2dfbda702..4428225b349da5e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/format_column.test.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/format_column.test.ts @@ -7,7 +7,7 @@ import { Datatable, DatatableColumn } from 'src/plugins/expressions/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; -import { FormatColumnArgs, formatColumn } from './format_column'; +import { FormatColumnArgs, formatColumn } from './index'; describe('format_column', () => { const fn: (input: Datatable, args: FormatColumnArgs) => Datatable = functionWrapper(formatColumn); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts b/x-pack/plugins/lens/common/expressions/format_column/index.ts similarity index 98% rename from x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts rename to x-pack/plugins/lens/common/expressions/format_column/index.ts index 09a4e607a1586fa..c874eac1ede1ff6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/format_column.ts +++ b/x-pack/plugins/lens/common/expressions/format_column/index.ts @@ -5,11 +5,11 @@ * 2.0. */ -import { +import type { ExpressionFunctionDefinition, Datatable, DatatableColumn, -} from 'src/plugins/expressions/public'; +} from '../../../../../../src/plugins/expressions/common'; export interface FormatColumnArgs { format: string; diff --git a/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_chart.ts b/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_chart.ts new file mode 100644 index 000000000000000..4674879dcac9f8d --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_chart.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import type { PaletteOutput } from '../../../../../../src/plugins/charts/common'; +import type { LensMultiTable, CustomPaletteParams } from '../../types'; +import { HeatmapGridConfigResult, HEATMAP_GRID_FUNCTION } from './heatmap_grid'; +import { HeatmapLegendConfigResult, HEATMAP_LEGEND_FUNCTION } from './heatmap_legend'; + +export const HEATMAP_FUNCTION = 'lens_heatmap'; +export const HEATMAP_FUNCTION_RENDERER = 'lens_heatmap_renderer'; + +export type ChartShapes = 'heatmap'; + +export interface SharedHeatmapLayerState { + shape: ChartShapes; + xAccessor?: string; + yAccessor?: string; + valueAccessor?: string; + legend: HeatmapLegendConfigResult; + gridConfig: HeatmapGridConfigResult; +} + +export type HeatmapLayerState = SharedHeatmapLayerState & { + layerId: string; +}; + +export type HeatmapVisualizationState = HeatmapLayerState & { + // need to store the current accessor to reset the color stops at accessor change + palette?: PaletteOutput & { accessor: string }; +}; + +export type HeatmapExpressionArgs = SharedHeatmapLayerState & { + title?: string; + description?: string; + palette: PaletteOutput; +}; + +export interface HeatmapRender { + type: 'render'; + as: typeof HEATMAP_FUNCTION_RENDERER; + value: HeatmapExpressionProps; +} + +export interface HeatmapExpressionProps { + data: LensMultiTable; + args: HeatmapExpressionArgs; +} + +export const heatmap: ExpressionFunctionDefinition< + typeof HEATMAP_FUNCTION, + LensMultiTable, + HeatmapExpressionArgs, + HeatmapRender +> = { + name: HEATMAP_FUNCTION, + type: 'render', + help: i18n.translate('xpack.lens.heatmap.expressionHelpLabel', { + defaultMessage: 'Heatmap renderer', + }), + args: { + title: { + types: ['string'], + help: i18n.translate('xpack.lens.heatmap.titleLabel', { + defaultMessage: 'Title', + }), + }, + description: { + types: ['string'], + help: '', + }, + xAccessor: { + types: ['string'], + help: '', + }, + yAccessor: { + types: ['string'], + help: '', + }, + valueAccessor: { + types: ['string'], + help: '', + }, + shape: { + types: ['string'], + help: '', + }, + palette: { + default: `{theme "palette" default={system_palette name="default"} }`, + help: '', + types: ['palette'], + }, + legend: { + types: [HEATMAP_LEGEND_FUNCTION], + help: i18n.translate('xpack.lens.heatmapChart.legend.help', { + defaultMessage: 'Configure the chart legend.', + }), + }, + gridConfig: { + types: [HEATMAP_GRID_FUNCTION], + help: i18n.translate('xpack.lens.heatmapChart.gridConfig.help', { + defaultMessage: 'Configure the heatmap layout.', + }), + }, + }, + inputTypes: ['lens_multitable'], + fn(data: LensMultiTable, args: HeatmapExpressionArgs) { + return { + type: 'render', + as: HEATMAP_FUNCTION_RENDERER, + value: { + data, + args, + }, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_grid.ts b/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_grid.ts new file mode 100644 index 000000000000000..5fe7f4b8f6c62b3 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_grid.ts @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; + +export const HEATMAP_GRID_FUNCTION = 'lens_heatmap_grid'; + +export interface HeatmapGridConfig { + // grid + strokeWidth?: number; + strokeColor?: string; + cellHeight?: number; + cellWidth?: number; + // cells + isCellLabelVisible: boolean; + // Y-axis + isYAxisLabelVisible: boolean; + yAxisLabelWidth?: number; + yAxisLabelColor?: string; + // X-axis + isXAxisLabelVisible: boolean; +} + +export type HeatmapGridConfigResult = HeatmapGridConfig & { type: typeof HEATMAP_GRID_FUNCTION }; + +export const heatmapGridConfig: ExpressionFunctionDefinition< + typeof HEATMAP_GRID_FUNCTION, + null, + HeatmapGridConfig, + HeatmapGridConfigResult +> = { + name: HEATMAP_GRID_FUNCTION, + aliases: [], + type: HEATMAP_GRID_FUNCTION, + help: `Configure the heatmap layout `, + inputTypes: ['null'], + args: { + // grid + strokeWidth: { + types: ['number'], + help: i18n.translate('xpack.lens.heatmapChart.config.strokeWidth.help', { + defaultMessage: 'Specifies the grid stroke width', + }), + required: false, + }, + strokeColor: { + types: ['string'], + help: i18n.translate('xpack.lens.heatmapChart.config.strokeColor.help', { + defaultMessage: 'Specifies the grid stroke color', + }), + required: false, + }, + cellHeight: { + types: ['number'], + help: i18n.translate('xpack.lens.heatmapChart.config.cellHeight.help', { + defaultMessage: 'Specifies the grid cell height', + }), + required: false, + }, + cellWidth: { + types: ['number'], + help: i18n.translate('xpack.lens.heatmapChart.config.cellWidth.help', { + defaultMessage: 'Specifies the grid cell width', + }), + required: false, + }, + // cells + isCellLabelVisible: { + types: ['boolean'], + help: i18n.translate('xpack.lens.heatmapChart.config.isCellLabelVisible.help', { + defaultMessage: 'Specifies whether or not the cell label is visible.', + }), + }, + // Y-axis + isYAxisLabelVisible: { + types: ['boolean'], + help: i18n.translate('xpack.lens.heatmapChart.config.isYAxisLabelVisible.help', { + defaultMessage: 'Specifies whether or not the Y-axis labels are visible.', + }), + }, + yAxisLabelWidth: { + types: ['number'], + help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelWidth.help', { + defaultMessage: 'Specifies the width of the Y-axis labels.', + }), + required: false, + }, + yAxisLabelColor: { + types: ['string'], + help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelColor.help', { + defaultMessage: 'Specifies the color of the Y-axis labels.', + }), + required: false, + }, + // X-axis + isXAxisLabelVisible: { + types: ['boolean'], + help: i18n.translate('xpack.lens.heatmapChart.config.isXAxisLabelVisible.help', { + defaultMessage: 'Specifies whether or not the X-axis labels are visible.', + }), + }, + }, + fn(input, args) { + return { + type: HEATMAP_GRID_FUNCTION, + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_legend.ts b/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_legend.ts new file mode 100644 index 000000000000000..0f553c6cae1f099 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/heatmap_chart/heatmap_legend.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { Position } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; + +export const HEATMAP_LEGEND_FUNCTION = 'lens_heatmap_legendConfig'; + +export interface HeatmapLegendConfig { + /** + * Flag whether the legend should be shown. If there is just a single series, it will be hidden + */ + isVisible: boolean; + /** + * Position of the legend relative to the chart + */ + position: Position; +} + +export type HeatmapLegendConfigResult = HeatmapLegendConfig & { + type: typeof HEATMAP_LEGEND_FUNCTION; +}; + +/** + * TODO check if it's possible to make a shared function + * based on the XY chart + */ +export const heatmapLegendConfig: ExpressionFunctionDefinition< + typeof HEATMAP_LEGEND_FUNCTION, + null, + HeatmapLegendConfig, + HeatmapLegendConfigResult +> = { + name: HEATMAP_LEGEND_FUNCTION, + aliases: [], + type: HEATMAP_LEGEND_FUNCTION, + help: `Configure the heatmap chart's legend`, + inputTypes: ['null'], + args: { + isVisible: { + types: ['boolean'], + help: i18n.translate('xpack.lens.heatmapChart.legend.isVisible.help', { + defaultMessage: 'Specifies whether or not the legend is visible.', + }), + }, + position: { + types: ['string'], + options: [Position.Top, Position.Right, Position.Bottom, Position.Left], + help: i18n.translate('xpack.lens.heatmapChart.legend.position.help', { + defaultMessage: 'Specifies the legend position.', + }), + }, + }, + fn(input, args) { + return { + type: HEATMAP_LEGEND_FUNCTION, + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/heatmap_chart/index.ts b/x-pack/plugins/lens/common/expressions/heatmap_chart/index.ts new file mode 100644 index 000000000000000..96f8c074b1fc42f --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/heatmap_chart/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './heatmap_grid'; +export * from './heatmap_legend'; +export * from './heatmap_chart'; diff --git a/x-pack/plugins/lens/common/expressions/index.ts b/x-pack/plugins/lens/common/expressions/index.ts new file mode 100644 index 000000000000000..70a85f85938e444 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './counter_rate'; +export * from './format_column'; +export * from './rename_columns'; +export * from './merge_tables'; +export * from './time_scale'; +export * from './datatable'; +export * from './heatmap_chart'; +export * from './metric_chart'; +export * from './pie_chart'; +export * from './xy_chart'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts b/x-pack/plugins/lens/common/expressions/merge_tables/index.ts similarity index 84% rename from x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts rename to x-pack/plugins/lens/common/expressions/merge_tables/index.ts index cd93392fc712d68..e190da19886df5f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.ts +++ b/x-pack/plugins/lens/common/expressions/merge_tables/index.ts @@ -6,16 +6,16 @@ */ import { i18n } from '@kbn/i18n'; -import { - ExecutionContext, - Datatable, +import type { ExpressionFunctionDefinition, -} from 'src/plugins/expressions/public'; -import { ExpressionValueSearchContext, search } from '../../../../../src/plugins/data/public'; -const { toAbsoluteDates } = search.aggs; + Datatable, + ExecutionContext, +} from '../../../../../../src/plugins/expressions/common'; +import { toAbsoluteDates } from '../../../../../../src/plugins/data/common'; +import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common'; -import { LensMultiTable } from '../types'; -import { Adapters } from '../../../../../src/plugins/inspector/common'; +import type { LensMultiTable } from '../../types'; +import { Adapters } from '../../../../../../src/plugins/inspector/common'; interface MergeTables { layerIds: string[]; diff --git a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts b/x-pack/plugins/lens/common/expressions/merge_tables/merge_tables.test.ts similarity index 98% rename from x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts rename to x-pack/plugins/lens/common/expressions/merge_tables/merge_tables.test.ts index eb381b33655e2e7..c883f6b7cb479c5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/merge_tables.test.ts +++ b/x-pack/plugins/lens/common/expressions/merge_tables/merge_tables.test.ts @@ -6,7 +6,7 @@ */ import moment from 'moment'; -import { mergeTables } from './merge_tables'; +import { mergeTables } from './index'; import { ExpressionValueSearchContext } from 'src/plugins/data/public'; import { Datatable, diff --git a/x-pack/plugins/lens/common/expressions/metric_chart/index.ts b/x-pack/plugins/lens/common/expressions/metric_chart/index.ts new file mode 100644 index 000000000000000..40bd4f388645510 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/metric_chart/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './types'; +export * from './metric_chart'; diff --git a/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts new file mode 100644 index 000000000000000..53ed7c8da32eb63 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/metric_chart/metric_chart.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import type { LensMultiTable } from '../../types'; +import type { MetricConfig } from './types'; + +export interface MetricChartProps { + data: LensMultiTable; + args: MetricConfig; +} + +export interface MetricRender { + type: 'render'; + as: 'lens_metric_chart_renderer'; + value: MetricChartProps; +} + +export const metricChart: ExpressionFunctionDefinition< + 'lens_metric_chart', + LensMultiTable, + Omit, + MetricRender +> = { + name: 'lens_metric_chart', + type: 'render', + help: 'A metric chart', + args: { + title: { + types: ['string'], + help: 'The chart title.', + }, + description: { + types: ['string'], + help: '', + }, + metricTitle: { + types: ['string'], + help: 'The title of the metric shown.', + }, + accessor: { + types: ['string'], + help: 'The column whose value is being displayed', + }, + mode: { + types: ['string'], + options: ['reduced', 'full'], + default: 'full', + help: + 'The display mode of the chart - reduced will only show the metric itself without min size', + }, + }, + inputTypes: ['lens_multitable'], + fn(data, args) { + return { + type: 'render', + as: 'lens_metric_chart_renderer', + value: { + data, + args, + }, + } as MetricRender; + }, +}; diff --git a/x-pack/plugins/lens/public/metric_visualization/types.ts b/x-pack/plugins/lens/common/expressions/metric_chart/types.ts similarity index 100% rename from x-pack/plugins/lens/public/metric_visualization/types.ts rename to x-pack/plugins/lens/common/expressions/metric_chart/types.ts diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/index.ts b/x-pack/plugins/lens/common/expressions/pie_chart/index.ts new file mode 100644 index 000000000000000..e82294f8aff2562 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/pie_chart/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './types'; +export * from './pie_chart'; diff --git a/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts new file mode 100644 index 000000000000000..b298f1d8b3a806b --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/pie_chart/pie_chart.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Position } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import type { LensMultiTable } from '../../types'; +import type { PieExpressionProps, PieExpressionArgs } from './types'; + +export interface PieRender { + type: 'render'; + as: 'lens_pie_renderer'; + value: PieExpressionProps; +} + +export const pie: ExpressionFunctionDefinition< + 'lens_pie', + LensMultiTable, + PieExpressionArgs, + PieRender +> = { + name: 'lens_pie', + type: 'render', + help: i18n.translate('xpack.lens.pie.expressionHelpLabel', { + defaultMessage: 'Pie renderer', + }), + args: { + title: { + types: ['string'], + help: 'The chart title.', + }, + description: { + types: ['string'], + help: '', + }, + groups: { + types: ['string'], + multi: true, + help: '', + }, + metric: { + types: ['string'], + help: '', + }, + shape: { + types: ['string'], + options: ['pie', 'donut', 'treemap'], + help: '', + }, + hideLabels: { + types: ['boolean'], + help: '', + }, + numberDisplay: { + types: ['string'], + options: ['hidden', 'percent', 'value'], + help: '', + }, + categoryDisplay: { + types: ['string'], + options: ['default', 'inside', 'hide'], + help: '', + }, + legendDisplay: { + types: ['string'], + options: ['default', 'show', 'hide'], + help: '', + }, + nestedLegend: { + types: ['boolean'], + help: '', + }, + legendPosition: { + types: ['string'], + options: [Position.Top, Position.Right, Position.Bottom, Position.Left], + help: '', + }, + percentDecimals: { + types: ['number'], + help: '', + }, + palette: { + default: `{theme "palette" default={system_palette name="default"} }`, + help: '', + types: ['palette'], + }, + }, + inputTypes: ['lens_multitable'], + fn(data: LensMultiTable, args: PieExpressionArgs) { + return { + type: 'render', + as: 'lens_pie_renderer', + value: { + data, + args, + }, + }; + }, +}; diff --git a/x-pack/plugins/lens/public/pie_visualization/types.ts b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts similarity index 89% rename from x-pack/plugins/lens/public/pie_visualization/types.ts rename to x-pack/plugins/lens/common/expressions/pie_chart/types.ts index c03ab15ecc29079..e37727232295073 100644 --- a/x-pack/plugins/lens/public/pie_visualization/types.ts +++ b/x-pack/plugins/lens/common/expressions/pie_chart/types.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { PaletteOutput } from 'src/plugins/charts/public'; -import { LensMultiTable } from '../types'; +import type { PaletteOutput } from '../../../../../../src/plugins/charts/common'; +import type { LensMultiTable } from '../../types'; export interface SharedPieLayerState { groups: string[]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/index.ts b/x-pack/plugins/lens/common/expressions/rename_columns/index.ts similarity index 87% rename from x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/index.ts rename to x-pack/plugins/lens/common/expressions/rename_columns/index.ts index a69e0358d69c63a..4cb8ff75f486def 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/metric/component/index.ts +++ b/x-pack/plugins/lens/common/expressions/rename_columns/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { Metric } from './metric'; +export * from './rename_columns'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts similarity index 96% rename from x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts rename to x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts index 5654a599c5e27b4..f3db64c1d2257aa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.test.ts +++ b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.test.ts @@ -6,8 +6,8 @@ */ import { renameColumns } from './rename_columns'; -import { Datatable } from '../../../../../src/plugins/expressions/public'; -import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; +import { Datatable } from '../../../../../../src/plugins/expressions/common'; +import { createMockExecutionContext } from '../../../../../../src/plugins/expressions/common/mocks'; describe('rename_columns', () => { it('should rename columns of a given datatable', () => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts similarity index 86% rename from x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts rename to x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts index a16756126c030c2..517bd683d80aedf 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/rename_columns.ts +++ b/x-pack/plugins/lens/common/expressions/rename_columns/rename_columns.ts @@ -6,14 +6,20 @@ */ import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition, Datatable, DatatableColumn } from 'src/plugins/expressions'; -import { IndexPatternColumn } from './operations'; +import { + ExpressionFunctionDefinition, + Datatable, + DatatableColumn, +} from '../../../../../../src/plugins/expressions/common'; interface RemapArgs { idMap: string; } -export type OriginalColumn = { id: string } & IndexPatternColumn; +type OriginalColumn = { id: string; label: string } & ( + | { operationType: 'date_histogram'; sourceField: string } + | { operationType: string; sourceField: never } +); export const renameColumns: ExpressionFunctionDefinition< 'lens_rename_columns', @@ -75,7 +81,7 @@ export const renameColumns: ExpressionFunctionDefinition< }; function getColumnName(originalColumn: OriginalColumn, newColumn: DatatableColumn) { - if (originalColumn && originalColumn.operationType === 'date_histogram') { + if (originalColumn?.operationType === 'date_histogram') { const fieldName = originalColumn.sourceField; // HACK: This is a hack, and introduces some fragility into diff --git a/x-pack/plugins/lens/common/expressions/time_scale/index.ts b/x-pack/plugins/lens/common/expressions/time_scale/index.ts new file mode 100644 index 000000000000000..92fec01a9ecbc4e --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/time_scale/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './time_scale'; +export * from './types'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts similarity index 89% rename from x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts rename to x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts index 34579927cfe1951..c0a5c4bf1e1ec9c 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.test.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts @@ -7,14 +7,25 @@ import moment from 'moment'; import { Datatable } from 'src/plugins/expressions/public'; -import { DataPublicPluginStart, TimeRange } from 'src/plugins/data/public'; -import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks'; +import { TimeRange } from 'src/plugins/data/public'; import { functionWrapper } from 'src/plugins/expressions/common/expression_functions/specs/tests/utils'; -import { getTimeScaleFunction, TimeScaleArgs } from './time_scale'; + +// mock the specific inner variable: +// there are intra dependencies in the data plugin we might break trying to mock the whole thing +jest.mock('../../../../../../src/plugins/data/common/query/timefilter/get_time', () => { + const localMoment = jest.requireActual('moment'); + return { + calculateBounds: jest.fn(({ from, to }) => ({ + min: localMoment(from), + max: localMoment(to), + })), + }; +}); + +import { timeScale, TimeScaleArgs } from './time_scale'; describe('time_scale', () => { - let timeScale: (input: Datatable, args: TimeScaleArgs) => Promise; - let dataMock: jest.Mocked; + let timeScaleWrapped: (input: Datatable, args: TimeScaleArgs) => Promise; const emptyTable: Datatable = { type: 'datatable', @@ -61,7 +72,6 @@ describe('time_scale', () => { } beforeEach(() => { - dataMock = dataPluginMock.createStartContract(); setDateHistogramMeta({ timeZone: 'UTC', timeRange: { @@ -70,17 +80,11 @@ describe('time_scale', () => { }, interval: '1d', }); - (dataMock.query.timefilter.timefilter.calculateBounds as jest.Mock).mockImplementation( - ({ from, to }) => ({ - min: moment(from), - max: moment(to), - }) - ); - timeScale = functionWrapper(getTimeScaleFunction(dataMock)); + timeScaleWrapped = functionWrapper(timeScale); }); it('should apply time scale factor to each row', async () => { - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ @@ -115,7 +119,7 @@ describe('time_scale', () => { }); it('should skip gaps in the data', async () => { - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ @@ -163,7 +167,7 @@ describe('time_scale', () => { }, ], }; - const result = await timeScale(mismatchedTable, { + const result = await timeScaleWrapped(mismatchedTable, { ...defaultArgs, inputColumnId: 'nonexistent', }); @@ -180,7 +184,7 @@ describe('time_scale', () => { }, interval: '1h', }); - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ @@ -220,7 +224,7 @@ describe('time_scale', () => { }, interval: '3h', }); - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ @@ -262,7 +266,7 @@ describe('time_scale', () => { }, interval: '1d', }); - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ @@ -307,7 +311,7 @@ describe('time_scale', () => { }, interval: '1d', }); - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ @@ -347,7 +351,7 @@ describe('time_scale', () => { }, interval: '1y', }); - const result = await timeScale( + const result = await timeScaleWrapped( { ...emptyTable, rows: [ diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts new file mode 100644 index 000000000000000..fc2023ca4d599f2 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import moment from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import type { + ExpressionFunctionDefinition, + Datatable, +} from '../../../../../../src/plugins/expressions/common'; +import { + getDateHistogramMetaDataByDatatableColumn, + parseInterval, + calculateBounds, +} from '../../../../../../src/plugins/data/common'; +import { buildResultColumns } from '../../../../../../src/plugins/expressions/common'; +import type { TimeScaleUnit } from './types'; + +export interface TimeScaleArgs { + dateColumnId: string; + inputColumnId: string; + outputColumnId: string; + targetUnit: TimeScaleUnit; + outputColumnName?: string; +} + +const unitInMs: Record = { + s: 1000, + m: 1000 * 60, + h: 1000 * 60 * 60, + d: 1000 * 60 * 60 * 24, +}; + +export const timeScale: ExpressionFunctionDefinition< + 'lens_time_scale', + Datatable, + TimeScaleArgs, + Promise +> = { + name: 'lens_time_scale', + type: 'datatable', + help: '', + args: { + dateColumnId: { + types: ['string'], + help: '', + required: true, + }, + inputColumnId: { + types: ['string'], + help: '', + required: true, + }, + outputColumnId: { + types: ['string'], + help: '', + required: true, + }, + outputColumnName: { + types: ['string'], + help: '', + }, + targetUnit: { + types: ['string'], + options: ['s', 'm', 'h', 'd'], + help: '', + required: true, + }, + }, + inputTypes: ['datatable'], + async fn( + input, + { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs + ) { + const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); + + if (!dateColumnDefinition) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { + defaultMessage: 'Specified dateColumnId {columnId} does not exist.', + values: { + columnId: dateColumnId, + }, + }) + ); + } + + const resultColumns = buildResultColumns( + input, + outputColumnId, + inputColumnId, + outputColumnName, + { allowColumnOverwrite: true } + ); + + if (!resultColumns) { + return input; + } + + const targetUnitInMs = unitInMs[targetUnit]; + const timeInfo = getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition); + const intervalDuration = timeInfo?.interval && parseInterval(timeInfo.interval); + + if (!timeInfo || !intervalDuration) { + throw new Error( + i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { + defaultMessage: 'Could not fetch date histogram information', + }) + ); + } + // the datemath plugin always parses dates by using the current default moment time zone. + // to use the configured time zone, we are switching just for the bounds calculation. + const defaultTimezone = moment().zoneName(); + moment.tz.setDefault(timeInfo.timeZone); + + const timeBounds = timeInfo.timeRange && calculateBounds(timeInfo.timeRange); + + const result = { + ...input, + columns: resultColumns, + rows: input.rows.map((row) => { + const newRow = { ...row }; + + let startOfBucket = moment(row[dateColumnId]); + let endOfBucket = startOfBucket.clone().add(intervalDuration); + if (timeBounds && timeBounds.min) { + startOfBucket = moment.max(startOfBucket, timeBounds.min); + } + if (timeBounds && timeBounds.max) { + endOfBucket = moment.min(endOfBucket, timeBounds.max); + } + const bucketSize = endOfBucket.diff(startOfBucket); + const factor = bucketSize / targetUnitInMs; + + const currentValue = newRow[inputColumnId]; + if (currentValue != null) { + newRow[outputColumnId] = Number(currentValue) / factor; + } + + return newRow; + }), + }; + // reset default moment timezone + moment.tz.setDefault(defaultTimezone); + + return result; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/time_scale/types.ts b/x-pack/plugins/lens/common/expressions/time_scale/types.ts new file mode 100644 index 000000000000000..4ee00ce53e68ba4 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/time_scale/types.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts new file mode 100644 index 000000000000000..9a9273e43f6f1c7 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/axis_config.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import type { + ArgumentType, + ExpressionFunctionDefinition, +} from '../../../../../../src/plugins/expressions/common'; + +export interface AxesSettingsConfig { + x: boolean; + yLeft: boolean; + yRight: boolean; +} + +export interface AxisExtentConfig { + mode: 'full' | 'dataBounds' | 'custom'; + lowerBound?: number; + upperBound?: number; +} + +interface AxisConfig { + title: string; + hide?: boolean; +} + +export type YAxisMode = 'auto' | 'left' | 'right'; + +export interface YConfig { + forAccessor: string; + axisMode?: YAxisMode; + color?: string; +} + +export type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & { + type: 'lens_xy_axisTitlesVisibilityConfig'; +}; + +export const axisTitlesVisibilityConfig: ExpressionFunctionDefinition< + 'lens_xy_axisTitlesVisibilityConfig', + null, + AxesSettingsConfig, + AxisTitlesVisibilityConfigResult +> = { + name: 'lens_xy_axisTitlesVisibilityConfig', + aliases: [], + type: 'lens_xy_axisTitlesVisibilityConfig', + help: `Configure the xy chart's axis titles appearance`, + inputTypes: ['null'], + args: { + x: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.xAxisTitle.help', { + defaultMessage: 'Specifies whether or not the title of the x-axis are visible.', + }), + }, + yLeft: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yLeftAxisTitle.help', { + defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.', + }), + }, + yRight: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yRightAxisTitle.help', { + defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.', + }), + }, + }, + fn: function fn(input: unknown, args: AxesSettingsConfig) { + return { + type: 'lens_xy_axisTitlesVisibilityConfig', + ...args, + }; + }, +}; + +export type AxisExtentConfigResult = AxisExtentConfig & { type: 'lens_xy_axisExtentConfig' }; + +export const axisExtentConfig: ExpressionFunctionDefinition< + 'lens_xy_axisExtentConfig', + null, + AxisExtentConfig, + AxisExtentConfigResult +> = { + name: 'lens_xy_axisExtentConfig', + aliases: [], + type: 'lens_xy_axisExtentConfig', + help: `Configure the xy chart's axis extents`, + inputTypes: ['null'], + args: { + mode: { + types: ['string'], + options: ['full', 'dataBounds', 'custom'], + help: i18n.translate('xpack.lens.xyChart.extentMode.help', { + defaultMessage: 'The extent mode', + }), + }, + lowerBound: { + types: ['number'], + help: i18n.translate('xpack.lens.xyChart.extentMode.help', { + defaultMessage: 'The extent mode', + }), + }, + upperBound: { + types: ['number'], + help: i18n.translate('xpack.lens.xyChart.extentMode.help', { + defaultMessage: 'The extent mode', + }), + }, + }, + fn: function fn(input: unknown, args: AxisExtentConfig) { + return { + type: 'lens_xy_axisExtentConfig', + ...args, + }; + }, +}; + +export const axisConfig: { [key in keyof AxisConfig]: ArgumentType } = { + title: { + types: ['string'], + help: i18n.translate('xpack.lens.xyChart.title.help', { + defaultMessage: 'The axis title', + }), + }, + hide: { + types: ['boolean'], + default: false, + help: 'Show / hide axis', + }, +}; + +export type YConfigResult = YConfig & { type: 'lens_xy_yConfig' }; + +export const yAxisConfig: ExpressionFunctionDefinition< + 'lens_xy_yConfig', + null, + YConfig, + YConfigResult +> = { + name: 'lens_xy_yConfig', + aliases: [], + type: 'lens_xy_yConfig', + help: `Configure the behavior of a xy chart's y axis metric`, + inputTypes: ['null'], + args: { + forAccessor: { + types: ['string'], + help: 'The accessor this configuration is for', + }, + axisMode: { + types: ['string'], + options: ['auto', 'left', 'right'], + help: 'The axis mode of the metric', + }, + color: { + types: ['string'], + help: 'The color of the series', + }, + }, + fn: function fn(input: unknown, args: YConfig) { + return { + type: 'lens_xy_yConfig', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/fitting_function.ts b/x-pack/plugins/lens/common/expressions/xy_chart/fitting_function.ts new file mode 100644 index 000000000000000..0cfea62d578d707 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/fitting_function.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +export type FittingFunction = typeof fittingFunctionDefinitions[number]['id']; + +export const fittingFunctionDefinitions = [ + { + id: 'None', + title: i18n.translate('xpack.lens.fittingFunctionsTitle.none', { + defaultMessage: 'Hide', + }), + description: i18n.translate('xpack.lens.fittingFunctionsDescription.none', { + defaultMessage: 'Do not fill gaps', + }), + }, + { + id: 'Zero', + title: i18n.translate('xpack.lens.fittingFunctionsTitle.zero', { + defaultMessage: 'Zero', + }), + description: i18n.translate('xpack.lens.fittingFunctionsDescription.zero', { + defaultMessage: 'Fill gaps with zeros', + }), + }, + { + id: 'Linear', + title: i18n.translate('xpack.lens.fittingFunctionsTitle.linear', { + defaultMessage: 'Linear', + }), + description: i18n.translate('xpack.lens.fittingFunctionsDescription.linear', { + defaultMessage: 'Fill gaps with a line', + }), + }, + { + id: 'Carry', + title: i18n.translate('xpack.lens.fittingFunctionsTitle.carry', { + defaultMessage: 'Last', + }), + description: i18n.translate('xpack.lens.fittingFunctionsDescription.carry', { + defaultMessage: 'Fill gaps with the last value', + }), + }, + { + id: 'Lookahead', + title: i18n.translate('xpack.lens.fittingFunctionsTitle.lookahead', { + defaultMessage: 'Next', + }), + description: i18n.translate('xpack.lens.fittingFunctionsDescription.lookahead', { + defaultMessage: 'Fill gaps with the next value', + }), + }, +] as const; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/grid_lines_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/grid_lines_config.ts new file mode 100644 index 000000000000000..6338e9f039937f8 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/grid_lines_config.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import type { AxesSettingsConfig } from './axis_config'; + +export type GridlinesConfigResult = AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' }; + +export const gridlinesConfig: ExpressionFunctionDefinition< + 'lens_xy_gridlinesConfig', + null, + AxesSettingsConfig, + GridlinesConfigResult +> = { + name: 'lens_xy_gridlinesConfig', + aliases: [], + type: 'lens_xy_gridlinesConfig', + help: `Configure the xy chart's gridlines appearance`, + inputTypes: ['null'], + args: { + x: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.xAxisGridlines.help', { + defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.', + }), + }, + yLeft: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yLeftAxisgridlines.help', { + defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.', + }), + }, + yRight: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yRightAxisgridlines.help', { + defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.', + }), + }, + }, + fn: function fn(input: unknown, args: AxesSettingsConfig) { + return { + type: 'lens_xy_gridlinesConfig', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/index.ts b/x-pack/plugins/lens/common/expressions/xy_chart/index.ts new file mode 100644 index 000000000000000..4d1125fa459a39f --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './axis_config'; +export * from './fitting_function'; +export * from './grid_lines_config'; +export * from './layer_config'; +export * from './legend_config'; +export * from './series_type'; +export * from './tick_labels_config'; +export * from './xy_args'; +export * from './xy_chart'; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config.ts new file mode 100644 index 000000000000000..f3baf242425f5db --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PaletteOutput } from '../../../../../../src/plugins/charts/common'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import { axisConfig, YConfig } from './axis_config'; +import type { SeriesType } from './series_type'; + +export interface XYLayerConfig { + hide?: boolean; + layerId: string; + xAccessor?: string; + accessors: string[]; + yConfig?: YConfig[]; + seriesType: SeriesType; + splitAccessor?: string; + palette?: PaletteOutput; +} + +export interface ValidLayer extends XYLayerConfig { + xAccessor: NonNullable; +} + +export type LayerArgs = XYLayerConfig & { + columnToLabel?: string; // Actually a JSON key-value pair + yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; + xScaleType: 'time' | 'linear' | 'ordinal'; + isHistogram: boolean; + // palette will always be set on the expression + palette: PaletteOutput; +}; + +export type LayerConfigResult = LayerArgs & { type: 'lens_xy_layer' }; + +export const layerConfig: ExpressionFunctionDefinition< + 'lens_xy_layer', + null, + LayerArgs, + LayerConfigResult +> = { + name: 'lens_xy_layer', + aliases: [], + type: 'lens_xy_layer', + help: `Configure a layer in the xy chart`, + inputTypes: ['null'], + args: { + ...axisConfig, + layerId: { + types: ['string'], + help: '', + }, + xAccessor: { + types: ['string'], + help: '', + }, + seriesType: { + types: ['string'], + options: [ + 'bar', + 'line', + 'area', + 'bar_stacked', + 'area_stacked', + 'bar_percentage_stacked', + 'area_percentage_stacked', + ], + help: 'The type of chart to display.', + }, + xScaleType: { + options: ['ordinal', 'linear', 'time'], + help: 'The scale type of the x axis', + default: 'ordinal', + }, + isHistogram: { + types: ['boolean'], + default: false, + help: 'Whether to layout the chart as a histogram', + }, + yScaleType: { + options: ['log', 'sqrt', 'linear', 'time'], + help: 'The scale type of the y axes', + default: 'linear', + }, + splitAccessor: { + types: ['string'], + help: 'The column to split by', + multi: false, + }, + accessors: { + types: ['string'], + help: 'The columns to display on the y axis.', + multi: true, + }, + yConfig: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + types: ['lens_xy_yConfig' as any], + help: 'Additional configuration for y axes', + multi: true, + }, + columnToLabel: { + types: ['string'], + help: 'JSON key-value pairs of column ID to label', + }, + palette: { + default: `{theme "palette" default={system_palette name="default"} }`, + help: '', + types: ['palette'], + }, + }, + fn: function fn(input: unknown, args: LayerArgs) { + return { + type: 'lens_xy_layer', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/legend_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/legend_config.ts new file mode 100644 index 000000000000000..e228039b53ef6a2 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/legend_config.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { HorizontalAlignment, Position, VerticalAlignment } from '@elastic/charts'; +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; + +export interface LegendConfig { + /** + * Flag whether the legend should be shown. If there is just a single series, it will be hidden + */ + isVisible: boolean; + /** + * Position of the legend relative to the chart + */ + position: Position; + /** + * Flag whether the legend should be shown even with just a single series + */ + showSingleSeries?: boolean; + /** + * Flag whether the legend is inside the chart + */ + isInside?: boolean; + /** + * Horizontal Alignment of the legend when it is set inside chart + */ + horizontalAlignment?: HorizontalAlignment; + /** + * Vertical Alignment of the legend when it is set inside chart + */ + verticalAlignment?: VerticalAlignment; + /** + * Number of columns when legend is set inside chart + */ + floatingColumns?: number; +} + +export type LegendConfigResult = LegendConfig & { type: 'lens_xy_legendConfig' }; + +export const legendConfig: ExpressionFunctionDefinition< + 'lens_xy_legendConfig', + null, + LegendConfig, + LegendConfigResult +> = { + name: 'lens_xy_legendConfig', + aliases: [], + type: 'lens_xy_legendConfig', + help: `Configure the xy chart's legend`, + inputTypes: ['null'], + args: { + isVisible: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.isVisible.help', { + defaultMessage: 'Specifies whether or not the legend is visible.', + }), + }, + position: { + types: ['string'], + options: [Position.Top, Position.Right, Position.Bottom, Position.Left], + help: i18n.translate('xpack.lens.xyChart.position.help', { + defaultMessage: 'Specifies the legend position.', + }), + }, + showSingleSeries: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.showSingleSeries.help', { + defaultMessage: 'Specifies whether a legend with just a single entry should be shown', + }), + }, + isInside: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.isInside.help', { + defaultMessage: 'Specifies whether a legend is inside the chart', + }), + }, + horizontalAlignment: { + types: ['string'], + options: [HorizontalAlignment.Right, HorizontalAlignment.Left], + help: i18n.translate('xpack.lens.xyChart.horizontalAlignment.help', { + defaultMessage: + 'Specifies the horizontal alignment of the legend when it is displayed inside chart.', + }), + }, + verticalAlignment: { + types: ['string'], + options: [VerticalAlignment.Top, VerticalAlignment.Bottom], + help: i18n.translate('xpack.lens.xyChart.verticalAlignment.help', { + defaultMessage: + 'Specifies the vertical alignment of the legend when it is displayed inside chart.', + }), + }, + floatingColumns: { + types: ['number'], + help: i18n.translate('xpack.lens.xyChart.floatingColumns.help', { + defaultMessage: 'Specifies the number of columns when legend is displayed inside chart.', + }), + }, + }, + fn: function fn(input: unknown, args: LegendConfig) { + return { + type: 'lens_xy_legendConfig', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/series_type.ts b/x-pack/plugins/lens/common/expressions/xy_chart/series_type.ts new file mode 100644 index 000000000000000..f9a375b8b47a11a --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/series_type.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type SeriesType = + | 'bar' + | 'bar_horizontal' + | 'line' + | 'area' + | 'bar_stacked' + | 'bar_percentage_stacked' + | 'bar_horizontal_stacked' + | 'bar_horizontal_percentage_stacked' + | 'area_stacked' + | 'area_percentage_stacked'; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/tick_labels_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/tick_labels_config.ts new file mode 100644 index 000000000000000..4af78d835578642 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/tick_labels_config.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import type { AxesSettingsConfig } from './axis_config'; + +export type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' }; + +export const tickLabelsConfig: ExpressionFunctionDefinition< + 'lens_xy_tickLabelsConfig', + null, + AxesSettingsConfig, + TickLabelsConfigResult +> = { + name: 'lens_xy_tickLabelsConfig', + aliases: [], + type: 'lens_xy_tickLabelsConfig', + help: `Configure the xy chart's tick labels appearance`, + inputTypes: ['null'], + args: { + x: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.xAxisTickLabels.help', { + defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.', + }), + }, + yLeft: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yLeftAxisTickLabels.help', { + defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.', + }), + }, + yRight: { + types: ['boolean'], + help: i18n.translate('xpack.lens.xyChart.yRightAxisTickLabels.help', { + defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.', + }), + }, + }, + fn: function fn(input: unknown, args: AxesSettingsConfig) { + return { + type: 'lens_xy_tickLabelsConfig', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts b/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts new file mode 100644 index 000000000000000..3fcf2a187464d19 --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AxisExtentConfigResult, AxisTitlesVisibilityConfigResult } from './axis_config'; +import type { FittingFunction } from './fitting_function'; +import type { GridlinesConfigResult } from './grid_lines_config'; +import type { LayerArgs } from './layer_config'; +import type { LegendConfigResult } from './legend_config'; +import type { TickLabelsConfigResult } from './tick_labels_config'; + +export type ValueLabelConfig = 'hide' | 'inside' | 'outside'; + +export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X'; + +// Arguments to XY chart expression, with computed properties +export interface XYArgs { + title?: string; + description?: string; + xTitle: string; + yTitle: string; + yRightTitle: string; + yLeftExtent: AxisExtentConfigResult; + yRightExtent: AxisExtentConfigResult; + legend: LegendConfigResult; + valueLabels: ValueLabelConfig; + layers: LayerArgs[]; + fittingFunction?: FittingFunction; + axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult; + tickLabelsVisibilitySettings?: TickLabelsConfigResult; + gridlinesVisibilitySettings?: GridlinesConfigResult; + curveType?: XYCurveType; + fillOpacity?: number; + hideEndzones?: boolean; + valuesInLegend?: boolean; +} diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts b/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts new file mode 100644 index 000000000000000..7fa26f4a2c25d3d --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import type { ExpressionFunctionDefinition } from '../../../../../../src/plugins/expressions/common'; +import type { ExpressionValueSearchContext } from '../../../../../../src/plugins/data/common'; +import type { LensMultiTable } from '../../types'; +import type { XYArgs } from './xy_args'; +import { fittingFunctionDefinitions } from './fitting_function'; + +export interface XYChartProps { + data: LensMultiTable; + args: XYArgs; +} + +export interface XYRender { + type: 'render'; + as: 'lens_xy_chart_renderer'; + value: XYChartProps; +} + +export const xyChart: ExpressionFunctionDefinition< + 'lens_xy_chart', + LensMultiTable | ExpressionValueSearchContext | null, + XYArgs, + XYRender +> = { + name: 'lens_xy_chart', + type: 'render', + inputTypes: ['lens_multitable', 'kibana_context', 'null'], + help: i18n.translate('xpack.lens.xyChart.help', { + defaultMessage: 'An X/Y chart', + }), + args: { + title: { + types: ['string'], + help: 'The chart title.', + }, + description: { + types: ['string'], + help: '', + }, + xTitle: { + types: ['string'], + help: i18n.translate('xpack.lens.xyChart.xTitle.help', { + defaultMessage: 'X axis title', + }), + }, + yTitle: { + types: ['string'], + help: i18n.translate('xpack.lens.xyChart.yLeftTitle.help', { + defaultMessage: 'Y left axis title', + }), + }, + yRightTitle: { + types: ['string'], + help: i18n.translate('xpack.lens.xyChart.yRightTitle.help', { + defaultMessage: 'Y right axis title', + }), + }, + yLeftExtent: { + types: ['lens_xy_axisExtentConfig'], + help: i18n.translate('xpack.lens.xyChart.yLeftExtent.help', { + defaultMessage: 'Y left axis extents', + }), + }, + yRightExtent: { + types: ['lens_xy_axisExtentConfig'], + help: i18n.translate('xpack.lens.xyChart.yRightExtent.help', { + defaultMessage: 'Y right axis extents', + }), + }, + legend: { + types: ['lens_xy_legendConfig'], + help: i18n.translate('xpack.lens.xyChart.legend.help', { + defaultMessage: 'Configure the chart legend.', + }), + }, + fittingFunction: { + types: ['string'], + options: [...fittingFunctionDefinitions.map(({ id }) => id)], + help: i18n.translate('xpack.lens.xyChart.fittingFunction.help', { + defaultMessage: 'Define how missing values are treated', + }), + }, + valueLabels: { + types: ['string'], + options: ['hide', 'inside'], + help: '', + }, + tickLabelsVisibilitySettings: { + types: ['lens_xy_tickLabelsConfig'], + help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', { + defaultMessage: 'Show x and y axes tick labels', + }), + }, + gridlinesVisibilitySettings: { + types: ['lens_xy_gridlinesConfig'], + help: i18n.translate('xpack.lens.xyChart.gridlinesSettings.help', { + defaultMessage: 'Show x and y axes gridlines', + }), + }, + axisTitlesVisibilitySettings: { + types: ['lens_xy_axisTitlesVisibilityConfig'], + help: i18n.translate('xpack.lens.xyChart.axisTitlesSettings.help', { + defaultMessage: 'Show x and y axes titles', + }), + }, + layers: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + types: ['lens_xy_layer'] as any, + help: 'Layers of visual series', + multi: true, + }, + curveType: { + types: ['string'], + options: ['LINEAR', 'CURVE_MONOTONE_X'], + help: i18n.translate('xpack.lens.xyChart.curveType.help', { + defaultMessage: 'Define how curve type is rendered for a line chart', + }), + }, + fillOpacity: { + types: ['number'], + help: i18n.translate('xpack.lens.xyChart.fillOpacity.help', { + defaultMessage: 'Define the area chart fill opacity', + }), + }, + hideEndzones: { + types: ['boolean'], + default: false, + help: i18n.translate('xpack.lens.xyChart.hideEndzones.help', { + defaultMessage: 'Hide endzone markers for partial data', + }), + }, + valuesInLegend: { + types: ['boolean'], + default: false, + help: i18n.translate('xpack.lens.xyChart.valuesInLegend.help', { + defaultMessage: 'Show values in legend', + }), + }, + }, + fn(data: LensMultiTable, args: XYArgs) { + return { + type: 'render', + as: 'lens_xy_chart_renderer', + value: { + data, + args, + }, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/index.ts b/x-pack/plugins/lens/common/index.ts index 25a96d764bb283b..42e673058f1dbfc 100644 --- a/x-pack/plugins/lens/common/index.ts +++ b/x-pack/plugins/lens/common/index.ts @@ -8,3 +8,6 @@ export * from './api'; export * from './constants'; export * from './types'; + +// Note: do not import the expression folder here or the page bundle will be bloated with all +// the package diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts b/x-pack/plugins/lens/common/suffix_formatter/index.ts similarity index 93% rename from x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts rename to x-pack/plugins/lens/common/suffix_formatter/index.ts index f21b854128958db..12a4e02a81ef20f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts +++ b/x-pack/plugins/lens/common/suffix_formatter/index.ts @@ -10,9 +10,9 @@ import { FieldFormat, FieldFormatInstanceType, KBN_FIELD_TYPES, -} from '../../../../../src/plugins/data/public'; -import { FormatFactory } from '../types'; -import { TimeScaleUnit } from './time_scale'; +} from '../../../../../src/plugins/data/common'; +import type { FormatFactory } from '../types'; +import type { TimeScaleUnit } from '../expressions/time_scale'; const unitSuffixes: Record = { s: i18n.translate('xpack.lens.fieldFormats.suffix.s', { defaultMessage: '/s' }), diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts b/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts similarity index 96% rename from x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts rename to x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts index 4349b95c4deafa3..c4379bdd1fb34cb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts +++ b/x-pack/plugins/lens/common/suffix_formatter/suffix_formatter.test.ts @@ -6,7 +6,7 @@ */ import { FormatFactory } from '../types'; -import { getSuffixFormatter } from './suffix_formatter'; +import { getSuffixFormatter } from './index'; describe('suffix formatter', () => { it('should call nested formatter and apply suffix', () => { diff --git a/x-pack/plugins/lens/common/types.ts b/x-pack/plugins/lens/common/types.ts index 2ca31c08a4ec773..a60061a3aa054b7 100644 --- a/x-pack/plugins/lens/common/types.ts +++ b/x-pack/plugins/lens/common/types.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { FilterMeta, Filter } from 'src/plugins/data/common'; +import type { FilterMeta, Filter, IFieldFormat } from '../../../../src/plugins/data/common'; +import type { Datatable, SerializedFieldFormat } from '../../../../src/plugins/expressions/common'; + +export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat; export interface ExistingFields { indexPatternTitle: string; @@ -24,3 +27,32 @@ export interface PersistableFilterMeta extends FilterMeta { export interface PersistableFilter extends Filter { meta: PersistableFilterMeta; } + +export interface LensMultiTable { + type: 'lens_multitable'; + tables: Record; + dateRange?: { + fromDate: Date; + toDate: Date; + }; +} + +export interface ColorStop { + color: string; + stop: number; +} + +export interface CustomPaletteParams { + name?: string; + reverse?: boolean; + rangeType?: 'number' | 'percent'; + continuity?: 'above' | 'below' | 'all' | 'none'; + progression?: 'fixed'; + rangeMin?: number; + rangeMax?: number; + stops?: ColorStop[]; + colorStops?: ColorStop[]; + steps?: number; +} + +export type RequiredPaletteParamTypes = Required; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx index aa8c6ffb26d1705..fb9cb992fcf47d0 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.test.tsx @@ -14,7 +14,7 @@ import { Datatable } from 'src/plugins/expressions/public'; import { IUiSettingsClient } from 'kibana/public'; import { act } from 'react-dom/test-utils'; import { ReactWrapper } from 'enzyme'; -import { Args, ColumnConfigArg } from '../expression'; +import { DatatableArgs, ColumnConfigArg } from '../../../common/expressions'; import { DataContextType } from './types'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; @@ -91,7 +91,7 @@ describe('datatable cell renderer', () => { const paletteRegistry = chartPluginMock.createPaletteRegistry(); const customPalette = paletteRegistry.get('custom'); - function getCellRenderer(columnConfig: Args) { + function getCellRenderer(columnConfig: DatatableArgs) { return createGridCell( { a: { convert: (x) => `formatted ${x}` } as FieldFormat, @@ -101,7 +101,7 @@ describe('datatable cell renderer', () => { ({ get: jest.fn() } as unknown) as IUiSettingsClient ); } - function getColumnConfiguration(): Args { + function getColumnConfiguration(): DatatableArgs { return { title: 'myData', columns: [ @@ -136,7 +136,10 @@ describe('datatable cell renderer', () => { }); } - async function renderCellComponent(columnConfig: Args, context: Partial = {}) { + async function renderCellComponent( + columnConfig: DatatableArgs, + context: Partial = {} + ) { const CellRendererWithPalette = getCellRenderer(columnConfig); const setCellProps = jest.fn(); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx index 5a3aa3b45b848c2..6d6b2e4b1013fa4 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/cell_value.tsx @@ -8,11 +8,11 @@ import React, { useContext, useEffect } from 'react'; import { EuiDataGridCellValueElementProps } from '@elastic/eui'; import { IUiSettingsClient } from 'kibana/public'; -import type { FormatFactory } from '../../types'; +import type { FormatFactory } from '../../../common'; +import { getOriginalId } from '../../../common/expressions'; +import type { ColumnConfig } from '../../../common/expressions'; import type { DataContextType } from './types'; -import { ColumnConfig } from './table_basic'; import { getContrastColor, getNumericValue } from '../../shared_components/coloring/utils'; -import { getOriginalId } from '../transpose_helpers'; export const createGridCell = ( formatters: Record>, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx index 4372e2cd9e9640d..0bc249c783239e4 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/columns.tsx @@ -13,8 +13,8 @@ import { EuiListGroupItemProps, } from '@elastic/eui'; import type { Datatable, DatatableColumn, DatatableColumnMeta } from 'src/plugins/expressions'; -import type { FormatFactory } from '../../types'; -import { ColumnConfig } from './table_basic'; +import type { FormatFactory } from '../../../common'; +import type { ColumnConfig } from '../../../common/expressions'; export const createGridColumns = ( bucketColumns: string[], diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 705484edcf0e6dc..6840f4f13450c79 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -21,8 +21,7 @@ import { } from '@elastic/eui'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { VisualizationDimensionEditorProps } from '../../types'; -import { ColumnState, DatatableVisualizationState } from '../visualization'; -import { getOriginalId } from '../transpose_helpers'; +import { DatatableVisualizationState } from '../visualization'; import { CustomizablePalette, applyPaletteParams, @@ -33,13 +32,16 @@ import { PalettePanelContainer, findMinMaxByColumnId, } from '../../shared_components/'; -import './dimension_editor.scss'; +import type { ColumnState } from '../../../common/expressions'; import { + isNumericFieldForDatatable, getDefaultSummaryLabel, getFinalSummaryConfiguration, getSummaryRowOptions, -} from '../summary'; -import { isNumericField } from '../utils'; + getOriginalId, +} from '../../../common/expressions'; + +import './dimension_editor.scss'; const idPrefix = htmlIdGenerator()(); @@ -93,7 +95,7 @@ export function TableDimensionEditor( const currentData = frame.activeData?.[state.layerId]; // either read config state or use same logic as chart itself - const isNumeric = isNumericField(currentData, accessor); + const isNumeric = isNumericFieldForDatatable(currentData, accessor); const currentAlignment = column?.alignment || (isNumeric ? 'right' : 'left'); const currentColorMode = column?.colorMode || 'none'; const hasDynamicColoring = currentColorMode !== 'none'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts index 8490d33f83444de..bcce2fa2f6f693a 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.test.ts @@ -17,9 +17,8 @@ import { createGridHideHandler, createTransposeColumnFilterHandler, } from './table_actions'; -import { LensGridDirection } from './types'; -import { ColumnConfig } from './table_basic'; -import { LensMultiTable } from '../../types'; +import { LensMultiTable } from '../../../common'; +import { LensGridDirection, ColumnConfig } from '../../../common/expressions'; function getDefaultConfig(): ColumnConfig { return { diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts index 8615ed653631601..62c2ec3a7f7fdea 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_actions.ts @@ -7,15 +7,11 @@ import type { EuiDataGridSorting } from '@elastic/eui'; import type { Datatable, DatatableColumn } from 'src/plugins/expressions'; -import type { LensFilterEvent, LensMultiTable } from '../../types'; -import type { - LensGridDirection, - LensResizeAction, - LensSortAction, - LensToggleAction, -} from './types'; -import { ColumnConfig } from './table_basic'; -import { getOriginalId } from '../transpose_helpers'; +import type { LensFilterEvent } from '../../types'; +import type { LensMultiTable } from '../../../common'; +import type { LensResizeAction, LensSortAction, LensToggleAction } from './types'; +import type { ColumnConfig, LensGridDirection } from '../../../common/expressions'; +import { getOriginalId } from '../../../common/expressions'; export const createGridResizeHandler = ( columnConfig: ColumnConfig, diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx index ae51f7d42312f81..bb678a361e1741f 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.test.tsx @@ -15,8 +15,8 @@ import { VisualizationContainer } from '../../visualization_container'; import { EmptyPlaceholder } from '../../shared_components'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; import { DataContext, DatatableComponent } from './table_basic'; -import { LensMultiTable } from '../../types'; -import { DatatableProps } from '../expression'; +import { LensMultiTable } from '../../../common'; +import { DatatableProps } from '../../../common/expressions'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; import { IUiSettingsClient } from 'kibana/public'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx index 8ef64e4acdccc75..ac1324385dbd1fe 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/table_basic.tsx @@ -18,18 +18,17 @@ import { EuiDataGridSorting, EuiDataGridStyle, } from '@elastic/eui'; -import { CustomPaletteState, PaletteOutput } from 'src/plugins/charts/common'; -import { FormatFactory, LensFilterEvent, LensTableRowContextMenuEvent } from '../../types'; +import type { LensFilterEvent, LensTableRowContextMenuEvent } from '../../types'; +import type { FormatFactory } from '../../../common'; +import { LensGridDirection } from '../../../common/expressions'; import { VisualizationContainer } from '../../visualization_container'; import { EmptyPlaceholder, findMinMaxByColumnId } from '../../shared_components'; import { LensIconChartDatatable } from '../../assets/chart_datatable'; -import { ColumnState } from '../visualization'; -import { +import type { DataContextType, DatatableRenderProps, LensSortAction, LensResizeAction, - LensGridDirection, LensToggleAction, } from './types'; import { createGridColumns } from './columns'; @@ -42,8 +41,7 @@ import { createTransposeColumnFilterHandler, } from './table_actions'; import { CUSTOM_PALETTE } from '../../shared_components/coloring/constants'; -import { getFinalSummaryConfiguration } from '../summary'; -import { getOriginalId } from '../transpose_helpers'; +import { getOriginalId, getFinalSummaryConfiguration } from '../../../common/expressions'; export const DataContext = React.createContext({}); @@ -52,17 +50,6 @@ const gridStyle: EuiDataGridStyle = { header: 'underline', }; -export interface ColumnConfig { - columns: Array< - Omit & { - type: 'lens_datatable_column'; - palette?: PaletteOutput; - } - >; - sortingColumnId: string | undefined; - sortingDirection: LensGridDirection; -} - export const DatatableComponent = (props: DatatableRenderProps) => { const [firstTable] = Object.values(props.data.tables); diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/types.ts b/x-pack/plugins/lens/public/datatable_visualization/components/types.ts index 2095715756a532c..f3d81c2d133400a 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/types.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/components/types.ts @@ -5,16 +5,14 @@ * 2.0. */ -import type { Direction } from '@elastic/eui'; import { IUiSettingsClient } from 'kibana/public'; import { CustomPaletteState, PaletteRegistry } from 'src/plugins/charts/public'; import type { IAggType } from 'src/plugins/data/public'; import type { Datatable, RenderMode } from 'src/plugins/expressions'; -import type { FormatFactory, ILensInterpreterRenderHandlers, LensEditEvent } from '../../types'; -import type { DatatableProps } from '../expression'; +import type { ILensInterpreterRenderHandlers, LensEditEvent } from '../../types'; import { LENS_EDIT_SORT_ACTION, LENS_EDIT_RESIZE_ACTION, LENS_TOGGLE_ACTION } from './constants'; - -export type LensGridDirection = 'none' | Direction; +import type { FormatFactory } from '../../../common'; +import type { DatatableProps, LensGridDirection } from '../../../common/expressions'; export interface LensSortActionData { columnId: string | undefined; @@ -49,12 +47,6 @@ export type DatatableRenderProps = DatatableProps & { rowHasRowClickTriggerActions?: boolean[]; }; -export interface DatatableRender { - type: 'render'; - as: 'lens_datatable_renderer'; - value: DatatableProps; -} - export interface DataContextType { table?: Datatable; rowHasRowClickTriggerActions?: boolean[]; diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx index 3ba448b49afc9d1..4b4d2275d0dec00 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.test.tsx @@ -5,10 +5,11 @@ * 2.0. */ -import { DatatableProps, getDatatable } from './expression'; -import { LensMultiTable } from '../types'; +import { DatatableProps } from '../../common/expressions'; +import type { LensMultiTable } from '../../common'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { IFieldFormat } from '../../../../../src/plugins/data/public'; +import { getDatatable } from './expression'; function sampleArgs() { const indexPatternId = 'indexPatternId'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx index 79a541b0288ab60..4e541bce9a8c259 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/expression.tsx @@ -7,194 +7,20 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { cloneDeep } from 'lodash'; import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import type { IAggType } from 'src/plugins/data/public'; -import { - DatatableColumnMeta, - ExpressionFunctionDefinition, - ExpressionRenderDefinition, -} from 'src/plugins/expressions'; -import { CustomPaletteState, PaletteOutput } from 'src/plugins/charts/common'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { IUiSettingsClient } from 'kibana/public'; -import { getSortingCriteria } from './sorting'; - +import { ExpressionRenderDefinition } from 'src/plugins/expressions'; import { DatatableComponent } from './components/table_basic'; -import { ColumnState } from './visualization'; - -import type { FormatFactory, ILensInterpreterRenderHandlers, LensMultiTable } from '../types'; -import type { DatatableRender } from './components/types'; -import { transposeTable } from './transpose_helpers'; -import { computeSummaryRowForColumn } from './summary'; - -export type ColumnConfigArg = Omit & { - type: 'lens_datatable_column'; - palette?: PaletteOutput; - summaryRowValue?: unknown; -}; - -export interface Args { - title: string; - description?: string; - columns: ColumnConfigArg[]; - sortingColumnId: string | undefined; - sortingDirection: 'asc' | 'desc' | 'none'; -} - -export interface DatatableProps { - data: LensMultiTable; - untransposedData?: LensMultiTable; - args: Args; -} - -function isRange(meta: { params?: { id?: string } } | undefined) { - return meta?.params?.id === 'range'; -} - -export const getDatatable = ({ - formatFactory, -}: { - formatFactory: FormatFactory; -}): ExpressionFunctionDefinition<'lens_datatable', LensMultiTable, Args, DatatableRender> => ({ - name: 'lens_datatable', - type: 'render', - inputTypes: ['lens_multitable'], - help: i18n.translate('xpack.lens.datatable.expressionHelpLabel', { - defaultMessage: 'Datatable renderer', - }), - args: { - title: { - types: ['string'], - help: i18n.translate('xpack.lens.datatable.titleLabel', { - defaultMessage: 'Title', - }), - }, - description: { - types: ['string'], - help: '', - }, - columns: { - types: ['lens_datatable_column'], - help: '', - multi: true, - }, - sortingColumnId: { - types: ['string'], - help: '', - }, - sortingDirection: { - types: ['string'], - help: '', - }, - }, - fn(data, args, context) { - let untransposedData: LensMultiTable | undefined; - // do the sorting at this level to propagate it also at CSV download - const [firstTable] = Object.values(data.tables); - const [layerId] = Object.keys(context.inspectorAdapters.tables || {}); - const formatters: Record> = {}; - - firstTable.columns.forEach((column) => { - formatters[column.id] = formatFactory(column.meta?.params); - }); - - const hasTransposedColumns = args.columns.some((c) => c.isTransposed); - if (hasTransposedColumns) { - // store original shape of data separately - untransposedData = cloneDeep(data); - // transposes table and args inplace - transposeTable(args, firstTable, formatters); - } - - const { sortingColumnId: sortBy, sortingDirection: sortDirection } = args; - - const columnsReverseLookup = firstTable.columns.reduce< - Record - >((memo, { id, name, meta }, i) => { - memo[id] = { name, index: i, meta }; - return memo; - }, {}); - - const columnsWithSummary = args.columns.filter((c) => c.summaryRow); - for (const column of columnsWithSummary) { - column.summaryRowValue = computeSummaryRowForColumn( - column, - firstTable, - formatters, - formatFactory({ id: 'number' }) - ); - } - - if (sortBy && columnsReverseLookup[sortBy] && sortDirection !== 'none') { - // Sort on raw values for these types, while use the formatted value for the rest - const sortingCriteria = getSortingCriteria( - isRange(columnsReverseLookup[sortBy]?.meta) - ? 'range' - : columnsReverseLookup[sortBy]?.meta?.type, - sortBy, - formatters[sortBy], - sortDirection - ); - // replace the table here - context.inspectorAdapters.tables[layerId].rows = (firstTable.rows || []) - .slice() - .sort(sortingCriteria); - // replace also the local copy - firstTable.rows = context.inspectorAdapters.tables[layerId].rows; - } else { - args.sortingColumnId = undefined; - args.sortingDirection = 'none'; - } - return { - type: 'render', - as: 'lens_datatable_renderer', - value: { - data, - untransposedData, - args, - }, - }; - }, -}); -type DatatableColumnResult = ColumnState & { type: 'lens_datatable_column' }; +import type { ILensInterpreterRenderHandlers } from '../types'; +import type { FormatFactory } from '../../common'; +import { DatatableProps } from '../../common/expressions'; -export const datatableColumn: ExpressionFunctionDefinition< - 'lens_datatable_column', - null, - ColumnState, - DatatableColumnResult -> = { - name: 'lens_datatable_column', - aliases: [], - type: 'lens_datatable_column', - help: '', - inputTypes: ['null'], - args: { - columnId: { types: ['string'], help: '' }, - alignment: { types: ['string'], help: '' }, - hidden: { types: ['boolean'], help: '' }, - width: { types: ['number'], help: '' }, - isTransposed: { types: ['boolean'], help: '' }, - transposable: { types: ['boolean'], help: '' }, - colorMode: { types: ['string'], help: '' }, - palette: { - types: ['palette'], - help: '', - }, - summaryRow: { types: ['string'], help: '' }, - summaryLabel: { types: ['string'], help: '' }, - }, - fn: function fn(input: unknown, args: ColumnState) { - return { - type: 'lens_datatable_column', - ...args, - }; - }, -}; +export { datatableColumn, getDatatable } from '../../common/expressions'; export const getDatatableRenderer = (dependencies: { formatFactory: FormatFactory; diff --git a/x-pack/plugins/lens/public/datatable_visualization/index.ts b/x-pack/plugins/lens/public/datatable_visualization/index.ts index 7f48d00d00f7f79..b4f37faf0bc0054 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/index.ts +++ b/x-pack/plugins/lens/public/datatable_visualization/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { CoreSetup } from 'kibana/public'; -import { ChartsPluginSetup } from 'src/plugins/charts/public'; -import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; -import { EditorFrameSetup, FormatFactory } from '../types'; -import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import type { CoreSetup } from 'kibana/public'; +import type { ChartsPluginSetup } from 'src/plugins/charts/public'; +import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; +import type { EditorFrameSetup } from '../types'; +import type { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import type { FormatFactory } from '../../common'; interface DatatableVisualizationPluginStartPlugins { data: DataPublicPluginStart; diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index e7ab4aab88f2e8e..691fce0ed70d28f 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -10,9 +10,8 @@ import { render } from 'react-dom'; import { Ast } from '@kbn/interpreter/common'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { DatatableColumn } from 'src/plugins/expressions/public'; -import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; -import { +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { SuggestionRequest, Visualization, VisualizationSuggestion, @@ -21,32 +20,9 @@ import { import { LensIconChartDatatable } from '../assets/chart_datatable'; import { TableDimensionEditor } from './components/dimension_editor'; import { CUSTOM_PALETTE } from '../shared_components/coloring/constants'; -import { CustomPaletteParams } from '../shared_components/coloring/types'; import { getStopsForFixedMode } from '../shared_components'; -import { getDefaultSummaryLabel } from './summary'; - -export interface ColumnState { - columnId: string; - width?: number; - hidden?: boolean; - isTransposed?: boolean; - // These flags are necessary to transpose columns and map them back later - // They are set automatically and are not user-editable - transposable?: boolean; - originalColumnId?: string; - originalName?: string; - bucketValues?: Array<{ originalBucketColumn: DatatableColumn; value: unknown }>; - alignment?: 'left' | 'right' | 'center'; - palette?: PaletteOutput; - colorMode?: 'none' | 'cell' | 'text'; - summaryRow?: 'none' | 'sum' | 'avg' | 'count' | 'min' | 'max'; - summaryLabel?: string; -} - -export interface SortingState { - columnId: string | undefined; - direction: 'asc' | 'desc' | 'none'; -} +import { getDefaultSummaryLabel } from '../../common/expressions'; +import type { ColumnState, SortingState } from '../../common/expressions'; export interface DatatableVisualizationState { columns: ColumnState[]; diff --git a/x-pack/plugins/lens/public/editor_frame_service/service.tsx b/x-pack/plugins/lens/public/editor_frame_service/service.tsx index 63340795ec6c8de..b3574f19b5caf90 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/service.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/service.tsx @@ -22,7 +22,7 @@ import { EditorFrameStart, } from '../types'; import { Document } from '../persistence/saved_object_store'; -import { mergeTables } from './merge_tables'; +import { mergeTables } from '../../common/expressions'; import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { DashboardStart } from '../../../../../src/plugins/dashboard/public'; diff --git a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx index e27abc4ae32ecab..e8095f6c741a4d6 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable_component.tsx @@ -23,9 +23,8 @@ import type { LensByReferenceInput, LensByValueInput } from './embeddable'; import type { Document } from '../persistence'; import type { IndexPatternPersistedState } from '../indexpattern_datasource/types'; import type { XYState } from '../xy_visualization/types'; -import type { PieVisualizationState } from '../pie_visualization/types'; +import type { PieVisualizationState, MetricState } from '../../common/expressions'; import type { DatatableVisualizationState } from '../datatable_visualization/visualization'; -import type { MetricState } from '../metric_visualization/types'; type LensAttributes = Omit< Document, diff --git a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx index 3e8e9d184ed8acc..15e9963ff57406d 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/chart_component.tsx @@ -16,11 +16,11 @@ import { ScaleType, Settings, } from '@elastic/charts'; -import { CustomPaletteState } from 'src/plugins/charts/public'; +import type { CustomPaletteState } from 'src/plugins/charts/public'; import { VisualizationContainer } from '../visualization_container'; -import { HeatmapRenderProps } from './types'; +import type { HeatmapRenderProps } from './types'; import './index.scss'; -import { LensBrushEvent, LensFilterEvent } from '../types'; +import type { LensBrushEvent, LensFilterEvent } from '../types'; import { applyPaletteParams, defaultPaletteParams, diff --git a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx index 85daa4805b9ece0..ca4a65e6fb10f4a 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx @@ -14,8 +14,8 @@ import { EuiFlexGroup, EuiButtonEmpty, } from '@elastic/eui'; -import { PaletteRegistry } from 'src/plugins/charts/public'; -import { VisualizationDimensionEditorProps } from '../types'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { VisualizationDimensionEditorProps } from '../types'; import { CustomizablePalette, FIXED_PROGRESSION, @@ -23,7 +23,7 @@ import { PalettePanelContainer, } from '../shared_components/'; import './dimension_editor.scss'; -import { HeatmapVisualizationState } from './types'; +import type { HeatmapVisualizationState } from './types'; import { getSafePaletteParams } from './utils'; export function HeatmapDimensionEditor( diff --git a/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx b/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx index 37dad117bb78307..27be4b9ce7fe97c 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/expression.tsx @@ -9,221 +9,15 @@ import { i18n } from '@kbn/i18n'; import { I18nProvider } from '@kbn/i18n/react'; import ReactDOM from 'react-dom'; import React from 'react'; -import { Position } from '@elastic/charts'; -import { - ExpressionFunctionDefinition, - IInterpreterRenderHandlers, -} from '../../../../../src/plugins/expressions'; -import { FormatFactory, LensBrushEvent, LensFilterEvent, LensMultiTable } from '../types'; -import { - FUNCTION_NAME, - HEATMAP_GRID_FUNCTION, - LEGEND_FUNCTION, - LENS_HEATMAP_RENDERER, -} from './constants'; -import type { - HeatmapExpressionArgs, - HeatmapExpressionProps, - HeatmapGridConfig, - HeatmapGridConfigResult, - HeatmapRender, - LegendConfigResult, -} from './types'; -import { HeatmapLegendConfig } from './types'; -import { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; +import type { IInterpreterRenderHandlers } from '../../../../../src/plugins/expressions'; +import type { LensBrushEvent, LensFilterEvent } from '../types'; +import type { FormatFactory } from '../../common'; +import { LENS_HEATMAP_RENDERER } from './constants'; +import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; import { HeatmapChartReportable } from './chart_component'; +import type { HeatmapExpressionProps } from './types'; -export const heatmapGridConfig: ExpressionFunctionDefinition< - typeof HEATMAP_GRID_FUNCTION, - null, - HeatmapGridConfig, - HeatmapGridConfigResult -> = { - name: HEATMAP_GRID_FUNCTION, - aliases: [], - type: HEATMAP_GRID_FUNCTION, - help: `Configure the heatmap layout `, - inputTypes: ['null'], - args: { - // grid - strokeWidth: { - types: ['number'], - help: i18n.translate('xpack.lens.heatmapChart.config.strokeWidth.help', { - defaultMessage: 'Specifies the grid stroke width', - }), - required: false, - }, - strokeColor: { - types: ['string'], - help: i18n.translate('xpack.lens.heatmapChart.config.strokeColor.help', { - defaultMessage: 'Specifies the grid stroke color', - }), - required: false, - }, - cellHeight: { - types: ['number'], - help: i18n.translate('xpack.lens.heatmapChart.config.cellHeight.help', { - defaultMessage: 'Specifies the grid cell height', - }), - required: false, - }, - cellWidth: { - types: ['number'], - help: i18n.translate('xpack.lens.heatmapChart.config.cellWidth.help', { - defaultMessage: 'Specifies the grid cell width', - }), - required: false, - }, - // cells - isCellLabelVisible: { - types: ['boolean'], - help: i18n.translate('xpack.lens.heatmapChart.config.isCellLabelVisible.help', { - defaultMessage: 'Specifies whether or not the cell label is visible.', - }), - }, - // Y-axis - isYAxisLabelVisible: { - types: ['boolean'], - help: i18n.translate('xpack.lens.heatmapChart.config.isYAxisLabelVisible.help', { - defaultMessage: 'Specifies whether or not the Y-axis labels are visible.', - }), - }, - yAxisLabelWidth: { - types: ['number'], - help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelWidth.help', { - defaultMessage: 'Specifies the width of the Y-axis labels.', - }), - required: false, - }, - yAxisLabelColor: { - types: ['string'], - help: i18n.translate('xpack.lens.heatmapChart.config.yAxisLabelColor.help', { - defaultMessage: 'Specifies the color of the Y-axis labels.', - }), - required: false, - }, - // X-axis - isXAxisLabelVisible: { - types: ['boolean'], - help: i18n.translate('xpack.lens.heatmapChart.config.isXAxisLabelVisible.help', { - defaultMessage: 'Specifies whether or not the X-axis labels are visible.', - }), - }, - }, - fn(input, args) { - return { - type: HEATMAP_GRID_FUNCTION, - ...args, - }; - }, -}; - -/** - * TODO check if it's possible to make a shared function - * based on the XY chart - */ -export const heatmapLegendConfig: ExpressionFunctionDefinition< - typeof LEGEND_FUNCTION, - null, - HeatmapLegendConfig, - LegendConfigResult -> = { - name: LEGEND_FUNCTION, - aliases: [], - type: LEGEND_FUNCTION, - help: `Configure the heatmap chart's legend`, - inputTypes: ['null'], - args: { - isVisible: { - types: ['boolean'], - help: i18n.translate('xpack.lens.heatmapChart.legend.isVisible.help', { - defaultMessage: 'Specifies whether or not the legend is visible.', - }), - }, - position: { - types: ['string'], - options: [Position.Top, Position.Right, Position.Bottom, Position.Left], - help: i18n.translate('xpack.lens.heatmapChart.legend.position.help', { - defaultMessage: 'Specifies the legend position.', - }), - }, - }, - fn(input, args) { - return { - type: LEGEND_FUNCTION, - ...args, - }; - }, -}; - -export const heatmap: ExpressionFunctionDefinition< - typeof FUNCTION_NAME, - LensMultiTable, - HeatmapExpressionArgs, - HeatmapRender -> = { - name: FUNCTION_NAME, - type: 'render', - help: i18n.translate('xpack.lens.heatmap.expressionHelpLabel', { - defaultMessage: 'Heatmap renderer', - }), - args: { - title: { - types: ['string'], - help: i18n.translate('xpack.lens.heatmap.titleLabel', { - defaultMessage: 'Title', - }), - }, - description: { - types: ['string'], - help: '', - }, - xAccessor: { - types: ['string'], - help: '', - }, - yAccessor: { - types: ['string'], - help: '', - }, - valueAccessor: { - types: ['string'], - help: '', - }, - shape: { - types: ['string'], - help: '', - }, - palette: { - default: `{theme "palette" default={system_palette name="default"} }`, - help: '', - types: ['palette'], - }, - legend: { - types: [LEGEND_FUNCTION], - help: i18n.translate('xpack.lens.heatmapChart.legend.help', { - defaultMessage: 'Configure the chart legend.', - }), - }, - gridConfig: { - types: [HEATMAP_GRID_FUNCTION], - help: i18n.translate('xpack.lens.heatmapChart.gridConfig.help', { - defaultMessage: 'Configure the heatmap layout.', - }), - }, - }, - inputTypes: ['lens_multitable'], - fn(data: LensMultiTable, args: HeatmapExpressionArgs) { - return { - type: 'render', - as: LENS_HEATMAP_RENDERER, - value: { - data, - args, - }, - }; - }, -}; +export { heatmapGridConfig, heatmapLegendConfig, heatmap } from '../../common/expressions'; export const getHeatmapRenderer = (dependencies: { formatFactory: Promise; diff --git a/x-pack/plugins/lens/public/heatmap_visualization/index.ts b/x-pack/plugins/lens/public/heatmap_visualization/index.ts index 4599bd8d2a20878..11f9b907eb9291a 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/index.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { CoreSetup } from 'kibana/public'; -import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; -import { EditorFrameSetup, FormatFactory } from '../types'; -import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; +import type { CoreSetup } from 'kibana/public'; +import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; +import type { EditorFrameSetup } from '../types'; +import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; import { getTimeZone } from '../utils'; +import type { FormatFactory } from '../../common'; export interface HeatmapVisualizationPluginSetupPlugins { expressions: ExpressionsSetup; diff --git a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts index c11078be6c8b9c1..d7443ea8fe43df6 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.test.ts @@ -5,10 +5,10 @@ * 2.0. */ +import { Position } from '@elastic/charts'; import { getSuggestions } from './suggestions'; -import { HeatmapVisualizationState } from './types'; +import type { HeatmapVisualizationState } from './types'; import { HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants'; -import { Position } from '@elastic/charts'; describe('heatmap suggestions', () => { describe('rejects suggestions', () => { diff --git a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts index 5cddebe2cc2308d..3f27d5e81b50703 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/suggestions.ts @@ -8,8 +8,8 @@ import { partition } from 'lodash'; import { Position } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { Visualization } from '../types'; -import { HeatmapVisualizationState } from './types'; +import type { Visualization } from '../types'; +import type { HeatmapVisualizationState } from './types'; import { CHART_SHAPES, HEATMAP_GRID_FUNCTION, LEGEND_FUNCTION } from './constants'; export const getSuggestions: Visualization['getSuggestions'] = ({ diff --git a/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx b/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx index 6fd863ba919368f..c35143773551d27 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/toolbar_component.tsx @@ -9,9 +9,9 @@ import React, { memo } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Position } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { VisualizationToolbarProps } from '../types'; +import type { VisualizationToolbarProps } from '../types'; import { LegendSettingsPopover } from '../shared_components'; -import { HeatmapVisualizationState } from './types'; +import type { HeatmapVisualizationState } from './types'; const legendOptions: Array<{ id: string; value: 'auto' | 'show' | 'hide'; label: string }> = [ { diff --git a/x-pack/plugins/lens/public/heatmap_visualization/types.ts b/x-pack/plugins/lens/public/heatmap_visualization/types.ts index 32e3079c951d4a7..0cf830bea609aca 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/types.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/types.ts @@ -5,17 +5,12 @@ * 2.0. */ -import { Position } from '@elastic/charts'; -import { PaletteOutput } from '../../../../../src/plugins/charts/common'; -import { FormatFactory, LensBrushEvent, LensFilterEvent, LensMultiTable } from '../types'; -import { - CHART_SHAPES, - HEATMAP_GRID_FUNCTION, - LEGEND_FUNCTION, - LENS_HEATMAP_RENDERER, -} from './constants'; -import { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; -import { CustomPaletteParams } from '../shared_components'; +import type { PaletteOutput } from '../../../../../src/plugins/charts/common'; +import type { LensBrushEvent, LensFilterEvent } from '../types'; +import type { LensMultiTable, FormatFactory, CustomPaletteParams } from '../../common'; +import type { HeatmapGridConfigResult, HeatmapLegendConfigResult } from '../../common/expressions'; +import { CHART_SHAPES, LENS_HEATMAP_RENDERER } from './constants'; +import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; export type ChartShapes = typeof CHART_SHAPES[keyof typeof CHART_SHAPES]; @@ -24,7 +19,7 @@ export interface SharedHeatmapLayerState { xAccessor?: string; yAccessor?: string; valueAccessor?: string; - legend: LegendConfigResult; + legend: HeatmapLegendConfigResult; gridConfig: HeatmapGridConfigResult; } @@ -62,34 +57,3 @@ export type HeatmapRenderProps = HeatmapExpressionProps & { onSelectRange: (data: LensBrushEvent['data']) => void; paletteService: PaletteRegistry; }; - -export interface HeatmapLegendConfig { - /** - * Flag whether the legend should be shown. If there is just a single series, it will be hidden - */ - isVisible: boolean; - /** - * Position of the legend relative to the chart - */ - position: Position; -} - -export type LegendConfigResult = HeatmapLegendConfig & { type: typeof LEGEND_FUNCTION }; - -export interface HeatmapGridConfig { - // grid - strokeWidth?: number; - strokeColor?: string; - cellHeight?: number; - cellWidth?: number; - // cells - isCellLabelVisible: boolean; - // Y-axis - isYAxisLabelVisible: boolean; - yAxisLabelWidth?: number; - yAxisLabelColor?: string; - // X-axis - isXAxisLabelVisible: boolean; -} - -export type HeatmapGridConfigResult = HeatmapGridConfig & { type: typeof HEATMAP_GRID_FUNCTION }; diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts b/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts index 316a3ef36d66cb8..6cbe27fbf323fdb 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.test.ts @@ -19,8 +19,8 @@ import { LEGEND_FUNCTION, } from './constants'; import { Position } from '@elastic/charts'; -import { HeatmapVisualizationState } from './types'; -import { DatasourcePublicAPI, Operation } from '../types'; +import type { HeatmapVisualizationState } from './types'; +import type { DatasourcePublicAPI, Operation } from '../types'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; function exampleState(): HeatmapVisualizationState { diff --git a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx index 12fe28f801ef2cb..716792805e1b562 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/visualization.tsx @@ -12,8 +12,8 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { Ast } from '@kbn/interpreter/common'; import { Position } from '@elastic/charts'; import { PaletteRegistry } from '../../../../../src/plugins/charts/public'; -import { OperationMetadata, Visualization } from '../types'; -import { HeatmapVisualizationState } from './types'; +import type { OperationMetadata, Visualization } from '../types'; +import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; import { CHART_NAMES, @@ -27,9 +27,10 @@ import { } from './constants'; import { HeatmapToolbar } from './toolbar_component'; import { LensIconChartHeatmap } from '../assets/chart_heatmap'; -import { CustomPaletteParams, CUSTOM_PALETTE, getStopsForFixedMode } from '../shared_components'; +import { CUSTOM_PALETTE, getStopsForFixedMode } from '../shared_components'; import { HeatmapDimensionEditor } from './dimension_editor'; import { getSafePaletteParams } from './utils'; +import type { CustomPaletteParams } from '../../common'; const groupLabelForBar = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', { defaultMessage: 'Heatmap', diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 76afe7260a35a6e..84302f25d0a0235 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -11,8 +11,13 @@ export type { EmbeddableComponentProps, TypedLensByValueInput, } from './embeddable/embeddable_component'; +export type { XYState } from './xy_visualization/types'; +export type { DataType, OperationMetadata } from './types'; export type { - XYState, + PieVisualizationState, + PieLayerState, + SharedPieLayerState, + MetricState, AxesSettingsConfig, XYLayerConfig, LegendConfig, @@ -21,15 +26,8 @@ export type { YAxisMode, XYCurveType, YConfig, -} from './xy_visualization/types'; -export type { DataType, OperationMetadata } from './types'; -export type { - PieVisualizationState, - PieLayerState, - SharedPieLayerState, -} from './pie_visualization/types'; +} from '../common/expressions'; export type { DatatableVisualizationState } from './datatable_visualization/visualization'; -export type { MetricState } from './metric_visualization/types'; export type { IndexPatternPersistedState, PersistedIndexPatternLayer, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx index 3965f992805b5dd..d6091557ce235aa 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/dimension_panel.tsx @@ -16,7 +16,7 @@ import { IndexPatternColumn } from '../indexpattern'; import { isColumnInvalid } from '../utils'; import { IndexPatternPrivateState } from '../types'; import { DimensionEditor } from './dimension_editor'; -import { DateRange } from '../../../common'; +import type { DateRange } from '../../../common'; import { getOperationSupportMatrix } from './operation_support'; export type IndexPatternDimensionTriggerProps = DatasourceDimensionTriggerProps & { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx index db0a42047a1b86e..54eb3d48efccf5e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/field_select.tsx @@ -7,8 +7,9 @@ import './field_select.scss'; import { partition } from 'lodash'; -import React, { useMemo } from 'react'; +import React, { useMemo, useRef } from 'react'; import { i18n } from '@kbn/i18n'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; import { EuiComboBox, EuiFlexGroup, @@ -17,7 +18,6 @@ import { EuiComboBoxProps, } from '@elastic/eui'; import classNames from 'classnames'; -import { EuiHighlight } from '@elastic/eui'; import { OperationType } from '../indexpattern'; import { LensFieldIcon } from '../lens_field_icon'; import { DataType } from '../../types'; @@ -25,7 +25,7 @@ import { OperationSupportMatrix } from './operation_support'; import { IndexPattern, IndexPatternPrivateState } from '../types'; import { trackUiEvent } from '../../lens_ui_telemetry'; import { fieldExists } from '../pure_helpers'; - +import { TruncatedLabel } from './truncated_label'; export interface FieldChoice { type: 'field'; field: string; @@ -45,6 +45,10 @@ export interface FieldSelectProps extends EuiComboBoxProps(null); + const [labelProps, setLabelProps] = React.useState<{ + width: number; + font: string; + }>({ + width: DEFAULT_COMBOBOX_WIDTH - COMBOBOX_PADDINGS, + font: DEFAULT_FONT, + }); - return ( - { + if (comboBoxRef.current) { + const current = { + ...labelProps, + width: comboBoxRef.current?.clientWidth - COMBOBOX_PADDINGS, + }; + if (shouldRecomputeAll) { + current.font = window.getComputedStyle(comboBoxRef.current).font; } - singleSelection={{ asPlainText: true }} - onChange={(choices) => { - if (choices.length === 0) { - onDeleteColumn?.(); - return; - } + setLabelProps(current); + } + }; - const choice = (choices[0].value as unknown) as FieldChoice; + useEffectOnce(() => { + if (comboBoxRef.current) { + computeStyles(undefined, true); + } + window.addEventListener('resize', computeStyles); + }); - if (choice.field !== selectedField) { - trackUiEvent('indexpattern_dimension_field_changed'); - onChoose(choice); + return ( +
+ { - return ( - - - - - - {option.label} - - - ); - }} - {...rest} - /> + singleSelection={{ asPlainText: true }} + onChange={(choices) => { + if (choices.length === 0) { + onDeleteColumn?.(); + return; + } + + const choice = (choices[0].value as unknown) as FieldChoice; + + if (choice.field !== selectedField) { + trackUiEvent('indexpattern_dimension_field_changed'); + onChoose(choice); + } + }} + renderOption={(option, searchValue) => { + return ( + + + + + + + + + ); + }} + {...rest} + /> +
); } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx index 61e5da5931e88e3..7c611230683d391 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/time_scaling.tsx @@ -15,8 +15,8 @@ import { IndexPatternColumn, operationDefinitionMap, } from '../operations'; -import { unitSuffixesLong } from '../suffix_formatter'; -import { TimeScaleUnit } from '../time_scale'; +import type { TimeScaleUnit } from '../../../common/expressions'; +import { unitSuffixesLong } from '../../../common/suffix_formatter'; import { IndexPatternLayer } from '../types'; export function setTimeScaling( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx new file mode 100644 index 000000000000000..b558afddd689ec7 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { mount } from 'enzyme'; +import 'jest-canvas-mock'; +import { TruncatedLabel } from './truncated_label'; + +describe('truncated_label', () => { + const defaultProps = { + font: '14px Inter', + // jest-canvas-mock mocks measureText as the number of string characters, thats why the width is so low + width: 30, + search: '', + label: 'example_field', + }; + it('displays passed label if shorter than passed labelLength', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('example_field'); + }); + it('middle truncates label', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('example_….subcategory.subfield'); + }); + describe('with search value passed', () => { + it('constructs truncated label when searching for the string of index = 0', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('example_space.example_field.s…'); + expect(wrapper.find('mark').text()).toEqual('example_space'); + }); + it('constructs truncated label when searching for the string in the middle', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('…ample_field.subcategory.subf…'); + expect(wrapper.find('mark').text()).toEqual('ample_field'); + }); + it('constructs truncated label when searching for the string at the end of the label', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('…le_field.subcategory.subfield'); + expect(wrapper.find('mark').text()).toEqual('subf'); + }); + + it('constructs truncated label when searching for the string longer than the truncated width and highlights the whole content', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('…ample_space.example_field.su…'); + expect(wrapper.find('mark').text()).toEqual('…ample_space.example_field.su…'); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx new file mode 100644 index 000000000000000..47b1313a74c4ed2 --- /dev/null +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/truncated_label.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useMemo } from 'react'; +import { EuiMark } from '@elastic/eui'; +import { EuiHighlight } from '@elastic/eui'; + +const createContext = () => + document.createElement('canvas').getContext('2d') as CanvasRenderingContext2D; + +// extracted from getTextWidth for performance +const context = createContext(); + +const getTextWidth = (text: string, font: string) => { + const ctx = context ?? createContext(); + ctx.font = font; + const metrics = ctx.measureText(text); + return metrics.width; +}; + +const truncateLabel = ( + width: number, + font: string, + label: string, + approximateLength: number, + labelFn: (label: string, length: number) => string +) => { + let output = labelFn(label, approximateLength); + while (getTextWidth(output, font) > width) { + approximateLength = approximateLength - 1; + output = labelFn(label, approximateLength); + } + return output; +}; + +export const TruncatedLabel = React.memo(function TruncatedLabel({ + label, + width, + search, + font, +}: { + label: string; + search: string; + width: number; + font: string; +}) { + const textWidth = useMemo(() => getTextWidth(label, font), [label, font]); + + if (textWidth < width) { + return {label}; + } + + const searchPosition = label.indexOf(search); + const approximateLen = Math.round((width * label.length) / textWidth); + const separator = `…`; + let separatorsLength = separator.length; + let labelFn; + + if (!search || searchPosition === -1) { + labelFn = (text: string, length: number) => + `${text.substr(0, 8)}${separator}${text.substr(text.length - (length - 8))}`; + } else if (searchPosition === 0) { + // search phrase at the beginning + labelFn = (text: string, length: number) => `${text.substr(0, length)}${separator}`; + } else if (approximateLen > label.length - searchPosition) { + // search phrase close to the end or at the end + labelFn = (text: string, length: number) => `${separator}${text.substr(text.length - length)}`; + } else { + // search phrase is in the middle + labelFn = (text: string, length: number) => + `${separator}${text.substr(searchPosition, length)}${separator}`; + separatorsLength = 2 * separator.length; + } + + const outputLabel = truncateLabel(width, font, label, approximateLen, labelFn); + + return search.length < outputLabel.length - separatorsLength ? ( + {outputLabel} + ) : ( + {outputLabel} + ); +}); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts index f8bc84643bcaba1..35afd28c0f1ab76 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/index.ts @@ -43,14 +43,14 @@ export class IndexPatternDatasource { renameColumns, formatColumn, counterRate, - getTimeScaleFunction, + timeScale, getSuffixFormatter, } = await import('../async_services'); return core .getStartServices() .then(([coreStart, { data, indexPatternFieldEditor, uiActions }]) => { data.fieldFormats.register([getSuffixFormatter(data.fieldFormats.deserialize)]); - expressions.registerFunction(getTimeScaleFunction(data)); + expressions.registerFunction(timeScale); expressions.registerFunction(counterRate); expressions.registerFunction(renameColumns); expressions.registerFunction(formatColumn); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index 34be770a7f50d64..2cbe801a5b7b855 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -69,11 +69,15 @@ export function columnToOperation(column: IndexPatternColumn, uniqueLabel?: stri }; } -export * from './rename_columns'; -export * from './format_column'; -export * from './time_scale'; -export * from './counter_rate'; -export * from './suffix_formatter'; +export { + CounterRateArgs, + ExpressionFunctionCounterRate, + counterRate, +} from '../../common/expressions'; +export { FormatColumnArgs, supportedFormats, formatColumn } from '../../common/expressions'; +export { getSuffixFormatter, unitSuffixesLong } from '../../common/suffix_formatter'; +export { timeScale, TimeScaleArgs } from '../../common/expressions'; +export { renameColumns } from '../../common/expressions'; export function getIndexPatternDatasource({ core, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts index 87116f71919b563..34b33d35d41399d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/calculations/utils.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import type { ExpressionFunctionAST } from '@kbn/interpreter/common'; import memoizeOne from 'memoize-one'; -import type { TimeScaleUnit } from '../../../time_scale'; +import type { TimeScaleUnit } from '../../../../../common/expressions'; import type { IndexPattern, IndexPatternLayer } from '../../../types'; import { adjustTimeScaleLabelSuffix } from '../../time_scale_utils'; import type { ReferenceBasedIndexPatternColumn } from '../column_types'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts index ae606a5851665e1..15bd7d4242b921d 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/column_types.ts @@ -7,7 +7,7 @@ import { Query } from 'src/plugins/data/public'; import type { Operation } from '../../../types'; -import { TimeScaleUnit } from '../../time_scale'; +import type { TimeScaleUnit } from '../../../../common/expressions'; import type { OperationType } from '../definitions'; export interface BaseIndexPatternColumn extends Operation { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx index fd2125800280876..cb737d694295dc1 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/ranges/ranges.tsx @@ -17,7 +17,7 @@ import { RangeEditor } from './range_editor'; import { OperationDefinition } from '../index'; import { FieldBasedIndexPatternColumn } from '../column_types'; import { updateColumnParam } from '../../layer_helpers'; -import { supportedFormats } from '../../../format_column'; +import { supportedFormats } from '../../../../../common/expressions'; import { MODES, AUTO_BARS, DEFAULT_INTERVAL, MIN_HISTOGRAM_BARS, SLICES } from './constants'; import { IndexPattern, IndexPatternField } from '../../../types'; import { getInvalidFieldMessage, isValidNumber } from '../helpers'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index b5b1960b7b76913..1e0d0792e132a63 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -32,7 +32,7 @@ import { getSortScoreByPriority } from './operations'; import { generateId } from '../../id_generator'; import { ReferenceBasedIndexPatternColumn } from './definitions/column_types'; import { FormulaIndexPatternColumn, regenerateLayerFromAst } from './definitions/formula'; -import { TimeScaleUnit } from '../time_scale'; +import type { TimeScaleUnit } from '../../../common/expressions'; interface ColumnAdvancedParams { filter?: Query | undefined; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts index 152fcaa457c3bcc..dbdfd5c564125e7 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.test.ts @@ -6,7 +6,7 @@ */ import type { IndexPatternLayer } from '../types'; -import type { TimeScaleUnit } from '../time_scale'; +import type { TimeScaleUnit } from '../../../common/expressions'; import type { IndexPatternColumn } from './definitions'; import { adjustTimeScaleLabelSuffix, adjustTimeScaleOnOtherColumnChange } from './time_scale_utils'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts index a0b61060b9f3add..a6c056933f022dd 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/time_scale_utils.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { unitSuffixesLong } from '../suffix_formatter'; -import type { TimeScaleUnit } from '../time_scale'; +import { unitSuffixesLong } from '../../../common/suffix_formatter'; +import type { TimeScaleUnit } from '../../../common/expressions'; import type { IndexPatternLayer } from '../types'; import type { IndexPatternColumn } from './definitions'; diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts b/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts deleted file mode 100644 index 368e06110efc98d..000000000000000 --- a/x-pack/plugins/lens/public/indexpattern_datasource/time_scale.ts +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import moment from 'moment-timezone'; -import { i18n } from '@kbn/i18n'; -import { ExpressionFunctionDefinition, Datatable } from 'src/plugins/expressions/public'; -import { DataPublicPluginStart } from 'src/plugins/data/public'; -import { search } from '../../../../../src/plugins/data/public'; -import { buildResultColumns } from '../../../../../src/plugins/expressions/common'; - -export type TimeScaleUnit = 's' | 'm' | 'h' | 'd'; - -export interface TimeScaleArgs { - dateColumnId: string; - inputColumnId: string; - outputColumnId: string; - targetUnit: TimeScaleUnit; - outputColumnName?: string; -} - -const unitInMs: Record = { - s: 1000, - m: 1000 * 60, - h: 1000 * 60 * 60, - d: 1000 * 60 * 60 * 24, -}; - -export function getTimeScaleFunction(data: DataPublicPluginStart) { - const timeScale: ExpressionFunctionDefinition< - 'lens_time_scale', - Datatable, - TimeScaleArgs, - Promise - > = { - name: 'lens_time_scale', - type: 'datatable', - help: '', - args: { - dateColumnId: { - types: ['string'], - help: '', - required: true, - }, - inputColumnId: { - types: ['string'], - help: '', - required: true, - }, - outputColumnId: { - types: ['string'], - help: '', - required: true, - }, - outputColumnName: { - types: ['string'], - help: '', - }, - targetUnit: { - types: ['string'], - options: ['s', 'm', 'h', 'd'], - help: '', - required: true, - }, - }, - inputTypes: ['datatable'], - async fn( - input, - { dateColumnId, inputColumnId, outputColumnId, outputColumnName, targetUnit }: TimeScaleArgs - ) { - const dateColumnDefinition = input.columns.find((column) => column.id === dateColumnId); - - if (!dateColumnDefinition) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.dateColumnMissingMessage', { - defaultMessage: 'Specified dateColumnId {columnId} does not exist.', - values: { - columnId: dateColumnId, - }, - }) - ); - } - - const resultColumns = buildResultColumns( - input, - outputColumnId, - inputColumnId, - outputColumnName, - { allowColumnOverwrite: true } - ); - - if (!resultColumns) { - return input; - } - - const targetUnitInMs = unitInMs[targetUnit]; - const timeInfo = search.aggs.getDateHistogramMetaDataByDatatableColumn(dateColumnDefinition); - const intervalDuration = timeInfo?.interval && search.aggs.parseInterval(timeInfo.interval); - - if (!timeInfo || !intervalDuration) { - throw new Error( - i18n.translate('xpack.lens.functions.timeScale.timeInfoMissingMessage', { - defaultMessage: 'Could not fetch date histogram information', - }) - ); - } - // the datemath plugin always parses dates by using the current default moment time zone. - // to use the configured time zone, we are switching just for the bounds calculation. - const defaultTimezone = moment().zoneName(); - moment.tz.setDefault(timeInfo.timeZone); - - const timeBounds = - timeInfo.timeRange && data.query.timefilter.timefilter.calculateBounds(timeInfo.timeRange); - - const result = { - ...input, - columns: resultColumns, - rows: input.rows.map((row) => { - const newRow = { ...row }; - - let startOfBucket = moment(row[dateColumnId]); - let endOfBucket = startOfBucket.clone().add(intervalDuration); - if (timeBounds && timeBounds.min) { - startOfBucket = moment.max(startOfBucket, timeBounds.min); - } - if (timeBounds && timeBounds.max) { - endOfBucket = moment.min(endOfBucket, timeBounds.max); - } - const bucketSize = endOfBucket.diff(startOfBucket); - const factor = bucketSize / targetUnitInMs; - - const currentValue = newRow[inputColumnId]; - if (currentValue != null) { - newRow[outputColumnId] = Number(currentValue) / factor; - } - - return newRow; - }), - }; - // reset default moment timezone - moment.tz.setDefault(defaultTimezone); - - return result; - }, - }; - return timeScale; -} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index b6f5c364e2d046c..69b60711e5186a5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -22,9 +22,10 @@ import { import { IndexPatternColumn } from './indexpattern'; import { operationDefinitionMap } from './operations'; import { IndexPattern, IndexPatternPrivateState, IndexPatternLayer } from './types'; -import { OriginalColumn } from './rename_columns'; import { dateHistogramOperation } from './operations/definitions'; +type OriginalColumn = { id: string } & IndexPatternColumn; + function getExpressionForLayer( layer: IndexPatternLayer, indexPattern: IndexPattern, diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx index 27a7659b1c81742..21c68a9fe1d826b 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.test.tsx @@ -5,13 +5,13 @@ * 2.0. */ -import { metricChart, MetricChart } from './expression'; -import { LensMultiTable } from '../types'; +import { MetricChart, metricChart } from './expression'; +import { MetricConfig } from '../../common/expressions'; import React from 'react'; import { shallow } from 'enzyme'; -import { MetricConfig } from './types'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { IFieldFormat } from '../../../../../src/plugins/data/public'; +import type { LensMultiTable } from '../../common'; function sampleArgs() { const data: LensMultiTable = { diff --git a/x-pack/plugins/lens/public/metric_visualization/expression.tsx b/x-pack/plugins/lens/public/metric_visualization/expression.tsx index cf6921b2ca5790e..5a1e0d7fb5bdf6c 100644 --- a/x-pack/plugins/lens/public/metric_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/expression.tsx @@ -9,75 +9,19 @@ import './expression.scss'; import { I18nProvider } from '@kbn/i18n/react'; import React from 'react'; import ReactDOM from 'react-dom'; -import { - ExpressionFunctionDefinition, +import type { ExpressionRenderDefinition, IInterpreterRenderHandlers, } from '../../../../../src/plugins/expressions/public'; -import { MetricConfig } from './types'; -import { FormatFactory, LensMultiTable } from '../types'; import { AutoScale } from './auto_scale'; import { VisualizationContainer } from '../visualization_container'; import { EmptyPlaceholder } from '../shared_components'; import { LensIconChartMetric } from '../assets/chart_metric'; +import type { FormatFactory } from '../../common'; +import type { MetricChartProps } from '../../common/expressions'; -export interface MetricChartProps { - data: LensMultiTable; - args: MetricConfig; -} - -export interface MetricRender { - type: 'render'; - as: 'lens_metric_chart_renderer'; - value: MetricChartProps; -} - -export const metricChart: ExpressionFunctionDefinition< - 'lens_metric_chart', - LensMultiTable, - Omit, - MetricRender -> = { - name: 'lens_metric_chart', - type: 'render', - help: 'A metric chart', - args: { - title: { - types: ['string'], - help: 'The chart title.', - }, - description: { - types: ['string'], - help: '', - }, - metricTitle: { - types: ['string'], - help: 'The title of the metric shown.', - }, - accessor: { - types: ['string'], - help: 'The column whose value is being displayed', - }, - mode: { - types: ['string'], - options: ['reduced', 'full'], - default: 'full', - help: - 'The display mode of the chart - reduced will only show the metric itself without min size', - }, - }, - inputTypes: ['lens_multitable'], - fn(data, args) { - return { - type: 'render', - as: 'lens_metric_chart_renderer', - value: { - data, - args, - }, - } as MetricRender; - }, -}; +export { metricChart } from '../../common/expressions'; +export type { MetricState, MetricConfig } from '../../common/expressions'; export const getMetricChartRenderer = ( formatFactory: Promise diff --git a/x-pack/plugins/lens/public/metric_visualization/index.ts b/x-pack/plugins/lens/public/metric_visualization/index.ts index c94063ed0bd7413..484dc6140ecf220 100644 --- a/x-pack/plugins/lens/public/metric_visualization/index.ts +++ b/x-pack/plugins/lens/public/metric_visualization/index.ts @@ -5,9 +5,10 @@ * 2.0. */ -import { CoreSetup } from 'kibana/public'; -import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; -import { EditorFrameSetup, FormatFactory } from '../types'; +import type { CoreSetup } from 'kibana/public'; +import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; +import type { EditorFrameSetup } from '../types'; +import type { FormatFactory } from '../../common'; export interface MetricVisualizationPluginSetupPlugins { expressions: ExpressionsSetup; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts index e7f510f106fffa7..d07dccb7701962e 100644 --- a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts +++ b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts @@ -6,7 +6,7 @@ */ import { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../types'; -import { MetricState } from './types'; +import type { MetricState } from '../../common/expressions'; import { LensIconChartMetric } from '../assets/chart_metric'; /** diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts b/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts index 2882d9c4c0246b9..2c359d139bb3b77 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts @@ -6,7 +6,7 @@ */ import { metricVisualization } from './visualization'; -import { MetricState } from './types'; +import type { MetricState } from '../../common/expressions'; import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; import { generateId } from '../id_generator'; import { DatasourcePublicAPI, FramePublicAPI } from '../types'; diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx index 49565f53bda366b..d312030b5a490da 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx @@ -10,7 +10,7 @@ import { Ast } from '@kbn/interpreter/target/common'; import { getSuggestions } from './metric_suggestions'; import { LensIconChartMetric } from '../assets/chart_metric'; import { Visualization, OperationMetadata, DatasourcePublicAPI } from '../types'; -import { MetricState } from './types'; +import type { MetricState } from '../../common/expressions'; const toExpression = ( state: MetricState, diff --git a/x-pack/plugins/lens/public/pie_visualization/expression.tsx b/x-pack/plugins/lens/public/pie_visualization/expression.tsx index 208ce746f4b9d4f..ce36f88b2805e99 100644 --- a/x-pack/plugins/lens/public/pie_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/expression.tsx @@ -8,108 +8,18 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; -import { Position } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; -import { +import type { IInterpreterRenderHandlers, ExpressionRenderDefinition, - ExpressionFunctionDefinition, } from 'src/plugins/expressions/public'; -import { LensMultiTable, FormatFactory, LensFilterEvent } from '../types'; -import { PieExpressionProps, PieExpressionArgs } from './types'; +import type { LensFilterEvent } from '../types'; import { PieComponent } from './render_function'; -import { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; +import type { FormatFactory } from '../../common'; +import type { PieExpressionProps } from '../../common/expressions'; +import type { ChartsPluginSetup, PaletteRegistry } from '../../../../../src/plugins/charts/public'; -export interface PieRender { - type: 'render'; - as: 'lens_pie_renderer'; - value: PieExpressionProps; -} - -export const pie: ExpressionFunctionDefinition< - 'lens_pie', - LensMultiTable, - PieExpressionArgs, - PieRender -> = { - name: 'lens_pie', - type: 'render', - help: i18n.translate('xpack.lens.pie.expressionHelpLabel', { - defaultMessage: 'Pie renderer', - }), - args: { - title: { - types: ['string'], - help: 'The chart title.', - }, - description: { - types: ['string'], - help: '', - }, - groups: { - types: ['string'], - multi: true, - help: '', - }, - metric: { - types: ['string'], - help: '', - }, - shape: { - types: ['string'], - options: ['pie', 'donut', 'treemap'], - help: '', - }, - hideLabels: { - types: ['boolean'], - help: '', - }, - numberDisplay: { - types: ['string'], - options: ['hidden', 'percent', 'value'], - help: '', - }, - categoryDisplay: { - types: ['string'], - options: ['default', 'inside', 'hide'], - help: '', - }, - legendDisplay: { - types: ['string'], - options: ['default', 'show', 'hide'], - help: '', - }, - nestedLegend: { - types: ['boolean'], - help: '', - }, - legendPosition: { - types: ['string'], - options: [Position.Top, Position.Right, Position.Bottom, Position.Left], - help: '', - }, - percentDecimals: { - types: ['number'], - help: '', - }, - palette: { - default: `{theme "palette" default={system_palette name="default"} }`, - help: '', - types: ['palette'], - }, - }, - inputTypes: ['lens_multitable'], - fn(data: LensMultiTable, args: PieExpressionArgs) { - return { - type: 'render', - as: 'lens_pie_renderer', - value: { - data, - args, - }, - }; - }, -}; +export { pie } from '../../common/expressions'; export const getPieRenderer = (dependencies: { formatFactory: Promise; diff --git a/x-pack/plugins/lens/public/pie_visualization/index.ts b/x-pack/plugins/lens/public/pie_visualization/index.ts index 9f4a176ef8aa8f0..aa74eb5088ea314 100644 --- a/x-pack/plugins/lens/public/pie_visualization/index.ts +++ b/x-pack/plugins/lens/public/pie_visualization/index.ts @@ -5,11 +5,12 @@ * 2.0. */ -import { CoreSetup } from 'src/core/public'; -import { ExpressionsSetup } from 'src/plugins/expressions/public'; -import { EditorFrameSetup, FormatFactory } from '../types'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; -import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; +import type { CoreSetup } from 'src/core/public'; +import type { ExpressionsSetup } from 'src/plugins/expressions/public'; +import type { EditorFrameSetup } from '../types'; +import type { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; +import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; +import type { FormatFactory } from '../../common'; export interface PieVisualizationPluginSetupPlugins { editorFrame: EditorFrameSetup; diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx index a3a10b803fcd340..adef7188d12d0a3 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.test.tsx @@ -16,9 +16,9 @@ import { Chart, } from '@elastic/charts'; import { shallow } from 'enzyme'; -import { LensMultiTable } from '../types'; +import type { LensMultiTable } from '../../common'; +import type { PieExpressionArgs } from '../../common/expressions'; import { PieComponent } from './render_function'; -import { PieExpressionArgs } from './types'; import { VisualizationContainer } from '../visualization_container'; import { EmptyPlaceholder } from '../shared_components'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index b161a81a835f13b..ac0aa6cd4b1f184 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -24,10 +24,11 @@ import { ElementClickListener, } from '@elastic/charts'; import { RenderMode } from 'src/plugins/expressions'; -import { FormatFactory, LensFilterEvent } from '../types'; +import type { LensFilterEvent } from '../types'; import { VisualizationContainer } from '../visualization_container'; import { CHART_NAMES, DEFAULT_PERCENT_DECIMALS } from './constants'; -import { PieExpressionProps } from './types'; +import type { FormatFactory } from '../../common'; +import type { PieExpressionProps } from '../../common/expressions'; import { getSliceValue, getFilterContext } from './render_helpers'; import { EmptyPlaceholder } from '../shared_components'; import './visualization.scss'; diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts index a527a3c8645430a..36470fa3d74cf83 100644 --- a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts +++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts @@ -8,7 +8,7 @@ import { PaletteOutput } from 'src/plugins/charts/public'; import { DataType, SuggestionRequest } from '../types'; import { suggestions } from './suggestions'; -import { PieVisualizationState } from './types'; +import type { PieVisualizationState } from '../../common/expressions'; describe('suggestions', () => { describe('pie', () => { diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts index 644f0a0cd8aaf84..22be8e3357bbbb6 100644 --- a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts +++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts @@ -7,8 +7,8 @@ import { partition } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { SuggestionRequest, VisualizationSuggestion } from '../types'; -import { PieVisualizationState } from './types'; +import type { SuggestionRequest, VisualizationSuggestion } from '../types'; +import type { PieVisualizationState } from '../../common/expressions'; import { CHART_NAMES, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants'; function shouldReject({ table, keptLayerIds }: SuggestionRequest) { diff --git a/x-pack/plugins/lens/public/pie_visualization/to_expression.ts b/x-pack/plugins/lens/public/pie_visualization/to_expression.ts index 14f1fe81c7bd688..7ee26383cebbf2b 100644 --- a/x-pack/plugins/lens/public/pie_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/pie_visualization/to_expression.ts @@ -9,7 +9,7 @@ import { Ast } from '@kbn/interpreter/common'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { Operation, DatasourcePublicAPI } from '../types'; import { DEFAULT_PERCENT_DECIMALS } from './constants'; -import { PieVisualizationState } from './types'; +import type { PieVisualizationState } from '../../common/expressions'; export function toExpression( state: PieVisualizationState, diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index a2596f7ce1e0ff3..5da69e47f861cd2 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -15,10 +15,10 @@ import { EuiRange, EuiHorizontalRule, } from '@elastic/eui'; -import { Position } from '@elastic/charts'; -import { PaletteRegistry } from 'src/plugins/charts/public'; +import type { Position } from '@elastic/charts'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; import { DEFAULT_PERCENT_DECIMALS } from './constants'; -import { PieVisualizationState, SharedPieLayerState } from './types'; +import type { PieVisualizationState, SharedPieLayerState } from '../../common/expressions'; import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types'; import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue } from '../shared_components'; import { PalettePicker } from '../shared_components'; diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.test.ts b/x-pack/plugins/lens/public/pie_visualization/visualization.test.ts index 2a961cef315bf2d..07a4161e7d239b8 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.test.ts @@ -6,7 +6,7 @@ */ import { getPieVisualization } from './visualization'; -import { PieVisualizationState } from './types'; +import type { PieVisualizationState } from '../../common/expressions'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; jest.mock('../id_generator'); diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx index c82fdb2766f7eb3..5d75d82220d1fe4 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx @@ -9,10 +9,10 @@ import React from 'react'; import { render } from 'react-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; -import { PaletteRegistry } from 'src/plugins/charts/public'; -import { Visualization, OperationMetadata, AccessorConfig } from '../types'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { Visualization, OperationMetadata, AccessorConfig } from '../types'; import { toExpression, toPreviewExpression } from './to_expression'; -import { PieLayerState, PieVisualizationState } from './types'; +import type { PieLayerState, PieVisualizationState } from '../../common/expressions'; import { suggestions } from './suggestions'; import { CHART_NAMES, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants'; import { DimensionEditor, PieToolbar } from './toolbar'; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx index 37197b232ddf551..1431e6ad135bef5 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/color_stops.tsx @@ -22,7 +22,7 @@ import useUnmount from 'react-use/lib/useUnmount'; import { DEFAULT_COLOR } from './constants'; import { getDataMinMax, getStepValue, isValidColor } from './utils'; import { TooltipWrapper, useDebouncedValue } from '../index'; -import { ColorStop, CustomPaletteParams } from './types'; +import type { ColorStop, CustomPaletteParams } from '../../../common'; const idGeneratorFn = htmlIdGenerator(); diff --git a/x-pack/plugins/lens/public/shared_components/coloring/constants.ts b/x-pack/plugins/lens/public/shared_components/coloring/constants.ts index 5e6fc207656ac32..29b50d3aee22d8d 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/constants.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { RequiredPaletteParamTypes } from './types'; +import type { RequiredPaletteParamTypes } from '../../../common'; export const DEFAULT_PALETTE_NAME = 'positive'; export const FIXED_PROGRESSION = 'fixed' as const; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/index.ts b/x-pack/plugins/lens/public/shared_components/coloring/index.ts index 0ad831603ab95fa..7cbf79ac43b1e77 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/index.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/index.ts @@ -8,6 +8,5 @@ export { CustomizablePalette } from './palette_configuration'; export { PalettePanelContainer } from './palette_panel_container'; export { CustomStops } from './color_stops'; -export * from './types'; export * from './utils'; export * from './constants'; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx index e36e817b3d71442..ad1755bdbe85cba 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { EuiColorPalettePickerPaletteProps } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test/jest'; import { chartPluginMock } from 'src/plugins/charts/public/mocks'; -import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { ReactWrapper } from 'enzyme'; -import { CustomPaletteParams } from './types'; +import type { CustomPaletteParams } from '../../../common'; import { applyPaletteParams } from './utils'; import { CustomizablePalette } from './palette_configuration'; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx index 993bf4f78dd20bd..bc6a590db0cb7cf 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_configuration.tsx @@ -6,7 +6,7 @@ */ import React, { FC } from 'react'; -import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { EuiFormRow, htmlIdGenerator, @@ -26,7 +26,7 @@ import './palette_configuration.scss'; import { CustomStops } from './color_stops'; import { defaultPaletteParams, CUSTOM_PALETTE, DEFAULT_COLOR_STEPS } from './constants'; -import { CustomPaletteParams, RequiredPaletteParamTypes } from './types'; +import type { CustomPaletteParams, RequiredPaletteParamTypes } from '../../../common'; import { getColorStops, getPaletteStops, diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx index 1371fbe73ef8454..583d6e25ed4e288 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_panel_container.tsx @@ -7,6 +7,7 @@ import './palette_panel_container.scss'; +import { i18n } from '@kbn/i18n'; import React, { useState, useEffect, MutableRefObject } from 'react'; import { EuiFlyoutHeader, @@ -21,8 +22,6 @@ import { EuiPortal, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - export function PalettePanelContainer({ isOpen, handleClose, diff --git a/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx b/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx index 164ed9bf067a662..2a415cd178925b0 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/coloring/palette_picker.tsx @@ -13,9 +13,9 @@ import { DEFAULT_COLOR_STEPS, FIXED_PROGRESSION, defaultPaletteParams, -} from '../../shared_components/coloring/constants'; -import { CustomPaletteParams } from '../../shared_components/coloring/types'; -import { getStopsForFixedMode } from '../../shared_components/coloring/utils'; +} from './constants'; +import type { CustomPaletteParams } from '../../../common'; +import { getStopsForFixedMode } from './utils'; function getCustomPaletteConfig( palettes: PaletteRegistry, diff --git a/x-pack/plugins/lens/public/shared_components/coloring/types.ts b/x-pack/plugins/lens/public/shared_components/coloring/types.ts deleted file mode 100644 index d9a8edf0ccb62bc..000000000000000 --- a/x-pack/plugins/lens/public/shared_components/coloring/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -export interface ColorStop { - color: string; - stop: number; -} - -export interface CustomPaletteParams { - name?: string; - reverse?: boolean; - rangeType?: 'number' | 'percent'; - continuity?: 'above' | 'below' | 'all' | 'none'; - progression?: 'fixed'; - rangeMin?: number; - rangeMax?: number; - stops?: ColorStop[]; - colorStops?: ColorStop[]; - steps?: number; -} - -export type RequiredPaletteParamTypes = Required; diff --git a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts index 9c42ce5013b9b94..8cd0a6cf49001dc 100644 --- a/x-pack/plugins/lens/public/shared_components/coloring/utils.ts +++ b/x-pack/plugins/lens/public/shared_components/coloring/utils.ts @@ -9,7 +9,7 @@ import chroma from 'chroma-js'; import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps/theme'; import { isColorDark } from '@elastic/eui'; -import { Datatable } from 'src/plugins/expressions/public'; +import type { Datatable } from 'src/plugins/expressions/public'; import { CUSTOM_PALETTE, defaultPaletteParams, @@ -17,7 +17,7 @@ import { DEFAULT_MAX_STOP, DEFAULT_MIN_STOP, } from './constants'; -import { CustomPaletteParams, ColorStop } from './types'; +import type { CustomPaletteParams, ColorStop } from '../../../common'; /** * Some name conventions here: diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index cb47dcf6ec3889a..3d87d234ae98665 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -16,11 +16,10 @@ import { ExpressionRendererEvent, IInterpreterRenderHandlers, Datatable, - SerializedFieldFormat, } from '../../../../src/plugins/expressions/public'; import { DraggingIdentifier, DragDropIdentifier, DragContextState } from './drag_drop'; import { DateRange } from '../common'; -import { Query, Filter, IFieldFormat } from '../../../../src/plugins/data/public'; +import { Query, Filter } from '../../../../src/plugins/data/public'; import { VisualizeFieldContext } from '../../../../src/plugins/ui_actions/public'; import { RangeSelectContext, ValueClickContext } from '../../../../src/plugins/embeddable/public'; import { @@ -37,8 +36,6 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public'; export type ErrorCallback = (e: { message: string }) => void; -export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat; - export interface PublicAPIProps { state: T; layerId: string; @@ -387,15 +384,6 @@ export interface OperationMetadata { // introduce a raw document datasource, this should be considered here. } -export interface LensMultiTable { - type: 'lens_multitable'; - tables: Record; - dateRange?: { - fromDate: Date; - toDate: Date; - }; -} - export interface VisualizationConfigProps { layerId: string; frame: Pick; diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts index b82afeb1b7d1dfc..873827700d6e8b5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { LayerArgs } from './types'; +import { LayerArgs } from '../../common/expressions'; import { Datatable } from '../../../../../src/plugins/expressions/public'; import { getAxesConfiguration } from './axes_configuration'; diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts index 58a80ad0fed3787..83d86eb410b195c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts +++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { AxisExtentConfig, XYLayerConfig } from './types'; +import { FormatFactory } from '../../common'; +import { AxisExtentConfig, XYLayerConfig } from '../../common/expressions'; import { Datatable, SerializedFieldFormat } from '../../../../../src/plugins/expressions/public'; import { IFieldFormat } from '../../../../../src/plugins/data/public'; @@ -33,7 +34,7 @@ export function getAxesConfiguration( layers: XYLayerConfig[], shouldRotate: boolean, tables?: Record, - formatFactory?: (mapping: SerializedFieldFormat) => IFieldFormat + formatFactory?: FormatFactory ): GroupsConfiguration { const series: { auto: FormattedMetric[]; left: FormattedMetric[]; right: FormattedMetric[] } = { auto: [], diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx index a0d1dae2145d5a5..52098ab92cad654 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx @@ -20,7 +20,7 @@ import { EuiFieldNumber, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { XYLayerConfig, AxesSettingsConfig, AxisExtentConfig } from './types'; +import { XYLayerConfig, AxesSettingsConfig, AxisExtentConfig } from '../../common/expressions'; import { ToolbarPopover, useDebouncedValue } from '../shared_components'; import { isHorizontalChart } from './state_helpers'; import { EuiIconAxisBottom } from '../assets/axis_bottom'; diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.test.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.test.ts index 04a64d7ff5c9362..390eded97d705b1 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { FormatFactory, LensMultiTable } from '../types'; import { getColorAssignments } from './color_assignment'; -import { LayerArgs } from './types'; +import type { FormatFactory, LensMultiTable } from '../../common'; +import type { LayerArgs } from '../../common/expressions'; describe('color_assignment', () => { const layers: LayerArgs[] = [ diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index ef0c350f2096195..1e00d821d9b30ff 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -6,11 +6,12 @@ */ import { uniq, mapValues } from 'lodash'; -import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; -import { Datatable } from 'src/plugins/expressions'; -import { AccessorConfig, FormatFactory, FramePublicAPI } from '../types'; +import type { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import type { Datatable } from 'src/plugins/expressions'; +import type { AccessorConfig, FramePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; -import { XYLayerConfig } from './types'; +import type { FormatFactory } from '../../common'; +import type { XYLayerConfig } from '../../common/expressions'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index b018e62f1fd8f7d..3a28f137f93bd33 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -22,27 +22,22 @@ import { LayoutDirection, } from '@elastic/charts'; import { PaletteOutput } from 'src/plugins/charts/public'; +import { calculateMinInterval, XYChart, XYChartRenderProps, xyChart } from './expression'; +import type { LensMultiTable } from '../../common'; import { - calculateMinInterval, - xyChart, - XYChart, - XYChartProps, - XYChartRenderProps, -} from './expression'; -import { LensMultiTable } from '../types'; -import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public'; -import React from 'react'; -import { shallow } from 'enzyme'; -import { + layerConfig, + legendConfig, + tickLabelsConfig, + gridlinesConfig, XYArgs, LegendConfig, - legendConfig, - layerConfig, LayerArgs, AxesSettingsConfig, - tickLabelsConfig, - gridlinesConfig, -} from './types'; + XYChartProps, +} from '../../common/expressions'; +import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public'; +import React from 'react'; +import { shallow } from 'enzyme'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { mountWithIntl } from '@kbn/test/jest'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 7c767cd1d1b04d3..4cd63c5747e8714 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -30,8 +30,7 @@ import { LabelOverflowConstraint, } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; -import { - ExpressionFunctionDefinition, +import type { ExpressionRenderDefinition, Datatable, DatatableRow, @@ -39,24 +38,20 @@ import { import { IconType } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { RenderMode } from 'src/plugins/expressions'; -import { - LensMultiTable, - FormatFactory, - ILensInterpreterRenderHandlers, - LensFilterEvent, - LensBrushEvent, -} from '../types'; -import { XYArgs, SeriesType, visualizationTypes, LayerArgs } from './types'; +import type { ILensInterpreterRenderHandlers, LensFilterEvent, LensBrushEvent } from '../types'; +import type { LensMultiTable, FormatFactory } from '../../common'; +import { LayerArgs, SeriesType, XYChartProps } from '../../common/expressions'; +import { visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; import { isHorizontalChart, getSeriesColor } from './state_helpers'; -import { ExpressionValueSearchContext, search } from '../../../../../src/plugins/data/public'; +import { search } from '../../../../../src/plugins/data/public'; import { ChartsPluginSetup, PaletteRegistry, SeriesLayer, } from '../../../../../src/plugins/charts/public'; import { EmptyPlaceholder } from '../shared_components'; -import { fittingFunctionDefinitions, getFitOptions } from './fitting_functions'; +import { getFitOptions } from './fitting_functions'; import { getAxesConfiguration, GroupsConfiguration, validateExtent } from './axes_configuration'; import { getColorAssignments } from './color_assignment'; import { getXDomain, XyEndzones } from './x_domain'; @@ -76,16 +71,16 @@ type SeriesSpec = InferPropType & InferPropType & InferPropType; -export interface XYChartProps { - data: LensMultiTable; - args: XYArgs; -} - -export interface XYRender { - type: 'render'; - as: 'lens_xy_chart_renderer'; - value: XYChartProps; -} +export { + legendConfig, + yAxisConfig, + tickLabelsConfig, + gridlinesConfig, + axisTitlesVisibilityConfig, + axisExtentConfig, + layerConfig, + xyChart, +} from '../../common/expressions'; export type XYChartRenderProps = XYChartProps & { chartsThemeService: ChartsPluginSetup['theme']; @@ -99,139 +94,6 @@ export type XYChartRenderProps = XYChartProps & { syncColors: boolean; }; -export const xyChart: ExpressionFunctionDefinition< - 'lens_xy_chart', - LensMultiTable | ExpressionValueSearchContext | null, - XYArgs, - XYRender -> = { - name: 'lens_xy_chart', - type: 'render', - inputTypes: ['lens_multitable', 'kibana_context', 'null'], - help: i18n.translate('xpack.lens.xyChart.help', { - defaultMessage: 'An X/Y chart', - }), - args: { - title: { - types: ['string'], - help: 'The chart title.', - }, - description: { - types: ['string'], - help: '', - }, - xTitle: { - types: ['string'], - help: i18n.translate('xpack.lens.xyChart.xTitle.help', { - defaultMessage: 'X axis title', - }), - }, - yTitle: { - types: ['string'], - help: i18n.translate('xpack.lens.xyChart.yLeftTitle.help', { - defaultMessage: 'Y left axis title', - }), - }, - yRightTitle: { - types: ['string'], - help: i18n.translate('xpack.lens.xyChart.yRightTitle.help', { - defaultMessage: 'Y right axis title', - }), - }, - yLeftExtent: { - types: ['lens_xy_axisExtentConfig'], - help: i18n.translate('xpack.lens.xyChart.yLeftExtent.help', { - defaultMessage: 'Y left axis extents', - }), - }, - yRightExtent: { - types: ['lens_xy_axisExtentConfig'], - help: i18n.translate('xpack.lens.xyChart.yRightExtent.help', { - defaultMessage: 'Y right axis extents', - }), - }, - legend: { - types: ['lens_xy_legendConfig'], - help: i18n.translate('xpack.lens.xyChart.legend.help', { - defaultMessage: 'Configure the chart legend.', - }), - }, - fittingFunction: { - types: ['string'], - options: [...fittingFunctionDefinitions.map(({ id }) => id)], - help: i18n.translate('xpack.lens.xyChart.fittingFunction.help', { - defaultMessage: 'Define how missing values are treated', - }), - }, - valueLabels: { - types: ['string'], - options: ['hide', 'inside'], - help: '', - }, - tickLabelsVisibilitySettings: { - types: ['lens_xy_tickLabelsConfig'], - help: i18n.translate('xpack.lens.xyChart.tickLabelsSettings.help', { - defaultMessage: 'Show x and y axes tick labels', - }), - }, - gridlinesVisibilitySettings: { - types: ['lens_xy_gridlinesConfig'], - help: i18n.translate('xpack.lens.xyChart.gridlinesSettings.help', { - defaultMessage: 'Show x and y axes gridlines', - }), - }, - axisTitlesVisibilitySettings: { - types: ['lens_xy_axisTitlesVisibilityConfig'], - help: i18n.translate('xpack.lens.xyChart.axisTitlesSettings.help', { - defaultMessage: 'Show x and y axes titles', - }), - }, - layers: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - types: ['lens_xy_layer'] as any, - help: 'Layers of visual series', - multi: true, - }, - curveType: { - types: ['string'], - options: ['LINEAR', 'CURVE_MONOTONE_X'], - help: i18n.translate('xpack.lens.xyChart.curveType.help', { - defaultMessage: 'Define how curve type is rendered for a line chart', - }), - }, - fillOpacity: { - types: ['number'], - help: i18n.translate('xpack.lens.xyChart.fillOpacity.help', { - defaultMessage: 'Define the area chart fill opacity', - }), - }, - hideEndzones: { - types: ['boolean'], - default: false, - help: i18n.translate('xpack.lens.xyChart.hideEndzones.help', { - defaultMessage: 'Hide endzone markers for partial data', - }), - }, - valuesInLegend: { - types: ['boolean'], - default: false, - help: i18n.translate('xpack.lens.xyChart.valuesInLegend.help', { - defaultMessage: 'Show values in legend', - }), - }, - }, - fn(data: LensMultiTable, args: XYArgs) { - return { - type: 'render', - as: 'lens_xy_chart_renderer', - value: { - data, - args, - }, - }; - }, -}; - export function calculateMinInterval({ args: { layers }, data }: XYChartProps) { const filteredLayers = getFilteredLayers(layers, data); if (filteredLayers.length === 0) return; diff --git a/x-pack/plugins/lens/public/xy_visualization/fitting_functions.ts b/x-pack/plugins/lens/public/xy_visualization/fitting_functions.ts index a1f8ad1fa259ad0..0b0878dfe968436 100644 --- a/x-pack/plugins/lens/public/xy_visualization/fitting_functions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/fitting_functions.ts @@ -6,57 +6,7 @@ */ import { Fit } from '@elastic/charts'; -import { i18n } from '@kbn/i18n'; - -export type FittingFunction = typeof fittingFunctionDefinitions[number]['id']; - -export const fittingFunctionDefinitions = [ - { - id: 'None', - title: i18n.translate('xpack.lens.fittingFunctionsTitle.none', { - defaultMessage: 'Hide', - }), - description: i18n.translate('xpack.lens.fittingFunctionsDescription.none', { - defaultMessage: 'Do not fill gaps', - }), - }, - { - id: 'Zero', - title: i18n.translate('xpack.lens.fittingFunctionsTitle.zero', { - defaultMessage: 'Zero', - }), - description: i18n.translate('xpack.lens.fittingFunctionsDescription.zero', { - defaultMessage: 'Fill gaps with zeros', - }), - }, - { - id: 'Linear', - title: i18n.translate('xpack.lens.fittingFunctionsTitle.linear', { - defaultMessage: 'Linear', - }), - description: i18n.translate('xpack.lens.fittingFunctionsDescription.linear', { - defaultMessage: 'Fill gaps with a line', - }), - }, - { - id: 'Carry', - title: i18n.translate('xpack.lens.fittingFunctionsTitle.carry', { - defaultMessage: 'Last', - }), - description: i18n.translate('xpack.lens.fittingFunctionsDescription.carry', { - defaultMessage: 'Fill gaps with the last value', - }), - }, - { - id: 'Lookahead', - title: i18n.translate('xpack.lens.fittingFunctionsTitle.lookahead', { - defaultMessage: 'Next', - }), - description: i18n.translate('xpack.lens.fittingFunctionsDescription.lookahead', { - defaultMessage: 'Fill gaps with the next value', - }), - }, -] as const; +import { FittingFunction } from '../../common/expressions'; export function getFitEnum(fittingFunction?: FittingFunction) { if (fittingFunction) { diff --git a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx index e4edfe918a242d7..e3489ae7808af03 100644 --- a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.test.tsx @@ -10,8 +10,8 @@ import { LegendActionProps, SeriesIdentifier } from '@elastic/charts'; import { EuiPopover } from '@elastic/eui'; import { mountWithIntl } from '@kbn/test/jest'; import { ComponentType, ReactWrapper } from 'enzyme'; -import type { LayerArgs } from './types'; -import type { LensMultiTable } from '../types'; +import type { LensMultiTable } from '../../common'; +import type { LayerArgs } from '../../common/expressions'; import { getLegendAction } from './get_legend_action'; import { LegendActionPopover } from '../shared_components'; diff --git a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.tsx b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.tsx index c99bf948d6e3742..0603328ee5bb3aa 100644 --- a/x-pack/plugins/lens/public/xy_visualization/get_legend_action.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/get_legend_action.tsx @@ -7,8 +7,9 @@ import React from 'react'; import type { LegendAction, XYChartSeriesIdentifier } from '@elastic/charts'; -import type { LayerArgs } from './types'; -import type { LensMultiTable, LensFilterEvent, FormatFactory } from '../types'; +import type { LensFilterEvent } from '../types'; +import type { LensMultiTable, FormatFactory } from '../../common'; +import type { LayerArgs } from '../../common/expressions'; import { LegendActionPopover } from '../shared_components'; export const getLegendAction = ( diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts index f29d0f928024658..de9ecd0b694e473 100644 --- a/x-pack/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -5,12 +5,13 @@ * 2.0. */ -import { CoreSetup } from 'kibana/public'; -import { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; -import { EditorFrameSetup, FormatFactory } from '../types'; -import { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; -import { LensPluginStartDependencies } from '../plugin'; +import type { CoreSetup } from 'kibana/public'; +import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; +import type { EditorFrameSetup } from '../types'; +import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; +import type { LensPluginStartDependencies } from '../plugin'; import { getTimeZone } from '../utils'; +import type { FormatFactory } from '../../common'; export interface XyVisualizationPluginSetupPlugins { expressions: ExpressionsSetup; diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index aa8dede62f56679..e3b16f5981f88fb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -6,8 +6,9 @@ */ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; -import { FramePublicAPI, DatasourcePublicAPI } from '../types'; -import { SeriesType, visualizationTypes, XYLayerConfig, YConfig, ValidLayer } from './types'; +import type { FramePublicAPI, DatasourcePublicAPI } from '../types'; +import type { SeriesType, XYLayerConfig, YConfig, ValidLayer } from '../../common/expressions'; +import { visualizationTypes } from './types'; export function isHorizontalSeries(seriesType: SeriesType) { return ( diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index c89a5e81e35d014..b588cd5592a4363 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -8,9 +8,10 @@ import { Ast } from '@kbn/interpreter/common'; import { ScaleType } from '@elastic/charts'; import { PaletteRegistry } from 'src/plugins/charts/public'; -import { State, ValidLayer, XYLayerConfig } from './types'; +import { State } from './types'; import { OperationMetadata, DatasourcePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; +import { ValidLayer, XYLayerConfig } from '../../common/expressions'; export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: XYLayerConfig) => { const originalOrder = datasource diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index 609622186af20a6..dcc147b4170a09f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -5,10 +5,7 @@ * 2.0. */ -import { Position, VerticalAlignment, HorizontalAlignment } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; -import { PaletteOutput } from 'src/plugins/charts/public'; -import { ArgumentType, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { LensIconChartArea } from '../assets/chart_area'; import { LensIconChartAreaStacked } from '../assets/chart_area_stacked'; import { LensIconChartAreaPercentage } from '../assets/chart_area_percentage'; @@ -21,499 +18,16 @@ import { LensIconChartBarHorizontalPercentage } from '../assets/chart_bar_horizo import { LensIconChartLine } from '../assets/chart_line'; import { VisualizationType } from '../types'; -import { FittingFunction } from './fitting_functions'; - -export interface LegendConfig { - /** - * Flag whether the legend should be shown. If there is just a single series, it will be hidden - */ - isVisible: boolean; - /** - * Position of the legend relative to the chart - */ - position: Position; - /** - * Flag whether the legend should be shown even with just a single series - */ - showSingleSeries?: boolean; - /** - * Flag whether the legend is inside the chart - */ - isInside?: boolean; - /** - * Horizontal Alignment of the legend when it is set inside chart - */ - horizontalAlignment?: HorizontalAlignment; - /** - * Vertical Alignment of the legend when it is set inside chart - */ - verticalAlignment?: VerticalAlignment; - /** - * Number of columns when legend is set inside chart - */ - floatingColumns?: number; -} - -type LegendConfigResult = LegendConfig & { type: 'lens_xy_legendConfig' }; - -export const legendConfig: ExpressionFunctionDefinition< - 'lens_xy_legendConfig', - null, +import { + SeriesType, + ValueLabelConfig, LegendConfig, - LegendConfigResult -> = { - name: 'lens_xy_legendConfig', - aliases: [], - type: 'lens_xy_legendConfig', - help: `Configure the xy chart's legend`, - inputTypes: ['null'], - args: { - isVisible: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.isVisible.help', { - defaultMessage: 'Specifies whether or not the legend is visible.', - }), - }, - position: { - types: ['string'], - options: [Position.Top, Position.Right, Position.Bottom, Position.Left], - help: i18n.translate('xpack.lens.xyChart.position.help', { - defaultMessage: 'Specifies the legend position.', - }), - }, - showSingleSeries: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.showSingleSeries.help', { - defaultMessage: 'Specifies whether a legend with just a single entry should be shown', - }), - }, - isInside: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.isInside.help', { - defaultMessage: 'Specifies whether a legend is inside the chart', - }), - }, - horizontalAlignment: { - types: ['string'], - options: [HorizontalAlignment.Right, HorizontalAlignment.Left], - help: i18n.translate('xpack.lens.xyChart.horizontalAlignment.help', { - defaultMessage: - 'Specifies the horizontal alignment of the legend when it is displayed inside chart.', - }), - }, - verticalAlignment: { - types: ['string'], - options: [VerticalAlignment.Top, VerticalAlignment.Bottom], - help: i18n.translate('xpack.lens.xyChart.verticalAlignment.help', { - defaultMessage: - 'Specifies the vertical alignment of the legend when it is displayed inside chart.', - }), - }, - floatingColumns: { - types: ['number'], - help: i18n.translate('xpack.lens.xyChart.floatingColumns.help', { - defaultMessage: 'Specifies the number of columns when legend is displayed inside chart.', - }), - }, - }, - fn: function fn(input: unknown, args: LegendConfig) { - return { - type: 'lens_xy_legendConfig', - ...args, - }; - }, -}; - -export interface AxesSettingsConfig { - x: boolean; - yLeft: boolean; - yRight: boolean; -} - -type TickLabelsConfigResult = AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' }; - -export const tickLabelsConfig: ExpressionFunctionDefinition< - 'lens_xy_tickLabelsConfig', - null, - AxesSettingsConfig, - TickLabelsConfigResult -> = { - name: 'lens_xy_tickLabelsConfig', - aliases: [], - type: 'lens_xy_tickLabelsConfig', - help: `Configure the xy chart's tick labels appearance`, - inputTypes: ['null'], - args: { - x: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.xAxisTickLabels.help', { - defaultMessage: 'Specifies whether or not the tick labels of the x-axis are visible.', - }), - }, - yLeft: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yLeftAxisTickLabels.help', { - defaultMessage: 'Specifies whether or not the tick labels of the left y-axis are visible.', - }), - }, - yRight: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yRightAxisTickLabels.help', { - defaultMessage: 'Specifies whether or not the tick labels of the right y-axis are visible.', - }), - }, - }, - fn: function fn(input: unknown, args: AxesSettingsConfig) { - return { - type: 'lens_xy_tickLabelsConfig', - ...args, - }; - }, -}; - -type GridlinesConfigResult = AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' }; - -export const gridlinesConfig: ExpressionFunctionDefinition< - 'lens_xy_gridlinesConfig', - null, - AxesSettingsConfig, - GridlinesConfigResult -> = { - name: 'lens_xy_gridlinesConfig', - aliases: [], - type: 'lens_xy_gridlinesConfig', - help: `Configure the xy chart's gridlines appearance`, - inputTypes: ['null'], - args: { - x: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.xAxisGridlines.help', { - defaultMessage: 'Specifies whether or not the gridlines of the x-axis are visible.', - }), - }, - yLeft: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yLeftAxisgridlines.help', { - defaultMessage: 'Specifies whether or not the gridlines of the left y-axis are visible.', - }), - }, - yRight: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yRightAxisgridlines.help', { - defaultMessage: 'Specifies whether or not the gridlines of the right y-axis are visible.', - }), - }, - }, - fn: function fn(input: unknown, args: AxesSettingsConfig) { - return { - type: 'lens_xy_gridlinesConfig', - ...args, - }; - }, -}; - -type AxisTitlesVisibilityConfigResult = AxesSettingsConfig & { - type: 'lens_xy_axisTitlesVisibilityConfig'; -}; - -export const axisTitlesVisibilityConfig: ExpressionFunctionDefinition< - 'lens_xy_axisTitlesVisibilityConfig', - null, - AxesSettingsConfig, - AxisTitlesVisibilityConfigResult -> = { - name: 'lens_xy_axisTitlesVisibilityConfig', - aliases: [], - type: 'lens_xy_axisTitlesVisibilityConfig', - help: `Configure the xy chart's axis titles appearance`, - inputTypes: ['null'], - args: { - x: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.xAxisTitle.help', { - defaultMessage: 'Specifies whether or not the title of the x-axis are visible.', - }), - }, - yLeft: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yLeftAxisTitle.help', { - defaultMessage: 'Specifies whether or not the title of the left y-axis are visible.', - }), - }, - yRight: { - types: ['boolean'], - help: i18n.translate('xpack.lens.xyChart.yRightAxisTitle.help', { - defaultMessage: 'Specifies whether or not the title of the right y-axis are visible.', - }), - }, - }, - fn: function fn(input: unknown, args: AxesSettingsConfig) { - return { - type: 'lens_xy_axisTitlesVisibilityConfig', - ...args, - }; - }, -}; - -export interface AxisExtentConfig { - mode: 'full' | 'dataBounds' | 'custom'; - lowerBound?: number; - upperBound?: number; -} - -export const axisExtentConfig: ExpressionFunctionDefinition< - 'lens_xy_axisExtentConfig', - null, AxisExtentConfig, - AxisExtentConfigResult -> = { - name: 'lens_xy_axisExtentConfig', - aliases: [], - type: 'lens_xy_axisExtentConfig', - help: `Configure the xy chart's axis extents`, - inputTypes: ['null'], - args: { - mode: { - types: ['string'], - options: ['full', 'dataBounds', 'custom'], - help: i18n.translate('xpack.lens.xyChart.extentMode.help', { - defaultMessage: 'The extent mode', - }), - }, - lowerBound: { - types: ['number'], - help: i18n.translate('xpack.lens.xyChart.extentMode.help', { - defaultMessage: 'The extent mode', - }), - }, - upperBound: { - types: ['number'], - help: i18n.translate('xpack.lens.xyChart.extentMode.help', { - defaultMessage: 'The extent mode', - }), - }, - }, - fn: function fn(input: unknown, args: AxisExtentConfig) { - return { - type: 'lens_xy_axisExtentConfig', - ...args, - }; - }, -}; - -export type AxisExtentConfigResult = AxisExtentConfig & { type: 'lens_xy_axisExtentConfig' }; - -interface AxisConfig { - title: string; - hide?: boolean; -} - -const axisConfig: { [key in keyof AxisConfig]: ArgumentType } = { - title: { - types: ['string'], - help: i18n.translate('xpack.lens.xyChart.title.help', { - defaultMessage: 'The axis title', - }), - }, - hide: { - types: ['boolean'], - default: false, - help: 'Show / hide axis', - }, -}; - -type YConfigResult = YConfig & { type: 'lens_xy_yConfig' }; - -export const yAxisConfig: ExpressionFunctionDefinition< - 'lens_xy_yConfig', - null, - YConfig, - YConfigResult -> = { - name: 'lens_xy_yConfig', - aliases: [], - type: 'lens_xy_yConfig', - help: `Configure the behavior of a xy chart's y axis metric`, - inputTypes: ['null'], - args: { - forAccessor: { - types: ['string'], - help: 'The accessor this configuration is for', - }, - axisMode: { - types: ['string'], - options: ['auto', 'left', 'right'], - help: 'The axis mode of the metric', - }, - color: { - types: ['string'], - help: 'The color of the series', - }, - }, - fn: function fn(input: unknown, args: YConfig) { - return { - type: 'lens_xy_yConfig', - ...args, - }; - }, -}; - -type LayerConfigResult = LayerArgs & { type: 'lens_xy_layer' }; - -export const layerConfig: ExpressionFunctionDefinition< - 'lens_xy_layer', - null, - LayerArgs, - LayerConfigResult -> = { - name: 'lens_xy_layer', - aliases: [], - type: 'lens_xy_layer', - help: `Configure a layer in the xy chart`, - inputTypes: ['null'], - args: { - ...axisConfig, - layerId: { - types: ['string'], - help: '', - }, - xAccessor: { - types: ['string'], - help: '', - }, - seriesType: { - types: ['string'], - options: [ - 'bar', - 'line', - 'area', - 'bar_stacked', - 'area_stacked', - 'bar_percentage_stacked', - 'area_percentage_stacked', - ], - help: 'The type of chart to display.', - }, - xScaleType: { - options: ['ordinal', 'linear', 'time'], - help: 'The scale type of the x axis', - default: 'ordinal', - }, - isHistogram: { - types: ['boolean'], - default: false, - help: 'Whether to layout the chart as a histogram', - }, - yScaleType: { - options: ['log', 'sqrt', 'linear', 'time'], - help: 'The scale type of the y axes', - default: 'linear', - }, - splitAccessor: { - types: ['string'], - help: 'The column to split by', - multi: false, - }, - accessors: { - types: ['string'], - help: 'The columns to display on the y axis.', - multi: true, - }, - yConfig: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - types: ['lens_xy_yConfig' as any], - help: 'Additional configuration for y axes', - multi: true, - }, - columnToLabel: { - types: ['string'], - help: 'JSON key-value pairs of column ID to label', - }, - palette: { - default: `{theme "palette" default={system_palette name="default"} }`, - help: '', - types: ['palette'], - }, - }, - fn: function fn(input: unknown, args: LayerArgs) { - return { - type: 'lens_xy_layer', - ...args, - }; - }, -}; - -export type SeriesType = - | 'bar' - | 'bar_horizontal' - | 'line' - | 'area' - | 'bar_stacked' - | 'bar_percentage_stacked' - | 'bar_horizontal_stacked' - | 'bar_horizontal_percentage_stacked' - | 'area_stacked' - | 'area_percentage_stacked'; - -export type YAxisMode = 'auto' | 'left' | 'right'; - -export type ValueLabelConfig = 'hide' | 'inside' | 'outside'; - -export interface YConfig { - forAccessor: string; - axisMode?: YAxisMode; - color?: string; -} - -export interface XYLayerConfig { - hide?: boolean; - layerId: string; - xAccessor?: string; - accessors: string[]; - yConfig?: YConfig[]; - seriesType: SeriesType; - splitAccessor?: string; - palette?: PaletteOutput; -} - -export interface ValidLayer extends XYLayerConfig { - xAccessor: NonNullable; -} - -export type LayerArgs = XYLayerConfig & { - columnToLabel?: string; // Actually a JSON key-value pair - yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; - xScaleType: 'time' | 'linear' | 'ordinal'; - isHistogram: boolean; - // palette will always be set on the expression - palette: PaletteOutput; -}; - -// Arguments to XY chart expression, with computed properties -export interface XYArgs { - title?: string; - description?: string; - xTitle: string; - yTitle: string; - yRightTitle: string; - yLeftExtent: AxisExtentConfigResult; - yRightExtent: AxisExtentConfigResult; - legend: LegendConfig & { type: 'lens_xy_legendConfig' }; - valueLabels: ValueLabelConfig; - layers: LayerArgs[]; - fittingFunction?: FittingFunction; - axisTitlesVisibilitySettings?: AxesSettingsConfig & { - type: 'lens_xy_axisTitlesVisibilityConfig'; - }; - tickLabelsVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_tickLabelsConfig' }; - gridlinesVisibilitySettings?: AxesSettingsConfig & { type: 'lens_xy_gridlinesConfig' }; - curveType?: XYCurveType; - fillOpacity?: number; - hideEndzones?: boolean; - valuesInLegend?: boolean; -} - -export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X'; + XYLayerConfig, + XYCurveType, + AxesSettingsConfig, + FittingFunction, +} from '../../common/expressions'; // Persisted parts of the state export interface XYState { diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx index 1df7744524779bf..6080a8c68e57d2d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/line_curve_option.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiSwitch } from '@elastic/eui'; -import { XYCurveType } from '../types'; +import type { XYCurveType } from '../../../common/expressions'; export interface LineCurveOptionProps { /** diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx index fb6ecec4d280134..3dba8757903e942 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/missing_values_option.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonGroup, EuiFormRow, EuiIconTip, EuiSuperSelect, EuiText } from '@elastic/eui'; -import { FittingFunction, fittingFunctionDefinitions } from '../fitting_functions'; -import { ValueLabelConfig } from '../types'; +import { fittingFunctionDefinitions } from '../../../common/expressions'; +import type { FittingFunction, ValueLabelConfig } from '../../../common/expressions'; export interface MissingValuesOptionProps { valueLabels?: ValueLabelConfig; diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx index ec0c11a0b1d8660..b4c8e8f40dde7e2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallowWithIntl as shallow } from '@kbn/test/jest'; import { Position } from '@elastic/charts'; -import { FramePublicAPI } from '../../types'; +import type { FramePublicAPI } from '../../types'; import { createMockDatasource, createMockFramePublicAPI } from '../../mocks'; import { State } from '../types'; import { VisualOptionsPopover } from './visual_options_popover'; diff --git a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx index 843680e3f28ac63..6d0e5c2d55b7047 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visual_options_popover/visual_options_popover.tsx @@ -13,8 +13,8 @@ import { LineCurveOption } from './line_curve_option'; import { FillOpacityOption } from './fill_opacity_option'; import { XYState } from '../types'; import { hasHistogramSeries } from '../state_helpers'; -import { ValidLayer } from '../types'; -import { FramePublicAPI } from '../../types'; +import { ValidLayer } from '../../../common/expressions'; +import type { FramePublicAPI } from '../../types'; function getValueLabelDisableReason({ isAreaPercentage, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 304e323789c1473..fd80b9d96d30afb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -8,7 +8,8 @@ import { getXyVisualization } from './visualization'; import { Position } from '@elastic/charts'; import { Operation } from '../types'; -import { State, SeriesType, XYLayerConfig } from './types'; +import type { State } from './types'; +import type { SeriesType, XYLayerConfig } from '../../common/expressions'; import { createMockDatasource, createMockFramePublicAPI } from '../mocks'; import { LensIconChartBar } from '../assets/chart_bar'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 199dccdf702f70c..40caed7188190a6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -15,14 +15,15 @@ import { PaletteRegistry } from 'src/plugins/charts/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { getSuggestions } from './xy_suggestions'; import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel'; -import { +import type { Visualization, OperationMetadata, VisualizationType, AccessorConfig, DatasourcePublicAPI, } from '../types'; -import { State, SeriesType, visualizationTypes, XYLayerConfig, XYState } from './types'; +import { State, visualizationTypes, XYState } from './types'; +import type { SeriesType, XYLayerConfig } from '../../common/expressions'; import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; diff --git a/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx b/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx index 369063644a75456..ccb047d54e36923 100644 --- a/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/x_domain.tsx @@ -8,8 +8,8 @@ import { uniq } from 'lodash'; import React from 'react'; import { Endzones } from '../../../../../src/plugins/charts/public'; -import { LensMultiTable } from '../types'; -import { LayerArgs } from './types'; +import type { LensMultiTable } from '../../common'; +import type { LayerArgs } from '../../common/expressions'; export interface XDomain { min?: number; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index 3128527334553ef..061d0cbc4b4b515 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -21,23 +21,21 @@ import { EuiToolTip, EuiIcon, } from '@elastic/eui'; -import { PaletteRegistry } from 'src/plugins/charts/public'; -import { +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import type { VisualizationLayerWidgetProps, VisualizationToolbarProps, VisualizationDimensionEditorProps, - FormatFactory, FramePublicAPI, } from '../types'; -import { - State, +import { State, visualizationTypes, XYState } from './types'; +import type { FormatFactory } from '../../common'; +import type { SeriesType, - visualizationTypes, YAxisMode, AxesSettingsConfig, AxisExtentConfig, - XYState, -} from './types'; +} from '../../common/expressions'; import { isHorizontalChart, isHorizontalSeries, getSeriesColor } from './state_helpers'; import { trackUiEvent } from '../lens_ui_telemetry'; import { LegendSettingsPopover } from '../shared_components'; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index a494d51f5168155..893bd5fd04ee491 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -16,7 +16,8 @@ import { TableSuggestion, TableChangeType, } from '../types'; -import { State, SeriesType, XYState, visualizationTypes, XYLayerConfig } from './types'; +import { State, XYState, visualizationTypes } from './types'; +import type { SeriesType, XYLayerConfig } from '../../common/expressions'; import { getIconForSeries } from './state_helpers'; const columnSortOrder = { diff --git a/x-pack/plugins/lens/server/plugin.tsx b/x-pack/plugins/lens/server/plugin.tsx index c23c98cd12aec98..b47019fa54ec09b 100644 --- a/x-pack/plugins/lens/server/plugin.tsx +++ b/x-pack/plugins/lens/server/plugin.tsx @@ -9,6 +9,7 @@ import { Plugin, CoreSetup, CoreStart, PluginInitializerContext, Logger } from ' import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { Observable } from 'rxjs'; import { PluginStart as DataPluginStart } from 'src/plugins/data/server'; +import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { setupRoutes } from './routes'; import { @@ -24,6 +25,7 @@ export interface PluginSetupContract { usageCollection?: UsageCollectionSetup; taskManager?: TaskManagerSetupContract; embeddable: EmbeddableSetup; + expressions: ExpressionsServerSetup; } export interface PluginStartContract { diff --git a/x-pack/plugins/security/server/routes/authentication/saml.test.ts b/x-pack/plugins/security/server/routes/authentication/saml.test.ts index d27ea5f6dca7105..2ed6727492768d7 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.test.ts @@ -43,6 +43,15 @@ describe('SAML authentication routes', () => { routeHandler = acsRouteHandler; }); + it('additionally registers BWC route', () => { + expect( + router.post.mock.calls.find(([{ path }]) => path === '/api/security/saml/callback') + ).toBeDefined(); + expect( + router.post.mock.calls.find(([{ path }]) => path === '/api/security/v1/saml') + ).toBeDefined(); + }); + it('correctly defines route.', () => { expect(routeConfig.options).toEqual({ authRequired: false, diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 8a343c241377940..11baa10c342e153 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -14,40 +14,56 @@ import { ROUTE_TAG_AUTH_FLOW, ROUTE_TAG_CAN_REDIRECT } from '../tags'; /** * Defines routes required for SAML authentication. */ -export function defineSAMLRoutes({ router, getAuthenticationService }: RouteDefinitionParams) { - router.post( - { - path: '/api/security/saml/callback', - validate: { - body: schema.object( - { SAMLResponse: schema.string(), RelayState: schema.maybe(schema.string()) }, - { unknowns: 'ignore' } - ), - }, - options: { - authRequired: false, - xsrfRequired: false, - tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW], - }, - }, - async (context, request, response) => { - // When authenticating using SAML we _expect_ to redirect to the Kibana target location. - const authenticationResult = await getAuthenticationService().login(request, { - provider: { type: SAMLAuthenticationProvider.type }, - value: { - type: SAMLLogin.LoginWithSAMLResponse, - samlResponse: request.body.SAMLResponse, - relayState: request.body.RelayState, +export function defineSAMLRoutes({ + router, + getAuthenticationService, + basePath, + logger, +}: RouteDefinitionParams) { + // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. + for (const path of ['/api/security/saml/callback', '/api/security/v1/saml']) { + router.post( + { + path, + validate: { + body: schema.object( + { SAMLResponse: schema.string(), RelayState: schema.maybe(schema.string()) }, + { unknowns: 'ignore' } + ), }, - }); + options: { + authRequired: false, + xsrfRequired: false, + tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW], + }, + }, + async (context, request, response) => { + if (path === '/api/security/v1/saml') { + const serverBasePath = basePath.serverBasePath; + logger.warn( + // When authenticating using SAML we _expect_ to redirect to the SAML Identity provider. + `The "${serverBasePath}${path}" URL is deprecated and might stop working in a future release. Please use "${serverBasePath}/api/security/saml/callback" URL instead.` + ); + } - if (authenticationResult.redirected()) { - return response.redirected({ - headers: { location: authenticationResult.redirectURL! }, + // When authenticating using SAML we _expect_ to redirect to the Kibana target location. + const authenticationResult = await getAuthenticationService().login(request, { + provider: { type: SAMLAuthenticationProvider.type }, + value: { + type: SAMLLogin.LoginWithSAMLResponse, + samlResponse: request.body.SAMLResponse, + relayState: request.body.RelayState, + }, }); - } - return response.unauthorized({ body: authenticationResult.error }); - } - ); + if (authenticationResult.redirected()) { + return response.redirected({ + headers: { location: authenticationResult.redirectURL! }, + }); + } + + return response.unauthorized({ body: authenticationResult.error }); + } + ); + } } diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 871f1a01e3de0d9..c679828a1c494ab 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -215,7 +215,7 @@ const nestedDeepLinks: SecurityDeepLinks = { title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', { defaultMessage: 'External Alerts', }), - path: `${HOSTS_PATH}/alerts`, + path: `${HOSTS_PATH}/externalAlerts`, }, ], premium: [ diff --git a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts index 148deda9aec761b..05e1c2c4dca816d 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/endpoint/use_navigate_to_app_event_handler.ts @@ -26,7 +26,7 @@ type EventHandlerCallback = MouseEventHandlerSee policies */ export const useNavigateToAppEventHandler = ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts index ca14dde18455b55..e8fa53e2cf92054 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/hooks.ts @@ -14,6 +14,7 @@ import { MANAGEMENT_STORE_GLOBAL_NAMESPACE, } from '../../../../common/constants'; import { useAppUrl } from '../../../../../common/lib/kibana'; +import { pagePathGetters } from '../../../../../../../fleet/public'; export function useEndpointSelector(selector: (state: EndpointState) => TSelected) { return useSelector(function (state: State) { @@ -47,7 +48,8 @@ export const useAgentDetailsIngestUrl = ( ): { url: string; appId: string; appPath: string } => { const { getAppUrl } = useAppUrl(); return useMemo(() => { - const appPath = `#/fleet/agents/${agentId}/activity`; + const appPath = pagePathGetters.agent_details_logs({ agentId })[1]; + return { url: `${getAppUrl({ appId: 'fleet' })}${appPath}`, appId: 'fleet', diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 584e6df1ff78185..03df5d2bcbac718 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -120,13 +120,13 @@ export const useEndpointActionItems = ( 'data-test-subj': 'agentPolicyLink', navigateAppId: 'fleet', navigateOptions: { - path: `#${ + path: `${ pagePathGetters.policy_details({ policyId: fleetAgentPolicies[endpointPolicyId], })[1] }`, }, - href: `${getAppUrl({ appId: 'fleet' })}#${ + href: `${getAppUrl({ appId: 'fleet' })}${ pagePathGetters.policy_details({ policyId: fleetAgentPolicies[endpointPolicyId], })[1] @@ -145,13 +145,13 @@ export const useEndpointActionItems = ( 'data-test-subj': 'agentDetailsLink', navigateAppId: 'fleet', navigateOptions: { - path: `#${ + path: `${ pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] }`, }, - href: `${getAppUrl({ appId: 'fleet' })}#${ + href: `${getAppUrl({ appId: 'fleet' })}${ pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] @@ -169,17 +169,17 @@ export const useEndpointActionItems = ( 'data-test-subj': 'agentPolicyReassignLink', navigateAppId: 'fleet', navigateOptions: { - path: `#${ + path: `${ pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] - }/activity?openReassignFlyout=true`, + }?openReassignFlyout=true`, }, - href: `${getAppUrl({ appId: 'fleet' })}#${ + href: `${getAppUrl({ appId: 'fleet' })}${ pagePathGetters.agent_details({ agentId: fleetAgentId, })[1] - }/activity?openReassignFlyout=true`, + }?openReassignFlyout=true`, children: ( { }); it('navigates to the Ingest Agent Policy page', async () => { const agentPolicyLink = await renderResult.findByTestId('agentPolicyLink'); - expect(agentPolicyLink.getAttribute('href')).toEqual(`/app/fleet#/policies/${agentPolicyId}`); + expect(agentPolicyLink.getAttribute('href')).toEqual(`/app/fleet/policies/${agentPolicyId}`); }); it('navigates to the Ingest Agent Details page', async () => { const agentDetailsLink = await renderResult.findByTestId('agentDetailsLink'); - expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet#/agents/${agentId}`); + expect(agentDetailsLink.getAttribute('href')).toEqual(`/app/fleet/agents/${agentId}`); }); it('navigates to the Ingest Agent Details page with policy reassign', async () => { const agentPolicyReassignLink = await renderResult.findByTestId('agentPolicyReassignLink'); expect(agentPolicyReassignLink.getAttribute('href')).toEqual( - `/app/fleet#/agents/${agentId}/activity?openReassignFlyout=true` + `/app/fleet/agents/${agentId}?openReassignFlyout=true` ); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index c78d4ca6af634b4..74f5b15a72727f0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -156,7 +156,7 @@ export const EndpointList = () => { const handleCreatePolicyClick = useNavigateToAppEventHandler( 'fleet', { - path: `#/integrations/${ + path: `/integrations/${ endpointPackageVersion ? `/endpoint-${endpointPackageVersion}` : '' }/add-integration`, state: { @@ -203,7 +203,7 @@ export const EndpointList = () => { const handleDeployEndpointsClick = useNavigateToAppEventHandler( 'fleet', { - path: `#/policies/${selectedPolicyId}?openEnrollmentFlyout=true`, + path: `/policies/${selectedPolicyId}?openEnrollmentFlyout=true`, state: { onDoneNavigateTo: [ 'securitySolution', diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx index db5c42241a0cc41..29723a5fd3cf80c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx @@ -31,16 +31,10 @@ import { ExceptionBuilder } from '../../../../../../shared_imports'; import { useEventFiltersSelector } from '../../hooks'; import { getFormEntryStateMutable, getHasNameError, getNewComment } from '../../../store/selector'; -import { - FORM_DESCRIPTION, - NAME_LABEL, - NAME_ERROR, - NAME_PLACEHOLDER, - OS_LABEL, - RULE_NAME, -} from './translations'; +import { NAME_LABEL, NAME_ERROR, NAME_PLACEHOLDER, OS_LABEL, RULE_NAME } from './translations'; import { OS_TITLES } from '../../../../../common/translations'; import { ENDPOINT_EVENT_FILTERS_LIST_ID, EVENT_FILTER_LIST_TYPE } from '../../../constants'; +import { ABOUT_EVENT_FILTERS } from '../../translations'; const OPERATING_SYSTEMS: readonly OperatingSystem[] = [ OperatingSystem.MAC, @@ -205,8 +199,12 @@ export const EventFiltersForm: React.FC = memo( return !isIndexPatternLoading && exception ? ( - {FORM_DESCRIPTION} - + {!exception || !exception.item_id ? ( + + {ABOUT_EVENT_FILTERS} + + + ) : null} {nameInputMemo} {allowSelectOs ? ( diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts index 7391251a936e628..bfb828699118e49 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts @@ -7,13 +7,6 @@ import { i18n } from '@kbn/i18n'; -export const FORM_DESCRIPTION = i18n.translate( - 'xpack.securitySolution.eventFilter.modal.description', - { - defaultMessage: "Events are filtered when the rule's conditions are met:", - } -); - export const NAME_PLACEHOLDER = i18n.translate( 'xpack.securitySolution.eventFilter.form.name.placeholder', { diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx index 2d608bdc6e15750..95f3e856a6ff6d4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list_page.tsx @@ -44,6 +44,7 @@ import { EventFilterDeleteModal } from './components/event_filter_delete_modal'; import { SearchBar } from '../../../components/search_bar'; import { BackToExternalAppButton } from '../../../components/back_to_external_app_button'; +import { ABOUT_EVENT_FILTERS } from './translations'; type EventListPaginatedContent = PaginatedContentProps< Immutable, @@ -195,11 +196,7 @@ export const EventFiltersListPage = memo(() => { defaultMessage="Event Filters" /> } - subtitle={i18n.translate('xpack.securitySolution.eventFilters.aboutInfo', { - defaultMessage: - 'Add an event filter to exclude high volume or unwanted events from being written to Elasticsearch. Event ' + - 'filters are processed by the Endpoint Security integration, and are applied to hosts running this integration on their agents.', - })} + subtitle={ABOUT_EVENT_FILTERS} actions={ doesDataExist && ( { values: { error: getError.message }, }); }; + +export const ABOUT_EVENT_FILTERS = i18n.translate('xpack.securitySolution.eventFilters.aboutInfo', { + defaultMessage: + 'Add an event filter to exclude high volume or unwanted events from being written to Elasticsearch. Event ' + + 'filters are processed by the Endpoint Security integration, and are applied to hosts running this integration on their agents.', +}); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx index 7af9f84ad087571..2d21ec9565476c9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.test.tsx @@ -63,7 +63,7 @@ describe('OverviewEmpty', () => { fill: false, label: 'Add Endpoint Security', onClick: undefined, - url: `#/integrations/endpoint-${endpointPackageVersion}/add-integration`, + url: `/integrations/endpoint-${endpointPackageVersion}/add-integration`, }, }); }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx index c75438e18f5d589..6f885b348cdeb31 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_empty/index.tsx @@ -36,7 +36,7 @@ const OverviewEmptyComponent: React.FC = () => { const endpointIntegrationUrlPath = endpointPackageVersion ? `/endpoint-${endpointPackageVersion}/add-integration` : ''; - const endpointIntegrationUrl = `#/integrations${endpointIntegrationUrlPath}`; + const endpointIntegrationUrl = `/integrations${endpointIntegrationUrlPath}`; const handleEndpointClick = useNavigateToAppEventHandler('fleet', { path: endpointIntegrationUrl, }); diff --git a/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx b/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx index e511c76603cda05..839d16b3e3e362f 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx +++ b/x-pack/plugins/security_solution/public/resolver/view/panels/breadcrumbs.tsx @@ -7,10 +7,9 @@ /* eslint-disable react/display-name */ -import { i18n } from '@kbn/i18n'; -import { EuiBreadcrumb, EuiBetaBadge } from '@elastic/eui'; +import { EuiBreadcrumb } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; -import { BetaHeader, ThemedBreadcrumbs } from './styles'; +import { ThemedBreadcrumbs } from './styles'; import { useColors } from '../use_colors'; /** @@ -31,16 +30,6 @@ export const Breadcrumbs = memo(function ({ breadcrumbs }: { breadcrumbs: EuiBre const { resolverBreadcrumbBackground, resolverEdgeText } = useColors(); return ( <> - - - - - - - -`; diff --git a/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx b/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx index c7884683e1aedf0..ca6be33d63ebb5c 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/duration_chart.tsx @@ -94,8 +94,14 @@ export const DurationChartComponent = ({ const monitor = useSelector(monitorStatusSelector); return ( - - {hasLines ? ( + + {hasLines && typeof monitor?.monitor?.type === 'string' ? ( getTickFormat(d)} title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { defaultMessage: 'Duration in {unit}', - values: { unit: monitor?.monitor.type === 'browser' ? SECONDS_LABEL : MS_LABEL }, + values: { unit: monitor.monitor.type === 'browser' ? SECONDS_LABEL : MS_LABEL }, })} labelFormat={(d) => monitor?.monitor.type === 'browser' ? `${microToSec(d)}` : `${microToMilli(d)}` @@ -127,7 +133,7 @@ export const DurationChartComponent = ({ /> diff --git a/x-pack/plugins/uptime/public/components/common/charts/duration_charts.test.tsx b/x-pack/plugins/uptime/public/components/common/charts/duration_charts.test.tsx index d7ae92a0e76544f..7669ee29786f339 100644 --- a/x-pack/plugins/uptime/public/components/common/charts/duration_charts.test.tsx +++ b/x-pack/plugins/uptime/public/components/common/charts/duration_charts.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import DateMath from '@elastic/datemath'; import { DurationChartComponent } from './duration_chart'; import { MonitorDurationResult } from '../../../../common/types'; -import { shallowWithRouter } from '../../../lib'; +import { render } from '../../../lib/helper/rtl_helpers'; describe('MonitorCharts component', () => { let dateMathSpy: any; @@ -48,13 +48,37 @@ describe('MonitorCharts component', () => { }; it('renders the component without errors', () => { - const component = shallowWithRouter( + const { getByLabelText } = render( + />, + { + state: { + monitorStatus: { + loading: false, + status: { + docId: 'docId', + timestamp: '123', + monitor: { + duration: { us: 123 }, + id: 'mon-id', + status: 'up', + type: 'tcp', + }, + }, + }, + }, + } + ); + expect(getByLabelText(`A chart displaying the monitor's ping duration, grouped by location.`)); + }); + + it('renders an empty state when no monitor data is present', () => { + const { getByText } = render( + ); - expect(component).toMatchSnapshot(); + expect(getByText('No duration data available')); }); }); diff --git a/x-pack/test/api_integration/apis/ml/modules/index.ts b/x-pack/test/api_integration/apis/ml/modules/index.ts index 3cf1c7f7878402a..c6a75eccfa4c83c 100644 --- a/x-pack/test/api_integration/apis/ml/modules/index.ts +++ b/x-pack/test/api_integration/apis/ml/modules/index.ts @@ -8,6 +8,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); const ml = getService('ml'); const supertest = getService('supertest'); @@ -15,8 +16,12 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('modules', function () { before(async () => { + // use empty_kibana to make sure the fleet setup is removed correctly after the tests + await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + // Fleet need to be setup to be able to setup packages await supertest.post(`/api/fleet/setup`).set({ 'kbn-xsrf': 'some-xsrf-token' }).expect(200); + for (const fleetPackage of fleetPackages) { await ml.testResources.installFleetPackage(fleetPackage); } @@ -26,6 +31,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { for (const fleetPackage of fleetPackages) { await ml.testResources.removeFleetPackage(fleetPackage); } + await esArchiver.unload('x-pack/test/functional/es_archives/empty_kibana'); }); loadTestFile(require.resolve('./get_module')); diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts index 99f8c6ffedefc14..d5aded01fce7b67 100644 --- a/x-pack/test/functional/apps/dashboard/index.ts +++ b/x-pack/test/functional/apps/dashboard/index.ts @@ -21,5 +21,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dashboard_maps_by_value')); loadTestFile(require.resolve('./migration_smoke_tests/lens_migration_smoke_test')); + loadTestFile(require.resolve('./migration_smoke_tests/tsvb_migration_smoke_test')); }); } diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson new file mode 100644 index 000000000000000..d37184717fd0200 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson @@ -0,0 +1,3 @@ +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"64d009bf-b2cf-44ab-bfda-06cf883f2a24\"},\"panelIndex\":\"64d009bf-b2cf-44ab-bfda-06cf883f2a24\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"TSVB Dashboard\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{}}},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"e7cb1014-a092-44e8-a4d7-12fb6ad2bb7f\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Time Series Drilldown\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}},{\"eventId\":\"b5eebcad-8eb3-4654-bab4-785848686100\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Drilldown 2\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"title\":\"Time Series\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"0a7a9a89-7d4b-4a53-97f2-7d30f6670450\"},\"panelIndex\":\"0a7a9a89-7d4b-4a53-97f2-7d30f6670450\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"metric\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"avg\",\"field\":\"memory\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"background_color_rules\":[{\"id\":\"e35e8210-e3f0-11eb-bc03-1f348ce358cc\"}],\"time_range_mode\":\"entire_time_range\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"dfb6a2b8-04a3-4694-b144-7d6105c9f5ad\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Drilldown\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"title\":\"Average Memory\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"4ed295aa-b56e-4b3c-b749-89b21b3db498\"},\"panelIndex\":\"4ed295aa-b56e-4b3c-b749-89b21b3db498\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"top_n\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"terms\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"terms_field\":\"clientip\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"1d\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"bar_color_rules\":[{\"id\":\"506f5870-e3f1-11eb-bc03-1f348ce358cc\"}]},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Top N\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":15,\"w\":24,\"h\":15,\"i\":\"10354fb6-de13-4331-92e9-5d60e0782ef6\"},\"panelIndex\":\"10354fb6-de13-4331-92e9-5d60e0782ef6\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"gauge\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"gauge_color_rules\":[{\"id\":\"a521c790-e3f1-11eb-bc03-1f348ce358cc\"}],\"gauge_width\":10,\"gauge_inner_width\":10,\"gauge_style\":\"half\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Guage\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":30,\"w\":24,\"h\":15,\"i\":\"c21ec51b-ba7d-4fe3-95ed-63c198852368\"},\"panelIndex\":\"c21ec51b-ba7d-4fe3-95ed-63c198852368\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"markdown\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"markdown\":\"The count was {{ count.last.raw }}\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"TSVB Markdown\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":30,\"w\":24,\"h\":15,\"i\":\"1890c642-9782-43e0-851c-5ffb3775a67d\"},\"panelIndex\":\"1890c642-9782-43e0-851c-5ffb3775a67d\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"table\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"1d\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"bar_color_rules\":[{\"id\":\"d1f993b0-e3f1-11eb-bc03-1f348ce358cc\"}],\"pivot_id\":\"clientip\",\"pivot_type\":\"ip\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"TSVB Table\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":45,\"w\":24,\"h\":15,\"i\":\"da27208b-2425-41b7-b02a-ec456f7ef55e\"},\"panelIndex\":\"da27208b-2425-41b7-b02a-ec456f7ef55e\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"annotations\":[{\"fields\":\"bytes\",\"template\":\"Bytes {{bytes}}\",\"index_pattern\":\"logstash*\",\"query_string\":{\"query\":\"bytes > 18000\",\"language\":\"kuery\"},\"color\":\"#F00\",\"hidden\":false,\"icon\":\"fa-tag\",\"id\":\"f2f4e990-e4c1-11eb-b885-218b39edaf39\",\"ignore_global_filters\":1,\"ignore_panel_filters\":1,\"time_field\":\"@timestamp\"}],\"axis_formatter\":\"number\",\"axis_position\":\"left\",\"axis_scale\":\"normal\",\"background_color_rules\":[{\"id\":\"35d9b2e0-e4c2-11eb-b885-218b39edaf39\"}],\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"index_pattern\":\"\",\"interval\":\"\",\"isModelInvalid\":false,\"series\":[{\"axis_position\":\"right\",\"chart_type\":\"line\",\"color\":\"#68BC00\",\"fill\":0.5,\"formatter\":\"number\",\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"label\":\"\",\"line_width\":1,\"metrics\":[{\"field\":\"bytes\",\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"max\"}],\"point_size\":1,\"separate_axis\":0,\"split_color_mode\":\"kibana\",\"split_mode\":\"everything\",\"stacked\":\"none\",\"type\":\"timeseries\"}],\"show_grid\":1,\"show_legend\":1,\"time_field\":\"\",\"tooltip_mode\":\"show_all\",\"type\":\"timeseries\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"TSVB w Annotations\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"2015-09-21T10:30:00.000Z","timeRestore":true,"timeTo":"2015-09-22T12:00:00.000Z","title":"TSVB Index Pattern Smoke Test","version":1},"coreMigrationVersion":"7.12.1","id":"226f4380-e3f2-11eb-a821-bb6e72afa109","migrationVersion":{"dashboard":"7.11.0"},"references":[{"id":"226f4380-e3f2-11eb-a821-bb6e72afa109","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:e7cb1014-a092-44e8-a4d7-12fb6ad2bb7f:dashboardId","type":"dashboard"},{"id":"226f4380-e3f2-11eb-a821-bb6e72afa109","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:b5eebcad-8eb3-4654-bab4-785848686100:dashboardId","type":"dashboard"},{"id":"226f4380-e3f2-11eb-a821-bb6e72afa109","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:dfb6a2b8-04a3-4694-b144-7d6105c9f5ad:dashboardId","type":"dashboard"}],"type":"dashboard","updated_at":"2021-07-14T16:46:33.549Z","version":"WzY0MiwzXQ=="} +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","timeFieldName":"@timestamp","title":"logstash*"},"coreMigrationVersion":"7.12.1","id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-07-13T15:34:30.529Z","version":"WzMwOSwzXQ=="} +{"exportedCount":2,"missingRefCount":0,"missingReferences":[]} \ No newline at end of file diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson new file mode 100644 index 000000000000000..1a8b1b7f5391c95 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson @@ -0,0 +1,3 @@ +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.13.3\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"90f0f762-f2ae-4400-b650-e699513c6dbf\"},\"panelIndex\":\"90f0f762-f2ae-4400-b650-e699513c6dbf\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"Non Kibana Index\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"time_range_mode\":\"entire_time_range\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"@timestamp\",\"index_pattern\":\"logstash*\",\"use_kibana_indexes\":false,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"isModelInvalid\":false},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"enhancements\":{}}},{\"version\":\"7.13.3\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"e7112256-80ef-4d79-836c-6480e0039179\"},\"panelIndex\":\"e7112256-80ef-4d79-836c-6480e0039179\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"time_range_mode\":\"entire_time_range\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"isModelInvalid\":false,\"index_pattern_ref_name\":\"metrics_e7112256-80ef-4d79-836c-6480e0039179_0_index_pattern\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Kibana Index\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"2015-09-21T10:30:00.000Z","timeRestore":true,"timeTo":"2015-09-22T12:00:00.000Z","title":"TSVB 7.13.3","version":1},"coreMigrationVersion":"7.13.3","id":"d8c09ac0-e4ca-11eb-8015-4bb2e4229ebd","migrationVersion":{"dashboard":"7.13.1"},"references":[{"id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","name":"e7112256-80ef-4d79-836c-6480e0039179:metrics_e7112256-80ef-4d79-836c-6480e0039179_0_index_pattern","type":"index_pattern"}],"type":"dashboard","updated_at":"2021-07-23T18:45:00.781Z","version":"Wzc1NCwxXQ=="} +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","timeFieldName":"@timestamp","title":"logstash*"},"coreMigrationVersion":"7.13.3","id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-07-23T18:43:39.839Z","version":"WzcyMiwxXQ=="} +{"exportedCount":2,"missingRefCount":1,"missingReferences":[{"id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","type":"index_pattern"}]} \ No newline at end of file diff --git a/x-pack/test/functional/apps/dashboard/migration_smoke_tests/tsvb_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/tsvb_migration_smoke_test.ts new file mode 100644 index 000000000000000..22606cf6d28620b --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/migration_smoke_tests/tsvb_migration_smoke_test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import path from 'path'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); + const dashboardPanelActions = getService('dashboardPanelActions'); + + const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects', 'dashboard']); + + describe('Export import saved objects between versions', () => { + describe('From 7.12.1', () => { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.uiSettings.replace({}); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', 'tsvb_dashboard_migration_test_7_12_1.ndjson') + ); + }); + + it('should be able to import dashboard with TSVB panels from 7.12.1', async () => { + // this will catch cases where there is an error in the migrations. + await PageObjects.savedObjects.checkImportSucceeded(); + await PageObjects.savedObjects.clickImportDone(); + }); + + it('should render all panels on the dashboard', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.loadSavedDashboard('TSVB Index Pattern Smoke Test'); + + // dashboard should load properly + await PageObjects.dashboard.expectOnDashboard('TSVB Index Pattern Smoke Test'); + await PageObjects.dashboard.waitForRenderComplete(); + + // There should be 0 error embeddables on the dashboard + const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); + expect(errorEmbeddables.length).to.be(0); + }); + + it('should show the edit action for all panels', async () => { + await PageObjects.dashboard.switchToEditMode(); + + // All panels should be editable. This will catch cases where an error does not create an error embeddable. + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + for (const title of panelTitles) { + await dashboardPanelActions.expectExistsEditPanelAction(title); + } + }); + + it('should retain all panel drilldowns from 7.12.1', async () => { + // Both panels configured with drilldowns in 7.12.1 should still have drilldowns. + const totalPanels = await PageObjects.dashboard.getPanelCount(); + let panelsWithDrilldowns = 0; + let drilldownCount = 0; + for (let panelIndex = 0; panelIndex < totalPanels; panelIndex++) { + const panelDrilldownCount = await PageObjects.dashboard.getPanelDrilldownCount( + panelIndex + ); + if (panelDrilldownCount >= 1) { + panelsWithDrilldowns++; + } + + drilldownCount += await PageObjects.dashboard.getPanelDrilldownCount(panelIndex); + } + expect(panelsWithDrilldowns).to.be(2); + expect(drilldownCount).to.be(3); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + }); + }); + + describe('from 7.13.3', () => { + before(async () => { + await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/logstash_functional'); + await kibanaServer.uiSettings.replace({}); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', 'tsvb_dashboard_migration_test_7_13_3.ndjson') + ); + }); + + it('should be able to import dashboard with TSVB panels from 7.13.3', async () => { + // this will catch cases where there is an error in the migrations. + await PageObjects.savedObjects.checkImportSucceeded(); + await PageObjects.savedObjects.clickImportDone(); + }); + + it('should render all panels on the dashboard', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.loadSavedDashboard('TSVB 7.13.3'); + + // dashboard should load properly + await PageObjects.dashboard.expectOnDashboard('TSVB 7.13.3'); + await PageObjects.dashboard.waitForRenderComplete(); + + // There should be 0 error embeddables on the dashboard + const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); + expect(errorEmbeddables.length).to.be(0); + }); + + it('should show the edit action for all panels', async () => { + await PageObjects.dashboard.switchToEditMode(); + + // All panels should be editable. This will catch cases where an error does not create an error embeddable. + const panelTitles = await PageObjects.dashboard.getPanelTitles(); + for (const title of panelTitles) { + await dashboardPanelActions.expectExistsEditPanelAction(title); + } + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + await esArchiver.load('x-pack/test/functional/es_archives/empty_kibana'); + }); + }); + }); +} diff --git a/x-pack/test/functional/page_objects/synthetics_integration_page.ts b/x-pack/test/functional/page_objects/synthetics_integration_page.ts index 3321234a345e4a8..81ddaf06febd99c 100644 --- a/x-pack/test/functional/page_objects/synthetics_integration_page.ts +++ b/x-pack/test/functional/page_objects/synthetics_integration_page.ts @@ -24,25 +24,17 @@ export function SyntheticsIntegrationPageProvider({ * */ async navigateToPackagePage(packageVersion: string) { - await pageObjects.common.navigateToUrl( + await pageObjects.common.navigateToUrlWithBrowserHistory( 'fleet', - `/integrations/synthetics-${packageVersion}/add-integration`, - { - shouldUseHashForSubUrl: true, - useActualUrl: true, - } + `/integrations/synthetics-${packageVersion}/add-integration` ); await pageObjects.header.waitUntilLoadingHasFinished(); }, async navigateToPackageEditPage(packageId: string, agentId: string) { - await pageObjects.common.navigateToUrl( + await pageObjects.common.navigateToUrlWithBrowserHistory( 'fleet', - `/policies/${agentId}/edit-integration/${packageId}`, - { - shouldUseHashForSubUrl: true, - useActualUrl: true, - } + `/policies/${agentId}/edit-integration/${packageId}` ); await pageObjects.header.waitUntilLoadingHasFinished(); }, diff --git a/x-pack/test/functional/services/ml/anomaly_explorer.ts b/x-pack/test/functional/services/ml/anomaly_explorer.ts index 6ec45204388d7ec..4392de7ee556771 100644 --- a/x-pack/test/functional/services/ml/anomaly_explorer.ts +++ b/x-pack/test/functional/services/ml/anomaly_explorer.ts @@ -10,6 +10,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProviderContext) { + const retry = getService('retry'); const testSubjects = getService('testSubjects'); return { @@ -82,12 +83,8 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid }, async addAndEditSwimlaneInDashboard(dashboardTitle: string) { - await this.filterWithSearchString(dashboardTitle); - await testSubjects.isDisplayed('mlDashboardSelectionTable > checkboxSelectAll'); - await testSubjects.clickWhenNotDisabled('mlDashboardSelectionTable > checkboxSelectAll'); - expect(await testSubjects.isChecked('mlDashboardSelectionTable > checkboxSelectAll')).to.be( - true - ); + await this.filterDashboardSearchWithSearchString(dashboardTitle); + await this.selectAllDashboards(); await testSubjects.clickWhenNotDisabled('mlAddAndEditDashboardButton'); // changing to the dashboard app might take sime time const embeddable = await testSubjects.find('mlAnomalySwimlaneEmbeddableWrapper', 30 * 1000); @@ -106,11 +103,30 @@ export function MachineLearningAnomalyExplorerProvider({ getService }: FtrProvid await testSubjects.existOrFail('~mlDashboardSelectionTable', { timeout: 60 * 1000 }); }, - async filterWithSearchString(filter: string) { + async filterDashboardSearchWithSearchString(filter: string) { await this.waitForDashboardsToLoad(); const searchBarInput = await testSubjects.find('mlDashboardsSearchBox'); await searchBarInput.clearValueWithKeyboard(); await searchBarInput.type(filter); + await this.assertDashboardSearchInputValue(filter); + }, + + async assertDashboardSearchInputValue(expectedSearchValue: string) { + const searchBarInput = await testSubjects.find('mlDashboardsSearchBox'); + const actualSearchValue = await searchBarInput.getAttribute('value'); + expect(actualSearchValue).to.eql( + expectedSearchValue, + `Dashboard search input value should be '${expectedSearchValue}' (got '${actualSearchValue}')` + ); + }, + + async selectAllDashboards() { + await retry.tryForTime(3000, async () => { + await testSubjects.clickWhenNotDisabled('mlDashboardSelectionTable > checkboxSelectAll'); + expect( + await testSubjects.isChecked('mlDashboardSelectionTable > checkboxSelectAll') + ).to.eql(true, 'Checkbox to select all dashboards should be selected'); + }); }, async assertClearSelectionButtonVisible(expectVisible: boolean) { diff --git a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts index b22748608589ed5..ab3692be9288f0f 100644 --- a/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts +++ b/x-pack/test/functional/services/ml/data_frame_analytics_creation.ts @@ -404,42 +404,58 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( }, async assertConfigurationStepActive() { - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardConfigurationStep active'); + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardConfigurationStep active', { + timeout: 3000, + }); }, async assertAdditionalOptionsStepActive() { - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardAdvancedStep active'); + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardAdvancedStep active', { + timeout: 3000, + }); }, async assertDetailsStepActive() { - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardDetailsStep active'); + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardDetailsStep active', { + timeout: 3000, + }); }, async assertCreateStepActive() { - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardCreateStep active'); + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardCreateStep active', { + timeout: 3000, + }); }, async assertValidationStepActive() { - await testSubjects.existOrFail('mlAnalyticsCreateJobWizardValidationStepWrapper active'); + await testSubjects.existOrFail('mlAnalyticsCreateJobWizardValidationStepWrapper active', { + timeout: 3000, + }); }, async continueToAdditionalOptionsStep() { - await retry.tryForTime(5000, async () => { - await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await retry.tryForTime(15 * 1000, async () => { + await testSubjects.clickWhenNotDisabled( + 'mlAnalyticsCreateJobWizardConfigurationStep active > mlAnalyticsCreateJobWizardContinueButton' + ); await this.assertAdditionalOptionsStepActive(); }); }, async continueToDetailsStep() { - await retry.tryForTime(5000, async () => { - await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await retry.tryForTime(15 * 1000, async () => { + await testSubjects.clickWhenNotDisabled( + 'mlAnalyticsCreateJobWizardAdvancedStep active > mlAnalyticsCreateJobWizardContinueButton' + ); await this.assertDetailsStepActive(); }); }, async continueToValidationStep() { - await retry.tryForTime(5000, async () => { - await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await retry.tryForTime(15 * 1000, async () => { + await testSubjects.clickWhenNotDisabled( + 'mlAnalyticsCreateJobWizardDetailsStep active > mlAnalyticsCreateJobWizardContinueButton' + ); await this.assertValidationStepActive(); }); }, @@ -456,8 +472,10 @@ export function MachineLearningDataFrameAnalyticsCreationProvider( }, async continueToCreateStep() { - await retry.tryForTime(5000, async () => { - await testSubjects.clickWhenNotDisabled('mlAnalyticsCreateJobWizardContinueButton'); + await retry.tryForTime(15 * 1000, async () => { + await testSubjects.clickWhenNotDisabled( + 'mlAnalyticsCreateJobWizardValidationStepWrapper active > mlAnalyticsCreateJobWizardContinueButton' + ); await this.assertCreateStepActive(); }); }, diff --git a/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts b/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts index 5abc842ddc9c905..81e868c5a38cbd3 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/fleet_integrations_page.ts @@ -17,9 +17,10 @@ export function FleetIntegrations({ getService, getPageObjects }: FtrProviderCon return { async navigateToIntegrationDetails(pkgkey: string) { - await pageObjects.common.navigateToApp(INTEGRATIONS_PLUGIN_ID, { - hash: pagePathGetters.integration_details_overview({ pkgkey })[1], - }); + await pageObjects.common.navigateToUrlWithBrowserHistory( + INTEGRATIONS_PLUGIN_ID, + pagePathGetters.integration_details_overview({ pkgkey })[1] + ); }, async integrationDetailCustomTabExistsOrFail() { diff --git a/yarn.lock b/yarn.lock index 4d7370d1f527bc1..04261da41ea0507 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1617,11 +1617,6 @@ history "^4.9.0" qs "^6.7.0" -"@elastic/ui-ace@0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@elastic/ui-ace/-/ui-ace-0.2.3.tgz#5281aed47a79b7216c55542b0675e435692f20cd" - integrity sha512-Nti5s2dplBPhSKRwJxG9JXTMOev4jVOWcnTJD1TOkJr1MUBYKVZcNcJtIVMSvahWGmP0B/UfO9q9lyRqdivkvQ== - "@emotion/babel-plugin-jsx-pragmatic@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin-jsx-pragmatic/-/babel-plugin-jsx-pragmatic-0.1.5.tgz#27debfe9c27c4d83574d509787ae553bf8a34d7e" @@ -6951,11 +6946,6 @@ angular-sortable-view@^0.0.17: resolved "https://registry.yarnpkg.com/angular-sortable-view/-/angular-sortable-view-0.0.17.tgz#99e2679951a86b6ee6ff27b099022943c683fb4f" integrity sha512-2WkhM0Lt/wyMyrX/+7ve9ejSegBd7A4eRBNHEIJz8XMBIOjt+3oM1WpcAm+qNThkmNmmQaDeaYv0TQZw/WDMBw== -angular-ui-ace@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/angular-ui-ace/-/angular-ui-ace-0.2.3.tgz#3cb903428100621a367fc7f641440e97a42a26d0" - integrity sha1-PLkDQoEAYho2f8f2QUQOl6QqJtA= - angular@>=1.0.6, angular@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/angular/-/angular-1.8.0.tgz#b1ec179887869215cab6dfd0df2e42caa65b1b51"