diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index e6777e5e69f29a3..d4bf323f938a708 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -183,6 +183,7 @@ x-pack/examples/files_example @elastic/kibana-app-services
/src/plugins/controls/ @elastic/kibana-presentation
/test/functional/apps/dashboard/ @elastic/kibana-presentation
/test/functional/apps/dashboard_elements/ @elastic/kibana-presentation
+/test/functional/services/dashboard/ @elastic/kibana-presentation
/x-pack/plugins/canvas/ @elastic/kibana-presentation
/x-pack/plugins/dashboard_enhanced/ @elastic/kibana-presentation
/x-pack/test/functional/apps/canvas/ @elastic/kibana-presentation
diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc
index 7a1eaae67690664..e98561ce501082a 100644
--- a/docs/management/advanced-options.asciidoc
+++ b/docs/management/advanced-options.asciidoc
@@ -89,7 +89,7 @@ Set this property to `false` to prevent the filter editor and KQL autocomplete
from suggesting values for fields.
[[autocomplete-valuesuggestionmethod]]`autocomplete:valueSuggestionMethod`::
-When set to `terms_enum`, autocomplete uses the terms enum API for value suggestions. Kibana returns results faster, but suggestions are approximate, sorted alphabetically, and can be outside the selected time range.
+When set to `terms_enum`, autocomplete uses the terms enum API for value suggestions. Kibana returns results faster, but suggestions are approximate, sorted alphabetically, and can be outside the selected time range. (Note that this API is incompatible with {ref}/document-level-security.html[Document-Level-Security].)
When set to `terms_agg`, Kibana uses a terms aggregation for value suggestions, which is
slower, but suggestions include all values that optionally match your time range and are sorted by popularity.
diff --git a/docs/maps/asset-tracking-tutorial.asciidoc b/docs/maps/asset-tracking-tutorial.asciidoc
index 85629e0e611f6c2..4e6efff35b3a35f 100644
--- a/docs/maps/asset-tracking-tutorial.asciidoc
+++ b/docs/maps/asset-tracking-tutorial.asciidoc
@@ -318,9 +318,9 @@ cloud.auth:
. In {kib}, open the main menu, and click *Stack Management > Data Views*.
. Click *Create data view*.
. Give the data view a name: *tri_met_tracks**.
-. Click *Next step*.
-. Set the *Time field* to *trimet.time*.
-. Click *Create data view*.
+. Set the index pattern as: *tri_met_tracks**.
+. Set the *Timestamp field* to *trimet.time*.
+. Click *Save data view to Kibana*.
{kib} shows the fields in your data view.
diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel
index b084fa8617929e3..812d73e82826f37 100644
--- a/packages/BUILD.bazel
+++ b/packages/BUILD.bazel
@@ -14,110 +14,110 @@ filegroup(
"//packages/analytics/shippers/elastic_v3/common:build",
"//packages/analytics/shippers/elastic_v3/server:build",
"//packages/analytics/shippers/fullstory:build",
+ "//packages/core/analytics/core-analytics-browser:build",
"//packages/core/analytics/core-analytics-browser-internal:build",
"//packages/core/analytics/core-analytics-browser-mocks:build",
- "//packages/core/analytics/core-analytics-browser:build",
+ "//packages/core/analytics/core-analytics-server:build",
"//packages/core/analytics/core-analytics-server-internal:build",
"//packages/core/analytics/core-analytics-server-mocks:build",
- "//packages/core/analytics/core-analytics-server:build",
"//packages/core/base/core-base-browser-internal:build",
"//packages/core/base/core-base-browser-mocks:build",
- "//packages/core/base/core-base-common-internal:build",
"//packages/core/base/core-base-common:build",
+ "//packages/core/base/core-base-common-internal:build",
"//packages/core/base/core-base-server-internal:build",
"//packages/core/base/core-base-server-mocks:build",
"//packages/core/capabilities/core-capabilities-common:build",
+ "//packages/core/capabilities/core-capabilities-server:build",
"//packages/core/capabilities/core-capabilities-server-internal:build",
"//packages/core/capabilities/core-capabilities-server-mocks:build",
- "//packages/core/capabilities/core-capabilities-server:build",
"//packages/core/config/core-config-server-internal:build",
+ "//packages/core/deprecations/core-deprecations-browser:build",
"//packages/core/deprecations/core-deprecations-browser-internal:build",
"//packages/core/deprecations/core-deprecations-browser-mocks:build",
- "//packages/core/deprecations/core-deprecations-browser:build",
"//packages/core/deprecations/core-deprecations-common:build",
+ "//packages/core/doc-links/core-doc-links-browser:build",
"//packages/core/doc-links/core-doc-links-browser-internal:build",
"//packages/core/doc-links/core-doc-links-browser-mocks:build",
- "//packages/core/doc-links/core-doc-links-browser:build",
+ "//packages/core/doc-links/core-doc-links-server:build",
"//packages/core/doc-links/core-doc-links-server-internal:build",
"//packages/core/doc-links/core-doc-links-server-mocks:build",
- "//packages/core/doc-links/core-doc-links-server:build",
"//packages/core/elasticsearch/core-elasticsearch-client-server-internal:build",
"//packages/core/elasticsearch/core-elasticsearch-client-server-mocks:build",
+ "//packages/core/elasticsearch/core-elasticsearch-server:build",
"//packages/core/elasticsearch/core-elasticsearch-server-internal:build",
"//packages/core/elasticsearch/core-elasticsearch-server-mocks:build",
- "//packages/core/elasticsearch/core-elasticsearch-server:build",
"//packages/core/environment/core-environment-server-internal:build",
"//packages/core/environment/core-environment-server-mocks:build",
+ "//packages/core/execution-context/core-execution-context-browser:build",
"//packages/core/execution-context/core-execution-context-browser-internal:build",
"//packages/core/execution-context/core-execution-context-browser-mocks:build",
- "//packages/core/execution-context/core-execution-context-browser:build",
"//packages/core/execution-context/core-execution-context-common:build",
+ "//packages/core/execution-context/core-execution-context-server:build",
"//packages/core/execution-context/core-execution-context-server-internal:build",
"//packages/core/execution-context/core-execution-context-server-mocks:build",
- "//packages/core/execution-context/core-execution-context-server:build",
+ "//packages/core/fatal-errors/core-fatal-errors-browser:build",
"//packages/core/fatal-errors/core-fatal-errors-browser-internal:build",
"//packages/core/fatal-errors/core-fatal-errors-browser-mocks:build",
- "//packages/core/fatal-errors/core-fatal-errors-browser:build",
+ "//packages/core/http/core-http-browser:build",
"//packages/core/http/core-http-browser-internal:build",
"//packages/core/http/core-http-browser-mocks:build",
- "//packages/core/http/core-http-browser:build",
"//packages/core/http/core-http-common:build",
"//packages/core/http/core-http-context-server-internal:build",
"//packages/core/http/core-http-context-server-mocks:build",
"//packages/core/http/core-http-router-server-internal:build",
"//packages/core/http/core-http-router-server-mocks:build",
+ "//packages/core/http/core-http-server:build",
"//packages/core/http/core-http-server-internal:build",
"//packages/core/http/core-http-server-mocks:build",
- "//packages/core/http/core-http-server:build",
+ "//packages/core/i18n/core-i18n-browser:build",
"//packages/core/i18n/core-i18n-browser-internal:build",
"//packages/core/i18n/core-i18n-browser-mocks:build",
- "//packages/core/i18n/core-i18n-browser:build",
+ "//packages/core/injected-metadata/core-injected-metadata-browser:build",
"//packages/core/injected-metadata/core-injected-metadata-browser-internal:build",
"//packages/core/injected-metadata/core-injected-metadata-browser-mocks:build",
- "//packages/core/injected-metadata/core-injected-metadata-browser:build",
"//packages/core/injected-metadata/core-injected-metadata-common-internal:build",
"//packages/core/integrations/core-integrations-browser-internal:build",
"//packages/core/integrations/core-integrations-browser-mocks:build",
+ "//packages/core/logging/core-logging-server:build",
"//packages/core/logging/core-logging-server-internal:build",
"//packages/core/logging/core-logging-server-mocks:build",
- "//packages/core/logging/core-logging-server:build",
"//packages/core/metrics/core-metrics-collectors-server-internal:build",
"//packages/core/metrics/core-metrics-collectors-server-mocks:build",
+ "//packages/core/metrics/core-metrics-server:build",
"//packages/core/metrics/core-metrics-server-internal:build",
"//packages/core/metrics/core-metrics-server-mocks:build",
- "//packages/core/metrics/core-metrics-server:build",
- "//packages/core/mount-utils/core-mount-utils-browser-internal:build",
"//packages/core/mount-utils/core-mount-utils-browser:build",
+ "//packages/core/mount-utils/core-mount-utils-browser-internal:build",
+ "//packages/core/node/core-node-server:build",
"//packages/core/node/core-node-server-internal:build",
"//packages/core/node/core-node-server-mocks:build",
- "//packages/core/node/core-node-server:build",
+ "//packages/core/notifications/core-notifications-browser:build",
"//packages/core/notifications/core-notifications-browser-internal:build",
"//packages/core/notifications/core-notifications-browser-mocks:build",
- "//packages/core/notifications/core-notifications-browser:build",
+ "//packages/core/overlays/core-overlays-browser:build",
"//packages/core/overlays/core-overlays-browser-internal:build",
"//packages/core/overlays/core-overlays-browser-mocks:build",
- "//packages/core/overlays/core-overlays-browser:build",
+ "//packages/core/preboot/core-preboot-server:build",
"//packages/core/preboot/core-preboot-server-internal:build",
"//packages/core/preboot/core-preboot-server-mocks:build",
- "//packages/core/preboot/core-preboot-server:build",
"//packages/core/saved-objects/core-saved-objects-api-browser:build",
"//packages/core/saved-objects/core-saved-objects-api-server:build",
"//packages/core/saved-objects/core-saved-objects-base-server-internal:build",
"//packages/core/saved-objects/core-saved-objects-base-server-mocks:build",
+ "//packages/core/saved-objects/core-saved-objects-browser:build",
"//packages/core/saved-objects/core-saved-objects-browser-internal:build",
"//packages/core/saved-objects/core-saved-objects-browser-mocks:build",
- "//packages/core/saved-objects/core-saved-objects-browser:build",
"//packages/core/saved-objects/core-saved-objects-common:build",
"//packages/core/saved-objects/core-saved-objects-server:build",
"//packages/core/saved-objects/core-saved-objects-utils-server:build",
"//packages/core/test-helpers/core-test-helpers-deprecations-getters:build",
"//packages/core/test-helpers/core-test-helpers-http-setup-browser:build",
+ "//packages/core/theme/core-theme-browser:build",
"//packages/core/theme/core-theme-browser-internal:build",
"//packages/core/theme/core-theme-browser-mocks:build",
- "//packages/core/theme/core-theme-browser:build",
+ "//packages/core/ui-settings/core-ui-settings-browser:build",
"//packages/core/ui-settings/core-ui-settings-browser-internal:build",
"//packages/core/ui-settings/core-ui-settings-browser-mocks:build",
- "//packages/core/ui-settings/core-ui-settings-browser:build",
"//packages/core/ui-settings/core-ui-settings-common:build",
"//packages/home/sample_data_card:build",
"//packages/home/sample_data_tab:build",
@@ -141,11 +141,11 @@ filegroup(
"//packages/kbn-ci-stats-reporter:build",
"//packages/kbn-cli-dev-mode:build",
"//packages/kbn-coloring:build",
+ "//packages/kbn-config:build",
"//packages/kbn-config-mocks:build",
"//packages/kbn-config-schema:build",
- "//packages/kbn-config:build",
- "//packages/kbn-crypto-browser:build",
"//packages/kbn-crypto:build",
+ "//packages/kbn-crypto-browser:build",
"//packages/kbn-datemath:build",
"//packages/kbn-dev-cli-errors:build",
"//packages/kbn-dev-cli-runner:build",
@@ -154,10 +154,10 @@ filegroup(
"//packages/kbn-doc-links:build",
"//packages/kbn-docs-utils:build",
"//packages/kbn-ebt-tools:build",
+ "//packages/kbn-es:build",
"//packages/kbn-es-archiver:build",
"//packages/kbn-es-errors:build",
"//packages/kbn-es-query:build",
- "//packages/kbn-es:build",
"//packages/kbn-eslint-config:build",
"//packages/kbn-eslint-plugin-disable:build",
"//packages/kbn-eslint-plugin-eslint:build",
@@ -170,8 +170,8 @@ filegroup(
"//packages/kbn-get-repo-files:build",
"//packages/kbn-handlebars:build",
"//packages/kbn-hapi-mocks:build",
- "//packages/kbn-i18n-react:build",
"//packages/kbn-i18n:build",
+ "//packages/kbn-i18n-react:build",
"//packages/kbn-import-resolver:build",
"//packages/kbn-interpreter:build",
"//packages/kbn-io-ts-utils:build",
@@ -179,21 +179,21 @@ filegroup(
"//packages/kbn-jsonc:build",
"//packages/kbn-kibana-manifest-parser:build",
"//packages/kbn-kibana-manifest-schema:build",
- "//packages/kbn-logging-mocks:build",
"//packages/kbn-logging:build",
- "//packages/kbn-managed-vscode-config-cli:build",
+ "//packages/kbn-logging-mocks:build",
"//packages/kbn-managed-vscode-config:build",
+ "//packages/kbn-managed-vscode-config-cli:build",
"//packages/kbn-mapbox-gl:build",
"//packages/kbn-monaco:build",
- "//packages/kbn-optimizer-webpack-helpers:build",
"//packages/kbn-optimizer:build",
+ "//packages/kbn-optimizer-webpack-helpers:build",
"//packages/kbn-performance-testing-dataset-extractor:build",
"//packages/kbn-plugin-discovery:build",
"//packages/kbn-plugin-generator:build",
"//packages/kbn-plugin-helpers:build",
"//packages/kbn-react-field:build",
- "//packages/kbn-repo-source-classifier-cli:build",
"//packages/kbn-repo-source-classifier:build",
+ "//packages/kbn-repo-source-classifier-cli:build",
"//packages/kbn-rule-data-utils:build",
"//packages/kbn-safer-lodash-set:build",
"//packages/kbn-securitysolution-autocomplete:build",
@@ -222,23 +222,23 @@ filegroup(
"//packages/kbn-storybook:build",
"//packages/kbn-synthetic-package-map:build",
"//packages/kbn-telemetry-tools:build",
+ "//packages/kbn-test:build",
"//packages/kbn-test-jest-helpers:build",
"//packages/kbn-test-subj-selector:build",
- "//packages/kbn-test:build",
"//packages/kbn-timelion-grammar:build",
"//packages/kbn-tinymath:build",
"//packages/kbn-tooling-log:build",
+ "//packages/kbn-type-summarizer:build",
"//packages/kbn-type-summarizer-cli:build",
"//packages/kbn-type-summarizer-core:build",
- "//packages/kbn-type-summarizer:build",
"//packages/kbn-typed-react-router-config:build",
"//packages/kbn-ui-framework:build",
"//packages/kbn-ui-shared-deps-npm:build",
"//packages/kbn-ui-shared-deps-src:build",
"//packages/kbn-ui-theme:build",
"//packages/kbn-user-profile-components:build",
- "//packages/kbn-utility-types-jest:build",
"//packages/kbn-utility-types:build",
+ "//packages/kbn-utility-types-jest:build",
"//packages/kbn-utils:build",
"//packages/kbn-yarn-lock-validator:build",
"//packages/shared-ux/avatar/solution:build",
@@ -290,110 +290,110 @@ filegroup(
"//packages/analytics/shippers/elastic_v3/common:build_types",
"//packages/analytics/shippers/elastic_v3/server:build_types",
"//packages/analytics/shippers/fullstory:build_types",
+ "//packages/core/analytics/core-analytics-browser:build_types",
"//packages/core/analytics/core-analytics-browser-internal:build_types",
"//packages/core/analytics/core-analytics-browser-mocks:build_types",
- "//packages/core/analytics/core-analytics-browser:build_types",
+ "//packages/core/analytics/core-analytics-server:build_types",
"//packages/core/analytics/core-analytics-server-internal:build_types",
"//packages/core/analytics/core-analytics-server-mocks:build_types",
- "//packages/core/analytics/core-analytics-server:build_types",
"//packages/core/base/core-base-browser-internal:build_types",
"//packages/core/base/core-base-browser-mocks:build_types",
- "//packages/core/base/core-base-common-internal:build_types",
"//packages/core/base/core-base-common:build_types",
+ "//packages/core/base/core-base-common-internal:build_types",
"//packages/core/base/core-base-server-internal:build_types",
"//packages/core/base/core-base-server-mocks:build_types",
"//packages/core/capabilities/core-capabilities-common:build_types",
+ "//packages/core/capabilities/core-capabilities-server:build_types",
"//packages/core/capabilities/core-capabilities-server-internal:build_types",
"//packages/core/capabilities/core-capabilities-server-mocks:build_types",
- "//packages/core/capabilities/core-capabilities-server:build_types",
"//packages/core/config/core-config-server-internal:build_types",
+ "//packages/core/deprecations/core-deprecations-browser:build_types",
"//packages/core/deprecations/core-deprecations-browser-internal:build_types",
"//packages/core/deprecations/core-deprecations-browser-mocks:build_types",
- "//packages/core/deprecations/core-deprecations-browser:build_types",
"//packages/core/deprecations/core-deprecations-common:build_types",
+ "//packages/core/doc-links/core-doc-links-browser:build_types",
"//packages/core/doc-links/core-doc-links-browser-internal:build_types",
"//packages/core/doc-links/core-doc-links-browser-mocks:build_types",
- "//packages/core/doc-links/core-doc-links-browser:build_types",
+ "//packages/core/doc-links/core-doc-links-server:build_types",
"//packages/core/doc-links/core-doc-links-server-internal:build_types",
"//packages/core/doc-links/core-doc-links-server-mocks:build_types",
- "//packages/core/doc-links/core-doc-links-server:build_types",
"//packages/core/elasticsearch/core-elasticsearch-client-server-internal:build_types",
"//packages/core/elasticsearch/core-elasticsearch-client-server-mocks:build_types",
+ "//packages/core/elasticsearch/core-elasticsearch-server:build_types",
"//packages/core/elasticsearch/core-elasticsearch-server-internal:build_types",
"//packages/core/elasticsearch/core-elasticsearch-server-mocks:build_types",
- "//packages/core/elasticsearch/core-elasticsearch-server:build_types",
"//packages/core/environment/core-environment-server-internal:build_types",
"//packages/core/environment/core-environment-server-mocks:build_types",
+ "//packages/core/execution-context/core-execution-context-browser:build_types",
"//packages/core/execution-context/core-execution-context-browser-internal:build_types",
"//packages/core/execution-context/core-execution-context-browser-mocks:build_types",
- "//packages/core/execution-context/core-execution-context-browser:build_types",
"//packages/core/execution-context/core-execution-context-common:build_types",
+ "//packages/core/execution-context/core-execution-context-server:build_types",
"//packages/core/execution-context/core-execution-context-server-internal:build_types",
"//packages/core/execution-context/core-execution-context-server-mocks:build_types",
- "//packages/core/execution-context/core-execution-context-server:build_types",
+ "//packages/core/fatal-errors/core-fatal-errors-browser:build_types",
"//packages/core/fatal-errors/core-fatal-errors-browser-internal:build_types",
"//packages/core/fatal-errors/core-fatal-errors-browser-mocks:build_types",
- "//packages/core/fatal-errors/core-fatal-errors-browser:build_types",
+ "//packages/core/http/core-http-browser:build_types",
"//packages/core/http/core-http-browser-internal:build_types",
"//packages/core/http/core-http-browser-mocks:build_types",
- "//packages/core/http/core-http-browser:build_types",
"//packages/core/http/core-http-common:build_types",
"//packages/core/http/core-http-context-server-internal:build_types",
"//packages/core/http/core-http-context-server-mocks:build_types",
"//packages/core/http/core-http-router-server-internal:build_types",
"//packages/core/http/core-http-router-server-mocks:build_types",
+ "//packages/core/http/core-http-server:build_types",
"//packages/core/http/core-http-server-internal:build_types",
"//packages/core/http/core-http-server-mocks:build_types",
- "//packages/core/http/core-http-server:build_types",
+ "//packages/core/i18n/core-i18n-browser:build_types",
"//packages/core/i18n/core-i18n-browser-internal:build_types",
"//packages/core/i18n/core-i18n-browser-mocks:build_types",
- "//packages/core/i18n/core-i18n-browser:build_types",
+ "//packages/core/injected-metadata/core-injected-metadata-browser:build_types",
"//packages/core/injected-metadata/core-injected-metadata-browser-internal:build_types",
"//packages/core/injected-metadata/core-injected-metadata-browser-mocks:build_types",
- "//packages/core/injected-metadata/core-injected-metadata-browser:build_types",
"//packages/core/injected-metadata/core-injected-metadata-common-internal:build_types",
"//packages/core/integrations/core-integrations-browser-internal:build_types",
"//packages/core/integrations/core-integrations-browser-mocks:build_types",
+ "//packages/core/logging/core-logging-server:build_types",
"//packages/core/logging/core-logging-server-internal:build_types",
"//packages/core/logging/core-logging-server-mocks:build_types",
- "//packages/core/logging/core-logging-server:build_types",
"//packages/core/metrics/core-metrics-collectors-server-internal:build_types",
"//packages/core/metrics/core-metrics-collectors-server-mocks:build_types",
+ "//packages/core/metrics/core-metrics-server:build_types",
"//packages/core/metrics/core-metrics-server-internal:build_types",
"//packages/core/metrics/core-metrics-server-mocks:build_types",
- "//packages/core/metrics/core-metrics-server:build_types",
- "//packages/core/mount-utils/core-mount-utils-browser-internal:build_types",
"//packages/core/mount-utils/core-mount-utils-browser:build_types",
+ "//packages/core/mount-utils/core-mount-utils-browser-internal:build_types",
+ "//packages/core/node/core-node-server:build_types",
"//packages/core/node/core-node-server-internal:build_types",
"//packages/core/node/core-node-server-mocks:build_types",
- "//packages/core/node/core-node-server:build_types",
+ "//packages/core/notifications/core-notifications-browser:build_types",
"//packages/core/notifications/core-notifications-browser-internal:build_types",
"//packages/core/notifications/core-notifications-browser-mocks:build_types",
- "//packages/core/notifications/core-notifications-browser:build_types",
+ "//packages/core/overlays/core-overlays-browser:build_types",
"//packages/core/overlays/core-overlays-browser-internal:build_types",
"//packages/core/overlays/core-overlays-browser-mocks:build_types",
- "//packages/core/overlays/core-overlays-browser:build_types",
+ "//packages/core/preboot/core-preboot-server:build_types",
"//packages/core/preboot/core-preboot-server-internal:build_types",
"//packages/core/preboot/core-preboot-server-mocks:build_types",
- "//packages/core/preboot/core-preboot-server:build_types",
"//packages/core/saved-objects/core-saved-objects-api-browser:build_types",
"//packages/core/saved-objects/core-saved-objects-api-server:build_types",
"//packages/core/saved-objects/core-saved-objects-base-server-internal:build_types",
"//packages/core/saved-objects/core-saved-objects-base-server-mocks:build_types",
+ "//packages/core/saved-objects/core-saved-objects-browser:build_types",
"//packages/core/saved-objects/core-saved-objects-browser-internal:build_types",
"//packages/core/saved-objects/core-saved-objects-browser-mocks:build_types",
- "//packages/core/saved-objects/core-saved-objects-browser:build_types",
"//packages/core/saved-objects/core-saved-objects-common:build_types",
"//packages/core/saved-objects/core-saved-objects-server:build_types",
"//packages/core/saved-objects/core-saved-objects-utils-server:build_types",
"//packages/core/test-helpers/core-test-helpers-deprecations-getters:build_types",
"//packages/core/test-helpers/core-test-helpers-http-setup-browser:build_types",
+ "//packages/core/theme/core-theme-browser:build_types",
"//packages/core/theme/core-theme-browser-internal:build_types",
"//packages/core/theme/core-theme-browser-mocks:build_types",
- "//packages/core/theme/core-theme-browser:build_types",
+ "//packages/core/ui-settings/core-ui-settings-browser:build_types",
"//packages/core/ui-settings/core-ui-settings-browser-internal:build_types",
"//packages/core/ui-settings/core-ui-settings-browser-mocks:build_types",
- "//packages/core/ui-settings/core-ui-settings-browser:build_types",
"//packages/core/ui-settings/core-ui-settings-common:build_types",
"//packages/home/sample_data_card:build_types",
"//packages/home/sample_data_tab:build_types",
@@ -412,11 +412,11 @@ filegroup(
"//packages/kbn-ci-stats-reporter:build_types",
"//packages/kbn-cli-dev-mode:build_types",
"//packages/kbn-coloring:build_types",
+ "//packages/kbn-config:build_types",
"//packages/kbn-config-mocks:build_types",
"//packages/kbn-config-schema:build_types",
- "//packages/kbn-config:build_types",
- "//packages/kbn-crypto-browser:build_types",
"//packages/kbn-crypto:build_types",
+ "//packages/kbn-crypto-browser:build_types",
"//packages/kbn-datemath:build_types",
"//packages/kbn-dev-cli-errors:build_types",
"//packages/kbn-dev-cli-runner:build_types",
@@ -436,8 +436,8 @@ filegroup(
"//packages/kbn-get-repo-files:build_types",
"//packages/kbn-handlebars:build_types",
"//packages/kbn-hapi-mocks:build_types",
- "//packages/kbn-i18n-react:build_types",
"//packages/kbn-i18n:build_types",
+ "//packages/kbn-i18n-react:build_types",
"//packages/kbn-import-resolver:build_types",
"//packages/kbn-interpreter:build_types",
"//packages/kbn-io-ts-utils:build_types",
@@ -445,21 +445,21 @@ filegroup(
"//packages/kbn-jsonc:build_types",
"//packages/kbn-kibana-manifest-parser:build_types",
"//packages/kbn-kibana-manifest-schema:build_types",
- "//packages/kbn-logging-mocks:build_types",
"//packages/kbn-logging:build_types",
- "//packages/kbn-managed-vscode-config-cli:build_types",
+ "//packages/kbn-logging-mocks:build_types",
"//packages/kbn-managed-vscode-config:build_types",
+ "//packages/kbn-managed-vscode-config-cli:build_types",
"//packages/kbn-mapbox-gl:build_types",
"//packages/kbn-monaco:build_types",
- "//packages/kbn-optimizer-webpack-helpers:build_types",
"//packages/kbn-optimizer:build_types",
+ "//packages/kbn-optimizer-webpack-helpers:build_types",
"//packages/kbn-performance-testing-dataset-extractor:build_types",
"//packages/kbn-plugin-discovery:build_types",
"//packages/kbn-plugin-generator:build_types",
"//packages/kbn-plugin-helpers:build_types",
"//packages/kbn-react-field:build_types",
- "//packages/kbn-repo-source-classifier-cli:build_types",
"//packages/kbn-repo-source-classifier:build_types",
+ "//packages/kbn-repo-source-classifier-cli:build_types",
"//packages/kbn-rule-data-utils:build_types",
"//packages/kbn-safer-lodash-set:build_types",
"//packages/kbn-securitysolution-autocomplete:build_types",
@@ -486,19 +486,19 @@ filegroup(
"//packages/kbn-stdio-dev-helpers:build_types",
"//packages/kbn-storybook:build_types",
"//packages/kbn-telemetry-tools:build_types",
- "//packages/kbn-test-jest-helpers:build_types",
"//packages/kbn-test:build_types",
+ "//packages/kbn-test-jest-helpers:build_types",
"//packages/kbn-tooling-log:build_types",
+ "//packages/kbn-type-summarizer:build_types",
"//packages/kbn-type-summarizer-cli:build_types",
"//packages/kbn-type-summarizer-core:build_types",
- "//packages/kbn-type-summarizer:build_types",
"//packages/kbn-typed-react-router-config:build_types",
"//packages/kbn-ui-shared-deps-npm:build_types",
"//packages/kbn-ui-shared-deps-src:build_types",
"//packages/kbn-ui-theme:build_types",
"//packages/kbn-user-profile-components:build_types",
- "//packages/kbn-utility-types-jest:build_types",
"//packages/kbn-utility-types:build_types",
+ "//packages/kbn-utility-types-jest:build_types",
"//packages/kbn-utils:build_types",
"//packages/kbn-yarn-lock-validator:build_types",
"//packages/shared-ux/avatar/solution:build_types",
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts
index bf068d7f3318570..7bd2443031c8009 100644
--- a/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts
+++ b/packages/kbn-apm-synthtrace/src/lib/apm/client/apm_synthtrace_kibana_client.ts
@@ -54,9 +54,8 @@ export class ApmSynthtraceKibanaClient {
});
}
async fetchLatestApmPackageVersion(currentKibanaVersion: string) {
- const url =
- 'https://epr-snapshot.elastic.co/search?package=apm&prerelease=true&all=true&kibana.version=';
- const response = await fetch(url + currentKibanaVersion, { method: 'GET' });
+ const url = `https://epr-snapshot.elastic.co/search?package=apm&prerelease=true&all=true&kibana.version=${currentKibanaVersion}`;
+ const response = await fetch(url, { method: 'GET' });
const json = (await response.json()) as Array<{ version: string }>;
const packageVersions = (json ?? []).map((item) => item.version).sort(Semver.rcompare);
const validPackageVersions = packageVersions.filter((v) => Semver.valid(v));
@@ -71,7 +70,7 @@ export class ApmSynthtraceKibanaClient {
async installApmPackage(kibanaUrl: string, version: string, username: string, password: string) {
const packageVersion = await this.fetchLatestApmPackageVersion(version);
- const response = await fetch(kibanaUrl + '/api/fleet/epm/packages/apm/' + packageVersion, {
+ const response = await fetch(`${kibanaUrl}/api/fleet/epm/packages/apm/${packageVersion}`, {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64'),
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/index.ts b/packages/kbn-apm-synthtrace/src/lib/apm/index.ts
index fcb8e078bf02abf..a136daabee8f2f6 100644
--- a/packages/kbn-apm-synthtrace/src/lib/apm/index.ts
+++ b/packages/kbn-apm-synthtrace/src/lib/apm/index.ts
@@ -13,6 +13,7 @@ import { getChromeUserAgentDefaults } from './defaults/get_chrome_user_agent_def
import { getBreakdownMetrics } from './processors/get_breakdown_metrics';
import { getApmWriteTargets } from './utils/get_apm_write_targets';
import { ApmSynthtraceEsClient } from './client/apm_synthtrace_es_client';
+import { ApmSynthtraceKibanaClient } from './client/apm_synthtrace_kibana_client';
import type { ApmException } from './apm_fields';
@@ -25,6 +26,7 @@ export const apm = {
getBreakdownMetrics,
getApmWriteTargets,
ApmSynthtraceEsClient,
+ ApmSynthtraceKibanaClient,
};
export type { ApmSynthtraceEsClient, ApmException };
diff --git a/packages/kbn-bazel-packages/BUILD.bazel b/packages/kbn-bazel-packages/BUILD.bazel
index e3a3545be167d61..9c7a793459623ed 100644
--- a/packages/kbn-bazel-packages/BUILD.bazel
+++ b/packages/kbn-bazel-packages/BUILD.bazel
@@ -7,6 +7,7 @@ PKG_REQUIRE_NAME = "@kbn/bazel-packages"
SOURCE_FILES = glob(
[
+ "src/**/*.js",
"src/**/*.ts",
],
exclude = [
@@ -37,11 +38,6 @@ NPM_MODULE_EXTRA_FILES = [
# "@npm//name-of-package"
# eg. "@npm//lodash"
RUNTIME_DEPS = [
- "//packages/kbn-utils",
- "//packages/kbn-std",
- "//packages/kbn-synthetic-package-map",
- "@npm//globby",
- "@npm//normalize-path",
]
# In this array place dependencies necessary to build the types, which will include the
@@ -55,13 +51,8 @@ RUNTIME_DEPS = [
# References to NPM packages work the same as RUNTIME_DEPS:
# eg. "@npm//@types/babel__core"
TYPES_DEPS = [
- "//packages/kbn-utils:npm_module_types",
- "//packages/kbn-std:npm_module_types",
- "//packages/kbn-synthetic-package-map:npm_module_types",
"@npm//@types/node",
- "@npm//@types/normalize-path",
- "@npm//globby",
- "@npm//normalize-path",
+ "@npm//@types/jest",
]
jsts_transpiler(
@@ -87,6 +78,7 @@ ts_project(
deps = TYPES_DEPS,
declaration = True,
declaration_map = True,
+ allow_js = True,
emit_declaration_only = True,
out_dir = "target_types",
root_dir = "src",
diff --git a/packages/kbn-bazel-packages/src/async.js b/packages/kbn-bazel-packages/src/async.js
new file mode 100644
index 000000000000000..38b535447953f38
--- /dev/null
+++ b/packages/kbn-bazel-packages/src/async.js
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+/**
+ *
+ * @template T
+ * @template T2
+ * @param {(v: T) => Promise} fn
+ * @param {T} item
+ * @returns {Promise>}
+ */
+const settle = async (fn, item) => {
+ const [result] = await Promise.allSettled([(async () => fn(item))()]);
+ return result;
+};
+
+/**
+ * @template T
+ * @template T2
+ * @param {Array} source
+ * @param {number} limit
+ * @param {(v: T) => Promise} mapFn
+ * @returns {Promise}
+ */
+function asyncMapWithLimit(source, limit, mapFn) {
+ return new Promise((resolve, reject) => {
+ if (limit < 1) {
+ reject(new Error('invalid limit, must be greater than 0'));
+ return;
+ }
+
+ let failed = false;
+ let inProgress = 0;
+ const queue = [...source.entries()];
+
+ /** @type {T2[]} */
+ const results = new Array(source.length);
+
+ /**
+ * this is run for each item, manages the inProgress state,
+ * calls the mapFn with that item, writes the map result to
+ * the result array, and calls runMore() after each item
+ * completes to either start another item or resolve the
+ * returned promise.
+ *
+ * @param {number} index
+ * @param {T} item
+ */
+ function run(index, item) {
+ inProgress += 1;
+ settle(mapFn, item).then((result) => {
+ inProgress -= 1;
+
+ if (failed) {
+ return;
+ }
+
+ if (result.status === 'fulfilled') {
+ results[index] = result.value;
+ runMore();
+ return;
+ }
+
+ // when an error occurs we update the state to prevent
+ // holding onto old results and ignore future results
+ // from in-progress promises
+ failed = true;
+ results.length = 0;
+ reject(result.reason);
+ });
+ }
+
+ /**
+ * If there is work in the queue, schedule it, if there isn't
+ * any work to be scheduled and there isn't anything in progress
+ * then we're done. This function is called every time a mapFn
+ * promise resolves and once after initialization
+ */
+ function runMore() {
+ if (!queue.length) {
+ if (inProgress === 0) {
+ resolve(results);
+ }
+
+ return;
+ }
+
+ while (inProgress < limit) {
+ const entry = queue.shift();
+ if (!entry) {
+ break;
+ }
+
+ run(...entry);
+ }
+ }
+
+ runMore();
+ });
+}
+
+module.exports = { asyncMapWithLimit };
diff --git a/packages/kbn-bazel-packages/src/async.test.ts b/packages/kbn-bazel-packages/src/async.test.ts
new file mode 100644
index 000000000000000..b238783f74fb856
--- /dev/null
+++ b/packages/kbn-bazel-packages/src/async.test.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 { setTimeout } from 'timers/promises';
+import { asyncMapWithLimit } from './async';
+
+const NUMS = [1, 2, 3, 4];
+const ident = jest.fn(async function ident(x: T) {
+ return await x;
+});
+const double = jest.fn(async function double(x: number) {
+ return x * 2;
+});
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+it('resolves with an empty array', async () => {
+ const result = await asyncMapWithLimit([], 10, ident);
+ expect(ident).not.toHaveBeenCalled();
+ expect(result).toEqual([]);
+});
+
+it('resolves with a mapped array', async () => {
+ const result = await asyncMapWithLimit(NUMS, 10, double);
+ expect(double).toHaveBeenCalledTimes(NUMS.length);
+ expect(result.join(',')).toMatchInlineSnapshot(`"2,4,6,8"`);
+});
+
+it('rejects when limit it not >= 1', async () => {
+ await expect(() => asyncMapWithLimit([], -1, ident)).rejects.toMatchInlineSnapshot(
+ `[Error: invalid limit, must be greater than 0]`
+ );
+ await expect(() => asyncMapWithLimit([], 0, ident)).rejects.toMatchInlineSnapshot(
+ `[Error: invalid limit, must be greater than 0]`
+ );
+ await expect(() => asyncMapWithLimit([], -Infinity, ident)).rejects.toMatchInlineSnapshot(
+ `[Error: invalid limit, must be greater than 0]`
+ );
+});
+
+it('rejects with the first error produced and stops calling mapFn', async () => {
+ const map = jest.fn(async (num) => {
+ if (num % 2 === 0) {
+ throw new Error('even numbers are not supported');
+ }
+ return num * 2;
+ });
+
+ await expect(() => asyncMapWithLimit(NUMS, 1, map)).rejects.toMatchInlineSnapshot(
+ `[Error: even numbers are not supported]`
+ );
+ await setTimeout(10);
+ expect(map).toHaveBeenCalledTimes(2);
+});
diff --git a/packages/kbn-bazel-packages/src/bazel_package.ts b/packages/kbn-bazel-packages/src/bazel_package.js
similarity index 67%
rename from packages/kbn-bazel-packages/src/bazel_package.ts
rename to packages/kbn-bazel-packages/src/bazel_package.js
index 91e411770175bdf..c6a31ccc0dbc1f9 100644
--- a/packages/kbn-bazel-packages/src/bazel_package.ts
+++ b/packages/kbn-bazel-packages/src/bazel_package.js
@@ -6,27 +6,31 @@
* Side Public License, v 1.
*/
-import { inspect } from 'util';
-import Path from 'path';
-import Fsp from 'fs/promises';
+const { inspect } = require('util');
+const Path = require('path');
+const Fsp = require('fs/promises');
-import normalizePath from 'normalize-path';
-import { REPO_ROOT } from '@kbn/utils';
-
-import { readPackageJson, ParsedPackageJson } from './parse_package_json';
+/** @typedef {import('./types').ParsedPackageJson} ParsedPackageJson */
+const { readPackageJson } = require('./parse_package_json');
const BUILD_RULE_NAME = /(^|\s)name\s*=\s*"build"/;
const BUILD_TYPES_RULE_NAME = /(^|\s)name\s*=\s*"build_types"/;
/**
* Representation of a Bazel Package in the Kibana repository
+ * @class
+ * @property {string} normalizedRepoRelativeDir
+ * @property {import('./types').ParsedPackageJson} pkg
+ * @property {string | undefined} buildBazelContent
*/
-export class BazelPackage {
+class BazelPackage {
/**
* Create a BazelPackage object from a package directory. Reads some files from the package and returns
* a Promise for a BazelPackage instance.
+ * @param {string} repoRoot
+ * @param {string} dir
*/
- static async fromDir(dir: string) {
+ static async fromDir(repoRoot, dir) {
const pkg = readPackageJson(Path.resolve(dir, 'package.json'));
let buildBazelContent;
@@ -36,23 +40,30 @@ export class BazelPackage {
throw new Error(`unable to read BUILD.bazel file in [${dir}]: ${error.message}`);
}
- return new BazelPackage(normalizePath(Path.relative(REPO_ROOT, dir)), pkg, buildBazelContent);
+ return new BazelPackage(Path.relative(repoRoot, dir), pkg, buildBazelContent);
}
constructor(
/**
* Relative path from the root of the repository to the package directory
+ * @type {string}
*/
- public readonly normalizedRepoRelativeDir: string,
+ normalizedRepoRelativeDir,
/**
* Parsed package.json file from the package
+ * @type {import('./types').ParsedPackageJson}
*/
- public readonly pkg: ParsedPackageJson,
+ pkg,
/**
* Content of the BUILD.bazel file
+ * @type {string | undefined}
*/
- private readonly buildBazelContent?: string
- ) {}
+ buildBazelContent = undefined
+ ) {
+ this.normalizedRepoRelativeDir = normalizedRepoRelativeDir;
+ this.pkg = pkg;
+ this.buildBazelContent = buildBazelContent;
+ }
/**
* Returns true if the package includes a `:build` bazel rule
@@ -83,3 +94,7 @@ export class BazelPackage {
return `BazelPackage<${this.normalizedRepoRelativeDir}>`;
}
}
+
+module.exports = {
+ BazelPackage,
+};
diff --git a/packages/kbn-bazel-packages/src/bazel_package_dirs.ts b/packages/kbn-bazel-packages/src/bazel_package_dirs.js
similarity index 68%
rename from packages/kbn-bazel-packages/src/bazel_package_dirs.ts
rename to packages/kbn-bazel-packages/src/bazel_package_dirs.js
index 755ef614c045619..7e3f728a21ca72e 100644
--- a/packages/kbn-bazel-packages/src/bazel_package_dirs.ts
+++ b/packages/kbn-bazel-packages/src/bazel_package_dirs.js
@@ -6,10 +6,7 @@
* Side Public License, v 1.
*/
-import globby from 'globby';
-import Path from 'path';
-
-import { REPO_ROOT } from '@kbn/utils';
+const { expandWildcards } = require('./find_files');
/**
* This is a list of repo-relative paths to directories containing packages. Do not
@@ -19,7 +16,7 @@ import { REPO_ROOT } from '@kbn/utils';
* eg. src/vis_editors => would find a package at src/vis_editors/foo/package.json
* src/vis_editors/* => would find a package at src/vis_editors/foo/bar/package.json
*/
-export const BAZEL_PACKAGE_DIRS = [
+const BAZEL_PACKAGE_DIRS = [
'packages',
'packages/shared-ux',
'packages/shared-ux/*',
@@ -34,18 +31,13 @@ export const BAZEL_PACKAGE_DIRS = [
/**
* Resolve all the BAZEL_PACKAGE_DIRS to absolute paths
+ * @param {string} repoRoot
*/
-export function getAllBazelPackageDirs() {
- return globby.sync(BAZEL_PACKAGE_DIRS, {
- cwd: REPO_ROOT,
- onlyDirectories: true,
- expandDirectories: false,
- });
+function getAllBazelPackageDirs(repoRoot) {
+ return expandWildcards(repoRoot, BAZEL_PACKAGE_DIRS);
}
-/**
- * Resolve all the BAZEL_PACKAGE_DIRS to repo-relative paths
- */
-export function getAllRepoRelativeBazelPackageDirs() {
- return getAllBazelPackageDirs().map((path) => Path.relative(REPO_ROOT, path));
-}
+module.exports = {
+ BAZEL_PACKAGE_DIRS,
+ getAllBazelPackageDirs,
+};
diff --git a/packages/kbn-bazel-packages/src/discover_packages.js b/packages/kbn-bazel-packages/src/discover_packages.js
new file mode 100644
index 000000000000000..17678115c74526b
--- /dev/null
+++ b/packages/kbn-bazel-packages/src/discover_packages.js
@@ -0,0 +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 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.
+ */
+
+const Path = require('path');
+
+const { BazelPackage } = require('./bazel_package');
+const { getAllBazelPackageDirs } = require('./bazel_package_dirs');
+const { findPackages } = require('./find_files');
+const { asyncMapWithLimit } = require('./async');
+
+/**
+ * @param {string} repoRoot
+ */
+function discoverBazelPackageLocations(repoRoot) {
+ const packagesWithBuildBazel = getAllBazelPackageDirs(repoRoot)
+ .flatMap((packageDir) => findPackages(packageDir, 'BUILD.bazel'))
+ .map((path) => Path.dirname(path));
+
+ // NOTE: only return as discovered packages with a package.json + BUILD.bazel file.
+ // In the future we should change this to only discover the ones with kibana.jsonc.
+ return getAllBazelPackageDirs(repoRoot)
+ .flatMap((packageDir) => findPackages(packageDir, 'package.json'))
+ .map((path) => Path.dirname(path))
+ .filter((pkg) => packagesWithBuildBazel.includes(pkg))
+ .sort((a, b) => a.localeCompare(b));
+}
+
+/**
+ * @param {string} repoRoot
+ */
+async function discoverBazelPackages(repoRoot) {
+ return await asyncMapWithLimit(
+ discoverBazelPackageLocations(repoRoot),
+ 100,
+ async (dir) => await BazelPackage.fromDir(repoRoot, dir)
+ );
+}
+
+module.exports = { discoverBazelPackageLocations, discoverBazelPackages };
diff --git a/packages/kbn-bazel-packages/src/discover_packages.ts b/packages/kbn-bazel-packages/src/discover_packages.ts
deleted file mode 100644
index 8b78e4e29311886..000000000000000
--- a/packages/kbn-bazel-packages/src/discover_packages.ts
+++ /dev/null
@@ -1,56 +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 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 Path from 'path';
-
-import globby from 'globby';
-import normalizePath from 'normalize-path';
-import { REPO_ROOT } from '@kbn/utils';
-import { asyncMapWithLimit } from '@kbn/std';
-
-import { BazelPackage } from './bazel_package';
-import { BAZEL_PACKAGE_DIRS } from './bazel_package_dirs';
-
-export function discoverBazelPackageLocations(repoRoot: string) {
- const packagesWithPackageJson = globby
- .sync(
- BAZEL_PACKAGE_DIRS.map((dir) => `${dir}/*/package.json`),
- {
- cwd: repoRoot,
- absolute: true,
- }
- )
- // NOTE: removing x-pack by default for now to prevent a situation where a BUILD.bazel file
- // needs to be added at the root of the folder which will make x-pack to be wrongly recognized
- // as a Bazel package in that case
- .filter((path) => !normalizePath(path).includes('x-pack/package.json'))
- .sort((a, b) => a.localeCompare(b))
- .map((path) => Path.dirname(path));
-
- const packagesWithBuildBazel = globby
- .sync(
- BAZEL_PACKAGE_DIRS.map((dir) => `${dir}/*/BUILD.bazel`),
- {
- cwd: repoRoot,
- absolute: true,
- }
- )
- .map((path) => Path.dirname(path));
-
- // NOTE: only return as discovered packages the ones with a package.json + BUILD.bazel file.
- // In the future we should change this to only discover the ones declaring kibana.json.
- return packagesWithPackageJson.filter((pkg) => packagesWithBuildBazel.includes(pkg));
-}
-
-export async function discoverBazelPackages(repoRoot: string = REPO_ROOT) {
- return await asyncMapWithLimit(
- discoverBazelPackageLocations(repoRoot),
- 100,
- async (dir) => await BazelPackage.fromDir(dir)
- );
-}
diff --git a/packages/kbn-bazel-packages/src/find_files.js b/packages/kbn-bazel-packages/src/find_files.js
new file mode 100644
index 000000000000000..9be6c2e25607905
--- /dev/null
+++ b/packages/kbn-bazel-packages/src/find_files.js
@@ -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 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.
+ */
+
+const Path = require('path');
+const Fs = require('fs');
+
+/**
+ * @param {string} path
+ */
+function safeIsFile(path) {
+ try {
+ return Fs.statSync(path).isFile();
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ return false;
+ }
+
+ throw error;
+ }
+}
+
+/**
+ * @param {string} path
+ */
+function safeReadDir(path) {
+ try {
+ return Fs.readdirSync(path, {
+ withFileTypes: true,
+ });
+ } catch (error) {
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR') {
+ return [];
+ }
+
+ throw error;
+ }
+}
+
+/**
+ * Search for `name` files in directories within `packageDir`
+ *
+ * @param {string} packageDir
+ * @param {string} name
+ * @returns {string[]}
+ */
+function findPackages(packageDir, name) {
+ return (
+ // get the directories within the "package dir"
+ safeReadDir(packageDir)
+ // if this directory has a file with the name, it's a match
+ .flatMap((e) => {
+ if (!e.isDirectory()) {
+ return [];
+ }
+
+ const path = Path.resolve(packageDir, e.name, name);
+ return safeIsFile(path) ? path : [];
+ })
+ );
+}
+
+/**
+ * Expand simple `*` wildcards in patterns, which are otherwise expected to be
+ * paths relative to `cwd`.
+ *
+ * @param {string} cwd
+ * @param {string[]} patterns
+ */
+function expandWildcards(cwd, patterns) {
+ /** @type {Set} */
+ const results = new Set();
+
+ /** @type {Set} */
+ const queue = new Set(patterns.map((p) => Path.resolve(cwd, p)));
+
+ for (const pattern of queue) {
+ let length = 3;
+ let index = pattern.indexOf('/*/');
+ if (index === -1 && pattern.endsWith('/*')) {
+ length = 2;
+ index = pattern.length - length;
+ }
+
+ if (index === -1) {
+ results.add(pattern);
+ continue;
+ }
+
+ const left = pattern.slice(0, index + 1);
+ const right = pattern.slice(index + length);
+ for (const ent of safeReadDir(left)) {
+ if (!ent.isDirectory()) {
+ continue;
+ }
+
+ const path = Path.resolve(left, ent.name);
+
+ if (right) {
+ queue.add(Path.resolve(path, right));
+ } else {
+ results.add(path);
+ }
+ }
+ }
+
+ return [...results];
+}
+
+module.exports = { findPackages, expandWildcards };
diff --git a/packages/kbn-bazel-packages/src/index.js b/packages/kbn-bazel-packages/src/index.js
new file mode 100644
index 000000000000000..254b8bb9da44c6c
--- /dev/null
+++ b/packages/kbn-bazel-packages/src/index.js
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** @typedef {import('./bazel_package').BazelPackage} BazelPackage */
+
+const { BAZEL_PACKAGE_DIRS, getAllBazelPackageDirs } = require('./bazel_package_dirs');
+const { discoverBazelPackageLocations, discoverBazelPackages } = require('./discover_packages');
+
+module.exports = {
+ BAZEL_PACKAGE_DIRS,
+ getAllBazelPackageDirs,
+ discoverBazelPackageLocations,
+ discoverBazelPackages,
+};
diff --git a/packages/kbn-bazel-packages/src/index.ts b/packages/kbn-bazel-packages/src/index.ts
deleted file mode 100644
index 55d5db9bb3adb21..000000000000000
--- a/packages/kbn-bazel-packages/src/index.ts
+++ /dev/null
@@ -1,11 +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 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 './bazel_package_dirs';
-export * from './discover_packages';
-export type { BazelPackage } from './bazel_package';
diff --git a/packages/kbn-bazel-packages/src/parse_package_json.ts b/packages/kbn-bazel-packages/src/parse_package_json.js
similarity index 57%
rename from packages/kbn-bazel-packages/src/parse_package_json.ts
rename to packages/kbn-bazel-packages/src/parse_package_json.js
index b060656c0c51256..27a78f91a446613 100644
--- a/packages/kbn-bazel-packages/src/parse_package_json.ts
+++ b/packages/kbn-bazel-packages/src/parse_package_json.js
@@ -6,40 +6,22 @@
* Side Public License, v 1.
*/
-import Fs from 'fs';
+const Fs = require('fs');
/**
- * Simple parsed representation of a package.json file, validated
- * by `assertParsedPackageJson()` and extensible as needed in the future
+ * @param {unknown} v
+ * @returns {v is Record}
*/
-export interface ParsedPackageJson {
- /** The name of the package, usually `@kbn/`+something */
- name: string;
- /** "dependenices" property from package.json */
- dependencies?: Record;
- /** "devDependenices" property from package.json */
- devDependencies?: Record;
- /** Some kibana specific properties about this package */
- kibana?: {
- /** Is this package only intended for dev? */
- devOnly?: boolean;
- };
- /** Scripts defined in the package.json file */
- scripts?: {
- [key: string]: string | undefined;
- };
- /** All other fields in the package.json are typed as unknown as we don't care what they are */
- [key: string]: unknown;
-}
-
-function isObj(v: unknown): v is Record {
+function isObj(v) {
return !!(typeof v === 'object' && v);
}
/**
* Asserts that given value looks like a parsed package.json file
+ * @param {unknown} v
+ * @returns {asserts v is import('./types').ParsedPackageJson}
*/
-export function assertParsedPackageJson(v: unknown): asserts v is ParsedPackageJson {
+function validateParsedPackageJson(v) {
if (!isObj(v) || typeof v.name !== 'string') {
throw new Error('Expected at least a "name" property');
}
@@ -66,14 +48,18 @@ export function assertParsedPackageJson(v: unknown): asserts v is ParsedPackageJ
/**
* Reads a given package.json file from disk and parses it
+ * @param {string} path
+ * @returns {import('./types').ParsedPackageJson}
*/
-export function readPackageJson(path: string): ParsedPackageJson {
+function readPackageJson(path) {
let pkg;
try {
pkg = JSON.parse(Fs.readFileSync(path, 'utf8'));
- assertParsedPackageJson(pkg);
+ validateParsedPackageJson(pkg);
} catch (error) {
throw new Error(`unable to parse package.json at [${path}]: ${error.message}`);
}
return pkg;
}
+
+module.exports = { readPackageJson, validateParsedPackageJson };
diff --git a/packages/kbn-bazel-packages/src/types.ts b/packages/kbn-bazel-packages/src/types.ts
new file mode 100644
index 000000000000000..dc77d35bc206af9
--- /dev/null
+++ b/packages/kbn-bazel-packages/src/types.ts
@@ -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.
+ */
+
+/**
+ * Simple parsed representation of a package.json file, validated
+ * by `assertParsedPackageJson()` and extensible as needed in the future
+ */
+export interface ParsedPackageJson {
+ /** The name of the package, usually `@kbn/`+something */
+ name: string;
+ /** "dependenices" property from package.json */
+ dependencies?: Record;
+ /** "devDependenices" property from package.json */
+ devDependencies?: Record;
+ /** Some kibana specific properties about this package */
+ kibana?: {
+ /** Is this package only intended for dev? */
+ devOnly?: boolean;
+ };
+ /** Scripts defined in the package.json file */
+ scripts?: {
+ [key: string]: string | undefined;
+ };
+ /** All other fields in the package.json are typed as unknown as we don't care what they are */
+ [key: string]: unknown;
+}
diff --git a/packages/kbn-bazel-packages/tsconfig.json b/packages/kbn-bazel-packages/tsconfig.json
index 789c6b3111115dd..9f78bc243ac66cb 100644
--- a/packages/kbn-bazel-packages/tsconfig.json
+++ b/packages/kbn-bazel-packages/tsconfig.json
@@ -4,6 +4,8 @@
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
+ "allowJs": true,
+ "checkJs": true,
"outDir": "target_types",
"rootDir": "src",
"stripInternal": false,
diff --git a/packages/kbn-generate/src/commands/package_command.ts b/packages/kbn-generate/src/commands/package_command.ts
index 4f5d58184aeac73..32d5ff008c79f48 100644
--- a/packages/kbn-generate/src/commands/package_command.ts
+++ b/packages/kbn-generate/src/commands/package_command.ts
@@ -162,7 +162,7 @@ ${BAZEL_PACKAGE_DIRS.map((dir) => ` ./${dir}/*\n`).join
Path.resolve(TEMPLATE_DIR, 'packages_BUILD.bazel.ejs'),
Path.resolve(REPO_ROOT, 'packages/BUILD.bazel'),
{
- packages: await discoverBazelPackages(),
+ packages: await discoverBazelPackages(REPO_ROOT),
}
);
log.info('Updated packages/BUILD.bazel');
diff --git a/packages/kbn-generate/src/commands/packages_build_manifest_command.ts b/packages/kbn-generate/src/commands/packages_build_manifest_command.ts
index 9103d56d42a1dbf..9b05c1aed332de4 100644
--- a/packages/kbn-generate/src/commands/packages_build_manifest_command.ts
+++ b/packages/kbn-generate/src/commands/packages_build_manifest_command.ts
@@ -31,7 +31,7 @@ export const PackagesBuildManifestCommand: GenerateCommand = {
async run({ log, render, flags }) {
const validate = !!flags.validate;
- const packages = await discoverBazelPackages();
+ const packages = await discoverBazelPackages(REPO_ROOT);
const dest = Path.resolve(REPO_ROOT, 'packages/BUILD.bazel');
const relDest = Path.relative(process.cwd(), dest);
diff --git a/src/dev/build/tasks/build_packages_task.ts b/src/dev/build/tasks/build_packages_task.ts
index 607e0ac0f08bafd..521b1299f423d0d 100644
--- a/src/dev/build/tasks/build_packages_task.ts
+++ b/src/dev/build/tasks/build_packages_task.ts
@@ -8,6 +8,7 @@
import Path from 'path';
+import { REPO_ROOT } from '@kbn/utils';
import { discoverBazelPackages } from '@kbn/bazel-packages';
import { runBazel } from '@kbn/bazel-runner';
@@ -16,7 +17,7 @@ import { Task, scanCopy, write } from '../lib';
export const BuildBazelPackages: Task = {
description: 'Building distributable versions of Bazel packages',
async run(config, log, build) {
- const packages = (await discoverBazelPackages()).filter((pkg) => !pkg.isDevOnly());
+ const packages = (await discoverBazelPackages(REPO_ROOT)).filter((pkg) => !pkg.isDevOnly());
log.info(`Preparing Bazel projects production build non-devOnly packages`);
await runBazel(['build', '//packages:build']);
diff --git a/src/dev/build/tasks/clean_tasks.ts b/src/dev/build/tasks/clean_tasks.ts
index 15b20e712334c40..409f6a77b50bfca 100644
--- a/src/dev/build/tasks/clean_tasks.ts
+++ b/src/dev/build/tasks/clean_tasks.ts
@@ -8,6 +8,7 @@
import minimatch from 'minimatch';
import { discoverBazelPackages } from '@kbn/bazel-packages';
+import { REPO_ROOT } from '@kbn/utils';
import { deleteAll, deleteEmptyFolders, scanDelete, Task, GlobalTask } from '../lib';
export const Clean: GlobalTask = {
@@ -259,7 +260,7 @@ export const DeleteBazelPackagesFromBuildRoot: Task = {
'Deleting bazel packages outputs from build folder root as they are now installed as node_modules',
async run(config, log, build) {
- const bazelPackagesOnBuildRoot = (await discoverBazelPackages()).map((pkg) =>
+ const bazelPackagesOnBuildRoot = (await discoverBazelPackages(REPO_ROOT)).map((pkg) =>
build.resolvePath(pkg.normalizedRepoRelativeDir)
);
diff --git a/src/plugins/controls/common/control_types/time_slider/time_slider_persistable_state.ts b/src/plugins/controls/common/control_types/time_slider/time_slider_persistable_state.ts
deleted file mode 100644
index b8b3a5f16b51f6e..000000000000000
--- a/src/plugins/controls/common/control_types/time_slider/time_slider_persistable_state.ts
+++ /dev/null
@@ -1,47 +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 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 {
- EmbeddableStateWithType,
- EmbeddablePersistableStateService,
-} from '@kbn/embeddable-plugin/common';
-import { SavedObjectReference } from '@kbn/core/types';
-import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common';
-import { TimeSliderControlEmbeddableInput } from './types';
-
-type TimeSliderInputWithType = Partial & { type: string };
-const dataViewReferenceName = 'timeSliderDataView';
-
-export const createTimeSliderInject = (): EmbeddablePersistableStateService['inject'] => {
- return (state: EmbeddableStateWithType, references: SavedObjectReference[]) => {
- const workingState = { ...state } as EmbeddableStateWithType | TimeSliderInputWithType;
- references.forEach((reference) => {
- if (reference.name === dataViewReferenceName) {
- (workingState as TimeSliderInputWithType).dataViewId = reference.id;
- }
- });
- return workingState as EmbeddableStateWithType;
- };
-};
-
-export const createTimeSliderExtract = (): EmbeddablePersistableStateService['extract'] => {
- return (state: EmbeddableStateWithType) => {
- const workingState = { ...state } as EmbeddableStateWithType | TimeSliderInputWithType;
- const references: SavedObjectReference[] = [];
-
- if ('dataViewId' in workingState) {
- references.push({
- name: dataViewReferenceName,
- type: DATA_VIEW_SAVED_OBJECT_TYPE,
- id: workingState.dataViewId!,
- });
- delete workingState.dataViewId;
- }
- return { state: workingState as EmbeddableStateWithType, references };
- };
-};
diff --git a/src/plugins/controls/common/control_types/time_slider/types.ts b/src/plugins/controls/common/control_types/time_slider/types.ts
deleted file mode 100644
index 31272380becde90..000000000000000
--- a/src/plugins/controls/common/control_types/time_slider/types.ts
+++ /dev/null
@@ -1,15 +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 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 { DataControlInput } from '../../types';
-
-export const TIME_SLIDER_CONTROL = 'timeSlider';
-
-export interface TimeSliderControlEmbeddableInput extends DataControlInput {
- value?: [number | null, number | null];
-}
diff --git a/src/plugins/controls/common/index.ts b/src/plugins/controls/common/index.ts
index c3361c29232bd95..a201553c09ec12a 100644
--- a/src/plugins/controls/common/index.ts
+++ b/src/plugins/controls/common/index.ts
@@ -36,5 +36,3 @@ export {
// Control Type exports
export { OPTIONS_LIST_CONTROL, type OptionsListEmbeddableInput } from './options_list/types';
export { type RangeSliderEmbeddableInput, RANGE_SLIDER_CONTROL } from './range_slider/types';
-
-export { TIME_SLIDER_CONTROL } from './control_types/time_slider/types';
diff --git a/src/plugins/controls/public/__stories__/storybook_control_factories.ts b/src/plugins/controls/public/__stories__/storybook_control_factories.ts
index 9b0b41f6393e735..2e27339a9eaf876 100644
--- a/src/plugins/controls/public/__stories__/storybook_control_factories.ts
+++ b/src/plugins/controls/public/__stories__/storybook_control_factories.ts
@@ -8,7 +8,6 @@
import { OptionsListEmbeddableFactory } from '../options_list';
import { RangeSliderEmbeddableFactory } from '../range_slider';
-import { TimesliderEmbeddableFactory } from '../control_types/time_slider';
import { ControlsService } from '../services/controls';
import { ControlFactory } from '..';
@@ -26,9 +25,4 @@ export const populateStorybookControlFactories = (controlsServiceStub: ControlsS
const rangeSliderControlFactory = rangeSliderFactoryStub as unknown as ControlFactory;
rangeSliderControlFactory.getDefaultInput = () => ({});
controlsServiceStub.registerControlType(rangeSliderControlFactory);
-
- const timesliderFactoryStub = new TimesliderEmbeddableFactory();
- const timeSliderControlFactory = timesliderFactoryStub as unknown as ControlFactory;
- timeSliderControlFactory.getDefaultInput = () => ({});
- controlsServiceStub.registerControlType(timeSliderControlFactory);
};
diff --git a/src/plugins/controls/public/control_types/time_slider/__stories__/time_slider.component.stories.tsx b/src/plugins/controls/public/control_types/time_slider/__stories__/time_slider.component.stories.tsx
deleted file mode 100644
index 90ea07dc276bd56..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/__stories__/time_slider.component.stories.tsx
+++ /dev/null
@@ -1,196 +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 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, { FC, useCallback, useState } from 'react';
-import moment from 'moment';
-import { EuiFormControlLayout } from '@elastic/eui';
-
-import { TimeSliderProps, TimeSlider } from '../time_slider.component';
-
-export default {
- title: 'Time Slider',
- description: '',
-};
-
-const TimeSliderWrapper: FC> = (props) => {
- const [value, setValue] = useState(props.value);
- const onChange = useCallback(
- (newValue: [number | null, number | null]) => {
- const lowValue = newValue[0];
- const highValue = newValue[1];
-
- setValue([lowValue, highValue]);
- },
- [setValue]
- );
-
- return (
-
-
-
-
-
- );
-};
-
-const undefinedValue: [null, null] = [null, null];
-const undefinedRange: [undefined, undefined] = [undefined, undefined];
-
-export const TimeSliderNoValuesOrRange = () => {
- // If range is undefined, that should be inndicate that we are loading the range
- return ;
-};
-
-export const TimeSliderUndefinedRangeNoValue = () => {
- // If a range is [undefined, undefined] then it was loaded, but no values were found.
- return ;
-};
-
-export const TimeSliderUndefinedRangeWithValue = () => {
- const lastWeek = moment().subtract(7, 'days');
- const now = moment();
-
- return (
-
- );
-};
-
-export const TimeSliderWithRangeAndNoValue = () => {
- const lastWeek = moment().subtract(7, 'days');
- const now = moment();
-
- return (
-
- );
-};
-
-export const TimeSliderWithRangeAndLowerValue = () => {
- const lastWeek = moment().subtract(7, 'days');
- const now = moment();
-
- const threeDays = moment().subtract(3, 'days');
-
- return (
-
- );
-};
-
-export const TimeSliderWithRangeAndUpperValue = () => {
- const lastWeek = moment().subtract(7, 'days');
- const now = moment();
-
- const threeDays = moment().subtract(3, 'days');
-
- return (
-
- );
-};
-
-export const TimeSliderWithLowRangeOverlap = () => {
- const lastWeek = moment().subtract(7, 'days');
- const now = moment();
-
- const threeDays = moment().subtract(3, 'days');
- const twoDays = moment().subtract(2, 'days');
-
- return (
-
- );
-};
-
-export const TimeSliderWithLowRangeOverlapAndIgnoredValidation = () => {
- const lastWeek = moment().subtract(7, 'days');
- const now = moment();
-
- const threeDays = moment().subtract(3, 'days');
- const twoDays = moment().subtract(2, 'days');
-
- return (
-
- );
-};
-
-export const TimeSliderWithRangeLowerThanValue = () => {
- const twoWeeksAgo = moment().subtract(14, 'days');
- const lastWeek = moment().subtract(7, 'days');
-
- const now = moment();
- const threeDays = moment().subtract(3, 'days');
-
- return (
-
- );
-};
-
-export const TimeSliderWithRangeHigherThanValue = () => {
- const twoWeeksAgo = moment().subtract(14, 'days');
- const lastWeek = moment().subtract(7, 'days');
-
- const now = moment();
- const threeDays = moment().subtract(3, 'days');
-
- return (
-
- );
-};
-
-export const PartialValueLowerThanRange = () => {
- // Selected value is March 8 -> March 9
- // Range is March 11 -> 25
- const eightDaysAgo = moment().subtract(8, 'days');
-
- const lastWeek = moment().subtract(7, 'days');
- const today = moment();
-
- return (
-
- );
-};
-
-export const PartialValueHigherThanRange = () => {
- // Selected value is March 8 -> March 9
- // Range is March 11 -> 25
- const eightDaysAgo = moment().subtract(8, 'days');
-
- const lastWeek = moment().subtract(7, 'days');
- const today = moment();
-
- return (
-
- );
-};
diff --git a/src/plugins/controls/public/control_types/time_slider/index.ts b/src/plugins/controls/public/control_types/time_slider/index.ts
deleted file mode 100644
index 1cd5900164676d2..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/index.ts
+++ /dev/null
@@ -1,11 +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 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 { TimesliderEmbeddableFactory } from './time_slider_embeddable_factory';
-export { type TimeSliderControlEmbeddableInput } from '../../../common/control_types/time_slider/types';
-export {} from '../../../common';
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider.component.scss b/src/plugins/controls/public/control_types/time_slider/time_slider.component.scss
deleted file mode 100644
index 3f8a37ec44d3705..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider.component.scss
+++ /dev/null
@@ -1,47 +0,0 @@
-.timeSlider__anchorOverride {
- display:block;
- >div {
- height: 100%;
- }
-}
-
-.timeSlider__popoverOverride {
- width: 100%;
- max-width: 100%;
- height: 100%;
-}
-
-.timeSlider__panelOverride {
- min-width: $euiSizeXXL * 15;
-}
-
-.timeSlider__anchor {
- text-decoration: none;
- width: 100%;
- background-color: $euiFormBackgroundColor;
- box-shadow: none;
- @include euiFormControlSideBorderRadius($euiFormControlBorderRadius, $side: 'right', $internal: true);
- overflow: hidden;
- height: 100%;
-
- &:enabled:focus {
- background-color: $euiFormBackgroundColor;
- }
-
- .euiText {
- background-color: $euiFormBackgroundColor;
- }
-
- .timeSlider__anchorText {
- font-weight: $euiFontWeightBold;
- }
-
- .timeSlider__anchorText--default {
- color: $euiColorMediumShade;
- }
-
- .timeSlider__anchorText--invalid {
- text-decoration: line-through;
- color: $euiColorMediumShade;
- }
-}
\ No newline at end of file
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx b/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx
deleted file mode 100644
index 1bb2f90b44121f4..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider.component.tsx
+++ /dev/null
@@ -1,343 +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 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, { FC, useState, useMemo, useCallback } from 'react';
-import { isNil } from 'lodash';
-import {
- EuiText,
- EuiLoadingSpinner,
- EuiInputPopover,
- EuiPopoverTitle,
- EuiSpacer,
- EuiFlexItem,
- EuiFlexGroup,
- EuiToolTip,
- EuiButtonIcon,
-} from '@elastic/eui';
-import { EuiRangeTick } from '@elastic/eui/src/components/form/range/range_ticks';
-import moment from 'moment-timezone';
-import { calcAutoIntervalNear } from '@kbn/data-plugin/common';
-import { ValidatedDualRange } from '@kbn/kibana-react-plugin/public';
-import { TimeSliderStrings } from './time_slider_strings';
-import './time_slider.component.scss';
-
-function getScaledDateFormat(interval: number): string {
- if (interval >= moment.duration(1, 'y').asMilliseconds()) {
- return 'YYYY';
- }
-
- if (interval >= moment.duration(1, 'd').asMilliseconds()) {
- return 'MMM D';
- }
-
- if (interval >= moment.duration(6, 'h').asMilliseconds()) {
- return 'Do HH';
- }
-
- if (interval >= moment.duration(1, 'h').asMilliseconds()) {
- return 'HH:mm';
- }
-
- if (interval >= moment.duration(1, 'm').asMilliseconds()) {
- return 'HH:mm';
- }
-
- if (interval >= moment.duration(1, 's').asMilliseconds()) {
- return 'mm:ss';
- }
-
- return 'ss.SSS';
-}
-
-export function getInterval(min: number, max: number, steps = 6): number {
- const duration = max - min;
- let interval = calcAutoIntervalNear(steps, duration).asMilliseconds();
- // Sometimes auto interval is not quite right and returns 2X or 3X requested ticks
- // Adjust the interval to get closer to the requested number of ticks
- const actualSteps = duration / interval;
- if (actualSteps > steps * 1.5) {
- const factor = Math.round(actualSteps / steps);
- interval *= factor;
- } else if (actualSteps < 5) {
- interval *= 0.5;
- }
- return interval;
-}
-
-export interface TimeSliderProps {
- id: string;
- range?: [number | undefined, number | undefined];
- value: [number | null, number | null];
- onChange: (range: [number | null, number | null]) => void;
- dateFormat?: string;
- timezone?: string;
- fieldName: string;
- ignoreValidation?: boolean;
-}
-
-const isValidRange = (maybeRange: TimeSliderProps['range']): maybeRange is [number, number] => {
- return maybeRange !== undefined && !isNil(maybeRange[0]) && !isNil(maybeRange[1]);
-};
-
-const unselectedClass = 'timeSlider__anchorText--default';
-const validClass = 'timeSlider__anchorText';
-const invalidClass = 'timeSlider__anchorText--invalid';
-
-export const TimeSlider: FC = (props) => {
- const defaultProps = {
- dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS',
- ignoreValidation: false,
- timezone: 'Browser',
- ...props,
- };
- const { range, value, timezone, dateFormat, fieldName, ignoreValidation } = defaultProps;
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
- const togglePopover = useCallback(() => {
- setIsPopoverOpen(!isPopoverOpen);
- }, [isPopoverOpen, setIsPopoverOpen]);
-
- const getTimezone = useCallback(() => {
- const detectedTimezone = moment.tz.guess();
-
- return timezone === 'Browser' ? detectedTimezone : timezone;
- }, [timezone]);
-
- const epochToKbnDateFormat = useCallback(
- (epoch: number) => {
- const tz = getTimezone();
- return moment.tz(epoch, tz).format(dateFormat);
- },
- [dateFormat, getTimezone]
- );
-
- // If we don't have a range or we have is loading, show the loading state
- const hasRange = range !== undefined;
-
- // We have values if we have a range or value entry for both position
- const hasValues =
- (value[0] !== null || (hasRange && range[0] !== undefined)) &&
- (value[1] !== null || (hasRange && range[1] !== undefined));
-
- let valueText: JSX.Element | null = null;
- if (hasValues) {
- let lower = value[0] !== null ? value[0] : range![0]!;
- let upper = value[1] !== null ? value[1] : range![1]!;
-
- if (value[0] !== null && lower > upper) {
- upper = lower;
- } else if (value[1] !== null && lower > upper) {
- lower = upper;
- }
-
- const hasLowerValueInRange =
- value[0] !== null && isValidRange(range) && value[0] >= range[0] && value[0] <= range[1];
- // It's out of range if the upper value is above the upper range or below the lower range
- const hasUpperValueInRange =
- value[1] !== null && isValidRange(range) && value[1] <= range[1] && value[1] >= range[0];
-
- let lowClass = unselectedClass;
- let highClass = unselectedClass;
- if (value[0] !== null && (hasLowerValueInRange || ignoreValidation)) {
- lowClass = validClass;
- } else if (value[0] !== null) {
- lowClass = invalidClass;
- }
-
- if (value[1] !== null && (hasUpperValueInRange || ignoreValidation)) {
- highClass = validClass;
- } else if (value[1] !== null) {
- highClass = invalidClass;
- }
-
- // if no value then anchorText default
- // if hasLowerValueInRange || skipValidation then anchor text
- // else strikethrough
-
- valueText = (
-
- {epochToKbnDateFormat(lower)}
- →
- {epochToKbnDateFormat(upper)}
-
- );
- }
-
- const button = (
-
- );
-
- return (
- setIsPopoverOpen(false)}
- panelPaddingSize="s"
- anchorPosition="downCenter"
- disableFocusTrap
- attachToAnchor={false}
- >
- {isValidRange(range) ? (
-
- ) : (
-
- )}
-
- );
-};
-
-const TimeSliderComponentPopoverNoDocuments: FC = () => {
- return {TimeSliderStrings.noDocumentsPopover.getLabel()};
-};
-
-export const TimeSliderComponentPopover: FC<
- TimeSliderProps & {
- range: [number, number];
- getTimezone: () => string;
- epochToKbnDateFormat: (epoch: number) => string;
- }
-> = ({ range, value, onChange, getTimezone, epochToKbnDateFormat, fieldName }) => {
- const [lowerBound, upperBound] = range;
- let [lowerValue, upperValue] = value;
-
- if (lowerValue === null) {
- lowerValue = lowerBound;
- }
-
- if (upperValue === null) {
- upperValue = upperBound;
- }
-
- const fullRange = useMemo(
- () => [Math.min(lowerValue!, lowerBound), Math.max(upperValue!, upperBound)],
- [lowerValue, lowerBound, upperValue, upperBound]
- );
-
- const getTicks = useCallback(
- (min: number, max: number, interval: number): EuiRangeTick[] => {
- const format = getScaledDateFormat(interval);
- const tz = getTimezone();
-
- let tick = Math.ceil(min / interval) * interval;
- const ticks: EuiRangeTick[] = [];
- while (tick < max) {
- ticks.push({
- value: tick,
- label: moment.tz(tick, tz).format(format),
- });
- tick += interval;
- }
-
- return ticks;
- },
- [getTimezone]
- );
-
- const ticks = useMemo(() => {
- const interval = getInterval(fullRange[0], fullRange[1]);
- return getTicks(fullRange[0], fullRange[1], interval);
- }, [fullRange, getTicks]);
-
- const onChangeHandler = useCallback(
- ([_min, _max]: [number | string, number | string]) => {
- // If a value is undefined and the number that is given here matches the range bounds
- // then we will ignore it, becuase they probably didn't actually select that value
- const report: [number | null, number | null] = [null, null];
-
- let min: number;
- let max: number;
- if (typeof _min === 'string') {
- min = parseFloat(_min);
- min = isNaN(min) ? range[0] : min;
- } else {
- min = _min;
- }
-
- if (typeof _max === 'string') {
- max = parseFloat(_max);
- max = isNaN(max) ? range[0] : max;
- } else {
- max = _max;
- }
-
- if (value[0] !== null || min !== range[0]) {
- report[0] = min;
- }
- if (value[1] !== null || max !== range[1]) {
- report[1] = max;
- }
-
- onChange(report);
- },
- [onChange, value, range]
- );
-
- const levels = [{ min: range[0], max: range[1], color: 'success' }];
-
- return (
- <>
- {fieldName}
-
- {epochToKbnDateFormat(lowerValue)} - {epochToKbnDateFormat(upperValue)}
-
-
-
-
-
-
-
-
- onChange([null, null])}
- aria-label={TimeSliderStrings.resetButton.getLabel()}
- data-test-subj="timeSlider__clearRangeButton"
- />
-
-
-
-
- >
- );
-};
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider.tsx b/src/plugins/controls/public/control_types/time_slider/time_slider.tsx
deleted file mode 100644
index 0b519406ccf8d6e..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider.tsx
+++ /dev/null
@@ -1,79 +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 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, { FC, useCallback, useMemo } from 'react';
-import { BehaviorSubject } from 'rxjs';
-import { debounce } from 'lodash';
-import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';
-
-import { timeSliderReducers } from './time_slider_reducers';
-import { TimeSlider as Component } from './time_slider.component';
-import { TimeSliderReduxState, TimeSliderSubjectState } from './types';
-
-interface TimeSliderProps {
- componentStateSubject: BehaviorSubject;
- dateFormat: string;
- timezone: string;
- fieldName: string;
- ignoreValidation: boolean;
-}
-
-export const TimeSlider: FC = ({
- componentStateSubject,
- dateFormat,
- timezone,
- fieldName,
- ignoreValidation,
-}) => {
- const {
- useEmbeddableDispatch,
- useEmbeddableSelector: select,
- actions: { selectRange },
- } = useReduxEmbeddableContext();
- const dispatch = useEmbeddableDispatch();
-
- const availableRange = select((state) => state.componentState.range);
- const value = select((state) => state.explicitInput.value);
- const id = select((state) => state.explicitInput.id);
-
- const { min, max } = availableRange
- ? availableRange
- : ({} as {
- min?: number;
- max?: number;
- });
-
- const dispatchChange = useCallback(
- (range: [number | null, number | null]) => {
- dispatch(selectRange(range));
- },
- [dispatch, selectRange]
- );
-
- const debouncedDispatchChange = useMemo(() => debounce(dispatchChange, 500), [dispatchChange]);
-
- const onChangeComplete = useCallback(
- (range: [number | null, number | null]) => {
- debouncedDispatchChange(range);
- },
- [debouncedDispatchChange]
- );
-
- return (
-
- );
-};
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable.test.ts b/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable.test.ts
deleted file mode 100644
index 4db5277a0e2f9db..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable.test.ts
+++ /dev/null
@@ -1,300 +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 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 { of } from 'rxjs';
-import { delay, map } from 'rxjs/operators';
-import { TimeSliderControlEmbeddableInput } from '.';
-import { TimeSliderControlEmbeddable } from './time_slider_embeddable';
-import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub';
-import { pluginServices } from '../../services';
-import { TestScheduler } from 'rxjs/testing';
-import { buildRangeFilter } from '@kbn/es-query';
-import { ReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public';
-
-const buildFilter = (range: [number | null, number | null]) => {
- const filterPieces: Record = {};
- if (range[0] !== null) {
- filterPieces.gte = range[0];
- }
- if (range[1] !== null) {
- filterPieces.lte = range[1];
- }
-
- const filter = buildRangeFilter(
- stubLogstashDataView.getFieldByName('bytes')!,
- filterPieces,
- stubLogstashDataView
- );
- filter.meta.key = 'bytes';
-
- return filter;
-};
-
-const rangeMin = 20;
-const rangeMax = 30;
-const range = { min: rangeMin, max: rangeMax };
-
-const lowerValue: [number, number] = [15, 25];
-const upperValue: [number, number] = [25, 35];
-const partialLowValue: [number, null] = [25, null];
-const partialHighValue: [null, number] = [null, 25];
-const withinRangeValue: [number, number] = [21, 29];
-const outOfRangeValue: [number, number] = [31, 40];
-
-const rangeFilter = buildFilter([rangeMin, rangeMax]);
-const lowerValueFilter = buildFilter(lowerValue);
-const lowerValuePartialFilter = buildFilter([20, 25]);
-const upperValueFilter = buildFilter(upperValue);
-const upperValuePartialFilter = buildFilter([25, 30]);
-
-const partialLowValueFilter = buildFilter(partialLowValue);
-const partialHighValueFilter = buildFilter(partialHighValue);
-const withinRangeValueFilter = buildFilter(withinRangeValue);
-const outOfRangeValueFilter = buildFilter(outOfRangeValue);
-
-const baseInput: TimeSliderControlEmbeddableInput = {
- id: 'id',
- fieldName: 'bytes',
- dataViewId: stubLogstashDataView.id!,
-};
-
-const mockReduxEmbeddablePackage = {
- createTools: () => {},
-} as unknown as ReduxEmbeddablePackage;
-
-describe('Time Slider Control Embeddable', () => {
- const services = pluginServices.getServices();
- const fetchRange = jest.spyOn(services.data, 'fetchFieldRange');
- const getDataView = jest.spyOn(services.data, 'getDataView');
- const fetchRange$ = jest.spyOn(services.data, 'fetchFieldRange$');
- const getDataView$ = jest.spyOn(services.data, 'getDataView$');
-
- beforeEach(() => {
- jest.resetAllMocks();
-
- fetchRange.mockResolvedValue(range);
- fetchRange$.mockReturnValue(of(range).pipe(delay(100)));
- getDataView.mockResolvedValue(stubLogstashDataView);
- getDataView$.mockReturnValue(of(stubLogstashDataView));
- });
-
- describe('outputting filters', () => {
- let testScheduler: TestScheduler;
- beforeEach(() => {
- testScheduler = new TestScheduler((actual, expected) => {
- expect(actual).toEqual(expected);
- });
- });
-
- const testFilterOutput = (
- input: any,
- expectedFilterAfterRangeFetch: any,
- mockRange: { min?: number; max?: number } = range
- ) => {
- testScheduler.run(({ expectObservable, cold }) => {
- fetchRange$.mockReturnValue(cold('--b', { b: mockRange }));
- const expectedMarbles = 'a-b';
- const expectedValues = {
- a: undefined,
- b: expectedFilterAfterRangeFetch ? [expectedFilterAfterRangeFetch] : undefined,
- };
-
- const embeddable = new TimeSliderControlEmbeddable(mockReduxEmbeddablePackage, input, {});
- const source$ = embeddable.getOutput$().pipe(map((o) => o.filters));
-
- expectObservable(source$).toBe(expectedMarbles, expectedValues);
- });
- };
-
- it('outputs no filter when no value is given', () => {
- testFilterOutput(baseInput, undefined);
- });
-
- it('outputs the value filter after the range is fetched', () => {
- testFilterOutput({ ...baseInput, value: withinRangeValue }, withinRangeValueFilter);
- });
-
- it('outputs a partial filter for a low partial value', () => {
- testFilterOutput({ ...baseInput, value: partialLowValue }, partialLowValueFilter);
- });
-
- it('outputs a partial filter for a high partial value', () => {
- testFilterOutput({ ...baseInput, value: partialHighValue }, partialHighValueFilter);
- });
-
- describe('with validation', () => {
- it('outputs a partial value filter if value is below range', () => {
- testFilterOutput({ ...baseInput, value: lowerValue }, lowerValuePartialFilter);
- });
-
- it('outputs a partial value filter if value is above range', () => {
- testFilterOutput({ ...baseInput, value: upperValue }, upperValuePartialFilter);
- });
-
- it('outputs range filter value if value is completely out of range', () => {
- testFilterOutput({ ...baseInput, value: outOfRangeValue }, rangeFilter);
- });
-
- it('outputs no filter when no range available', () => {
- testFilterOutput({ ...baseInput, value: withinRangeValue }, undefined, {});
- });
- });
-
- describe('with validation off', () => {
- it('outputs the lower value filter', () => {
- testFilterOutput(
- { ...baseInput, ignoreParentSettings: { ignoreValidations: true }, value: lowerValue },
- lowerValueFilter
- );
- });
-
- it('outputs the uppwer value filter', () => {
- testFilterOutput(
- { ...baseInput, ignoreParentSettings: { ignoreValidations: true }, value: upperValue },
- upperValueFilter
- );
- });
-
- it('outputs the out of range filter', () => {
- testFilterOutput(
- {
- ...baseInput,
- ignoreParentSettings: { ignoreValidations: true },
- value: outOfRangeValue,
- },
- outOfRangeValueFilter
- );
- });
-
- it('outputs the value filter when no range found', () => {
- testFilterOutput(
- {
- ...baseInput,
- ignoreParentSettings: { ignoreValidations: true },
- value: withinRangeValue,
- },
- withinRangeValueFilter,
- { min: undefined, max: undefined }
- );
- });
- });
- });
-
- describe('fetching range', () => {
- it('fetches range on init', () => {
- const testScheduler = new TestScheduler((actual, expected) => {
- expect(actual).toEqual(expected);
- });
-
- testScheduler.run(({ cold, expectObservable }) => {
- const mockRange = { min: 1, max: 2 };
- fetchRange$.mockReturnValue(cold('--b', { b: mockRange }));
-
- const expectedMarbles = 'a-b';
- const expectedValues = {
- a: undefined,
- b: mockRange,
- };
-
- const embeddable = new TimeSliderControlEmbeddable(
- mockReduxEmbeddablePackage,
- baseInput,
- {}
- );
- const source$ = embeddable.getComponentState$().pipe(map((state) => state.range));
-
- const { fieldName, ...inputForFetch } = baseInput;
-
- expectObservable(source$).toBe(expectedMarbles, expectedValues);
- expect(fetchRange$).toBeCalledWith(stubLogstashDataView, fieldName, {
- ...inputForFetch,
- filters: undefined,
- query: undefined,
- timeRange: undefined,
- viewMode: 'edit',
- });
- });
- });
-
- it('fetches range on input change', () => {
- const testScheduler = new TestScheduler((actual, expected) => {
- expect(actual).toEqual(expected);
- });
-
- testScheduler.run(({ cold, expectObservable, flush }) => {
- const mockRange = { min: 1, max: 2 };
- fetchRange$.mockReturnValue(cold('a', { a: mockRange }));
-
- const embeddable = new TimeSliderControlEmbeddable(
- mockReduxEmbeddablePackage,
- baseInput,
- {}
- );
- const updatedInput = { ...baseInput, fieldName: '@timestamp' };
-
- embeddable.updateInput(updatedInput);
-
- expect(fetchRange$).toBeCalledTimes(2);
- expect(fetchRange$.mock.calls[1][1]).toBe(updatedInput.fieldName);
- });
- });
-
- it('passes input to fetch range to build the query', () => {
- const testScheduler = new TestScheduler((actual, expected) => {
- expect(actual).toEqual(expected);
- });
-
- testScheduler.run(({ cold, expectObservable, flush }) => {
- const mockRange = { min: 1, max: 2 };
- fetchRange$.mockReturnValue(cold('a', { a: mockRange }));
-
- const input = {
- ...baseInput,
- query: {} as any,
- filters: {} as any,
- timeRange: {} as any,
- };
-
- new TimeSliderControlEmbeddable(mockReduxEmbeddablePackage, input, {});
-
- expect(fetchRange$).toBeCalledTimes(1);
- const args = fetchRange$.mock.calls[0][2];
- expect(args.query).toBe(input.query);
- expect(args.filters).toBe(input.filters);
- expect(args.timeRange).toBe(input.timeRange);
- });
- });
-
- it('does not pass ignored parent settings', () => {
- const testScheduler = new TestScheduler((actual, expected) => {
- expect(actual).toEqual(expected);
- });
-
- testScheduler.run(({ cold, expectObservable, flush }) => {
- const mockRange = { min: 1, max: 2 };
- fetchRange$.mockReturnValue(cold('a', { a: mockRange }));
-
- const input = {
- ...baseInput,
- query: '' as any,
- filters: {} as any,
- timeRange: {} as any,
- ignoreParentSettings: { ignoreFilters: true, ignoreQuery: true, ignoreTimerange: true },
- };
-
- new TimeSliderControlEmbeddable(mockReduxEmbeddablePackage, input, {});
-
- expect(fetchRange$).toBeCalledTimes(1);
- const args = fetchRange$.mock.calls[0][2];
- expect(args.query).not.toBe(input.query);
- expect(args.filters).not.toBe(input.filters);
- expect(args.timeRange).not.toBe(input.timeRange);
- });
- });
- });
-});
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable.tsx b/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable.tsx
deleted file mode 100644
index a4098a72dfe1ab7..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable.tsx
+++ /dev/null
@@ -1,334 +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 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 { compareFilters, buildRangeFilter, RangeFilterParams } from '@kbn/es-query';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { isEqual } from 'lodash';
-import deepEqual from 'fast-deep-equal';
-import { merge, Subscription, BehaviorSubject, Observable } from 'rxjs';
-import { map, distinctUntilChanged, skip, take, mergeMap } from 'rxjs/operators';
-
-import { Embeddable, IContainer } from '@kbn/embeddable-plugin/public';
-import { ReduxEmbeddableTools, ReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public';
-import { DataView } from '@kbn/data-views-plugin/public';
-
-import { TimeSliderControlEmbeddableInput } from '../../../common/control_types/time_slider/types';
-import { TIME_SLIDER_CONTROL } from '../..';
-import { ControlsSettingsService } from '../../services/settings';
-import { ControlsDataService } from '../../services/data';
-import { ControlOutput } from '../..';
-import { pluginServices } from '../../services';
-
-import { TimeSlider as TimeSliderComponent } from './time_slider';
-import { timeSliderReducers } from './time_slider_reducers';
-import { TimeSliderReduxState, TimeSliderSubjectState } from './types';
-
-const diffDataFetchProps = (current?: any, last?: any) => {
- if (!current || !last) return false;
- const { filters: currentFilters, ...currentWithoutFilters } = current;
- const { filters: lastFilters, ...lastWithoutFilters } = last;
- if (!deepEqual(currentWithoutFilters, lastWithoutFilters)) return false;
- if (!compareFilters(lastFilters ?? [], currentFilters ?? [])) return false;
- return true;
-};
-
-export class TimeSliderControlEmbeddable extends Embeddable<
- TimeSliderControlEmbeddableInput,
- ControlOutput
-> {
- public readonly type = TIME_SLIDER_CONTROL;
- public deferEmbeddableLoad = true;
-
- private subscriptions: Subscription = new Subscription();
- private node?: HTMLElement;
-
- // Internal data fetching state for this input control.
- private dataView?: DataView;
-
- private componentState: TimeSliderSubjectState;
- private componentStateSubject$ = new BehaviorSubject({
- range: undefined,
- loading: false,
- });
-
- // Internal state subject will let us batch updates to the externally accessible state subject
- private internalComponentStateSubject$ = new BehaviorSubject({
- range: undefined,
- loading: false,
- });
-
- private internalOutput: ControlOutput;
-
- private fetchRange$: ControlsDataService['fetchFieldRange$'];
- private getDataView$: ControlsDataService['getDataView$'];
- private getDateFormat: ControlsSettingsService['getDateFormat'];
- private getTimezone: ControlsSettingsService['getTimezone'];
-
- private reduxEmbeddableTools: ReduxEmbeddableTools<
- TimeSliderReduxState,
- typeof timeSliderReducers
- >;
-
- constructor(
- reduxEmbeddablePackage: ReduxEmbeddablePackage,
- input: TimeSliderControlEmbeddableInput,
- output: ControlOutput,
- parent?: IContainer
- ) {
- super(input, output, parent); // get filters for initial output...
-
- const {
- data: { fetchFieldRange$, getDataView$ },
- settings: { getDateFormat, getTimezone },
- } = pluginServices.getServices();
- this.fetchRange$ = fetchFieldRange$;
- this.getDataView$ = getDataView$;
- this.getDateFormat = getDateFormat;
- this.getTimezone = getTimezone;
-
- this.componentState = { loading: true };
- this.updateComponentState(this.componentState, true);
-
- this.internalOutput = {};
-
- // build redux embeddable tools
- this.reduxEmbeddableTools = reduxEmbeddablePackage.createTools<
- TimeSliderReduxState,
- typeof timeSliderReducers
- >({
- embeddable: this,
- reducers: timeSliderReducers,
- });
-
- this.initialize();
- }
-
- private initialize() {
- // If value is undefined, then we can be finished with initialization because we're not going to output a filter
- if (this.getInput().value === undefined) {
- this.setInitializationFinished();
- }
-
- this.setupSubscriptions();
- }
-
- private setupSubscriptions() {
- // We need to fetch data when any of these values change
- const dataFetchPipe = this.getInput$().pipe(
- map((newInput) => ({
- lastReloadRequestTime: newInput.lastReloadRequestTime,
- dataViewId: newInput.dataViewId,
- fieldName: newInput.fieldName,
- timeRange: newInput.timeRange,
- filters: newInput.filters,
- query: newInput.query,
- })),
- distinctUntilChanged(diffDataFetchProps)
- );
-
- // When data fetch pipe emits, we start the fetch
- this.subscriptions.add(dataFetchPipe.subscribe(this.fetchAvailableTimerange));
-
- const availableRangePipe = this.internalComponentStateSubject$.pipe(
- map((state) => (state.range ? { min: state.range.min, max: state.range.max } : {})),
- distinctUntilChanged((a, b) => isEqual(a, b))
- );
-
- this.subscriptions.add(
- merge(
- this.getInput$().pipe(
- skip(1), // Skip the first input value
- distinctUntilChanged((a, b) => isEqual(a.value, b.value))
- ),
- availableRangePipe.pipe(skip(1))
- ).subscribe(() => {
- this.setInitializationFinished();
- this.buildFilter();
-
- this.componentStateSubject$.next(this.componentState);
- })
- );
- }
-
- private buildFilter = () => {
- const { fieldName, value, ignoreParentSettings } = this.getInput();
-
- const min = value ? value[0] : null;
- const max = value ? value[1] : null;
- const hasRange =
- this.componentState.range?.max !== undefined && this.componentState.range?.min !== undefined;
-
- this.getCurrentDataView$().subscribe((dataView) => {
- const range: RangeFilterParams = {};
- let filterMin: number | undefined;
- let filterMax: number | undefined;
- const field = dataView.getFieldByName(fieldName);
-
- if (ignoreParentSettings?.ignoreValidations) {
- if (min !== null) {
- range.gte = min;
- }
-
- if (max !== null) {
- range.lte = max;
- }
- } else {
- // If we have a value or a range use the min/max of those, otherwise undefined
- if (min !== null && this.componentState.range!.min !== undefined) {
- filterMin = Math.max(min || 0, this.componentState.range!.min || 0);
- }
-
- if (max !== null && this.componentState.range!.max) {
- filterMax = Math.min(
- max || Number.MAX_SAFE_INTEGER,
- this.componentState.range!.max || Number.MAX_SAFE_INTEGER
- );
- }
-
- // Last check, if the value is completely outside the range then we will just default to the range
- if (
- hasRange &&
- ((min !== null && min > this.componentState.range!.max!) ||
- (max !== null && max < this.componentState.range!.min!))
- ) {
- filterMin = this.componentState.range!.min;
- filterMax = this.componentState.range!.max;
- }
-
- if (hasRange && filterMin !== undefined) {
- range.gte = filterMin;
- }
- if (hasRange && filterMax !== undefined) {
- range.lte = filterMax;
- }
- }
-
- if (range.lte !== undefined || range.gte !== undefined) {
- const rangeFilter = buildRangeFilter(field!, range, dataView);
- rangeFilter.meta.key = field?.name;
-
- this.updateInternalOutput({ filters: [rangeFilter] }, true);
- this.updateComponentState({ loading: false });
- } else {
- this.updateInternalOutput({ filters: undefined, dataViewId: dataView.id }, true);
- this.updateComponentState({ loading: false });
- }
- });
- };
-
- private updateComponentState(changes: Partial, publish = false) {
- this.componentState = {
- ...this.componentState,
- ...changes,
- };
-
- this.internalComponentStateSubject$.next(this.componentState);
-
- if (publish) {
- this.componentStateSubject$.next(this.componentState);
- }
- }
-
- private updateInternalOutput(changes: Partial, publish = false) {
- this.internalOutput = {
- ...this.internalOutput,
- ...changes,
- };
-
- if (publish) {
- this.updateOutput(this.internalOutput);
- }
- }
-
- private getCurrentDataView$ = () => {
- const { dataViewId } = this.getInput();
- if (this.dataView && this.dataView.id === dataViewId)
- return new Observable((subscriber) => {
- subscriber.next(this.dataView);
- subscriber.complete();
- });
-
- return this.getDataView$(dataViewId);
- };
-
- private fetchAvailableTimerange = () => {
- this.updateComponentState({ loading: true }, true);
- this.updateInternalOutput({ loading: true }, true);
-
- const { fieldName, ignoreParentSettings, query, filters, timeRange, ...input } =
- this.getInput();
-
- const inputForFetch = {
- ...input,
- ...(ignoreParentSettings?.ignoreQuery ? {} : { query }),
- ...(ignoreParentSettings?.ignoreFilters ? {} : { filters }),
- ...(ignoreParentSettings?.ignoreTimerange ? {} : { timeRange }),
- };
-
- try {
- this.getCurrentDataView$()
- .pipe(
- mergeMap((dataView) => this.fetchRange$(dataView, fieldName, inputForFetch)),
- take(1)
- )
- .subscribe(({ min, max }) => {
- this.updateInternalOutput({ loading: false });
- this.updateComponentState({
- range: {
- min: min === null ? undefined : min,
- max: max === null ? undefined : max,
- },
- loading: false,
- });
- });
- } catch (e) {
- this.updateComponentState({ loading: false }, true);
- this.updateInternalOutput({ loading: false }, true);
- }
- };
-
- public getComponentState$ = () => {
- return this.componentStateSubject$;
- };
-
- public destroy = () => {
- super.destroy();
- this.subscriptions.unsubscribe();
- };
-
- public reload = () => {
- this.fetchAvailableTimerange();
- };
-
- public render = (node: HTMLElement) => {
- if (this.node) {
- ReactDOM.unmountComponentAtNode(this.node);
- }
- this.node = node;
-
- const { Wrapper: TimeSliderControlReduxWrapper } = this.reduxEmbeddableTools;
-
- ReactDOM.render(
-
-
- ,
- node
- );
- };
-}
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable_factory.tsx b/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable_factory.tsx
deleted file mode 100644
index 51048489ccae9b7..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider_embeddable_factory.tsx
+++ /dev/null
@@ -1,69 +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 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 deepEqual from 'fast-deep-equal';
-
-import { lazyLoadReduxEmbeddablePackage } from '@kbn/presentation-util-plugin/public';
-import { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public';
-
-import { TIME_SLIDER_CONTROL } from '../..';
-import { ControlEmbeddable, DataControlField, IEditableControlFactory } from '../../types';
-import {
- createOptionsListExtract,
- createOptionsListInject,
-} from '../../../common/options_list/options_list_persistable_state';
-import { TimeSliderControlEmbeddableInput } from '../../../common/control_types/time_slider/types';
-import { TimeSliderStrings } from './time_slider_strings';
-
-export class TimesliderEmbeddableFactory
- implements EmbeddableFactoryDefinition, IEditableControlFactory
-{
- public type = TIME_SLIDER_CONTROL;
- public canCreateNew = () => false;
-
- constructor() {}
-
- public async create(initialInput: TimeSliderControlEmbeddableInput, parent?: IContainer) {
- const reduxEmbeddablePackage = await lazyLoadReduxEmbeddablePackage();
- const { TimeSliderControlEmbeddable } = await import('./time_slider_embeddable');
-
- return Promise.resolve(
- new TimeSliderControlEmbeddable(reduxEmbeddablePackage, initialInput, {}, parent)
- );
- }
-
- public presaveTransformFunction = (
- newInput: Partial,
- embeddable?: ControlEmbeddable
- ) => {
- if (
- embeddable &&
- ((newInput.fieldName && !deepEqual(newInput.fieldName, embeddable.getInput().fieldName)) ||
- (newInput.dataViewId && !deepEqual(newInput.dataViewId, embeddable.getInput().dataViewId)))
- ) {
- // if the field name or data view id has changed in this editing session, selected options are invalid, so reset them.
- newInput.value = undefined;
- }
- return newInput;
- };
-
- public isFieldCompatible = (dataControlField: DataControlField) => {
- if (dataControlField.field.type === 'date') {
- dataControlField.compatibleControlTypes.push(this.type);
- }
- };
-
- public isEditable = () => Promise.resolve(false);
-
- public getDisplayName = () => TimeSliderStrings.getDisplayName();
- public getIconType = () => 'clock';
- public getDescription = () => TimeSliderStrings.getDescription();
-
- public inject = createOptionsListInject();
- public extract = createOptionsListExtract();
-}
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider_reducers.ts b/src/plugins/controls/public/control_types/time_slider/time_slider_reducers.ts
deleted file mode 100644
index 95b8d87dc902e51..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider_reducers.ts
+++ /dev/null
@@ -1,20 +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 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 { PayloadAction } from '@reduxjs/toolkit';
-import { WritableDraft } from 'immer/dist/types/types-external';
-import { TimeSliderReduxState } from './types';
-
-export const timeSliderReducers = {
- selectRange: (
- state: WritableDraft,
- action: PayloadAction<[number | null, number | null]>
- ) => {
- state.explicitInput.value = action.payload;
- },
-};
diff --git a/src/plugins/controls/public/control_types/time_slider/time_slider_strings.ts b/src/plugins/controls/public/control_types/time_slider/time_slider_strings.ts
deleted file mode 100644
index 2c61d7d43a7976d..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/time_slider_strings.ts
+++ /dev/null
@@ -1,46 +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 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';
-
-export const TimeSliderStrings = {
- getDisplayName: () =>
- i18n.translate('controls.timeSlider.displayName', {
- defaultMessage: 'Time slider',
- }),
- getDescription: () =>
- i18n.translate('controls.timeSlider.description', {
- defaultMessage: 'Add a slider for selecting a time range',
- }),
- editor: {
- getDataViewTitle: () =>
- i18n.translate('controls.timeSlider.editor.dataViewTitle', {
- defaultMessage: 'Data view',
- }),
- getNoDataViewTitle: () =>
- i18n.translate('controls.timeSlider.editor.noDataViewTitle', {
- defaultMessage: 'Select data view',
- }),
- getFieldTitle: () =>
- i18n.translate('controls.timeSlider.editor.fieldTitle', {
- defaultMessage: 'Field',
- }),
- },
- resetButton: {
- getLabel: () =>
- i18n.translate('controls.timeSlider.resetButton.label', {
- defaultMessage: 'Reset selections',
- }),
- },
- noDocumentsPopover: {
- getLabel: () =>
- i18n.translate('controls.timeSlider.noDocuments.label', {
- defaultMessage: 'There were no documents found. Range selection unavailable.',
- }),
- },
-};
diff --git a/src/plugins/controls/public/control_types/time_slider/types.ts b/src/plugins/controls/public/control_types/time_slider/types.ts
deleted file mode 100644
index fc147dc3ba9596b..000000000000000
--- a/src/plugins/controls/public/control_types/time_slider/types.ts
+++ /dev/null
@@ -1,30 +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 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 { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public';
-
-import { ControlOutput } from '../../types';
-import { TimeSliderControlEmbeddableInput } from '../../../common/control_types/time_slider/types';
-
-export * from '../../../common/control_types/time_slider/types';
-
-// Component state is only used by public components.
-export interface TimeSliderSubjectState {
- range?: {
- min?: number;
- max?: number;
- };
- loading: boolean;
-}
-
-// public only - redux embeddable state type
-export type TimeSliderReduxState = ReduxEmbeddableState<
- TimeSliderControlEmbeddableInput,
- ControlOutput,
- TimeSliderSubjectState
->;
diff --git a/src/plugins/controls/public/index.ts b/src/plugins/controls/public/index.ts
index f55df5fa0f53a89..ecf430f6cc9277b 100644
--- a/src/plugins/controls/public/index.ts
+++ b/src/plugins/controls/public/index.ts
@@ -24,12 +24,7 @@ export type {
ControlInput,
} from '../common/types';
-export {
- CONTROL_GROUP_TYPE,
- OPTIONS_LIST_CONTROL,
- RANGE_SLIDER_CONTROL,
- TIME_SLIDER_CONTROL,
-} from '../common';
+export { CONTROL_GROUP_TYPE, OPTIONS_LIST_CONTROL, RANGE_SLIDER_CONTROL } from '../common';
export {
ControlGroupContainer,
diff --git a/src/plugins/controls/public/plugin.ts b/src/plugins/controls/public/plugin.ts
index da45ba2e68684d0..01375b174a93496 100644
--- a/src/plugins/controls/public/plugin.ts
+++ b/src/plugins/controls/public/plugin.ts
@@ -14,7 +14,6 @@ import {
CONTROL_GROUP_TYPE,
OPTIONS_LIST_CONTROL,
RANGE_SLIDER_CONTROL,
- // TIME_SLIDER_CONTROL,
} from '.';
import { OptionsListEmbeddableFactory, OptionsListEmbeddableInput } from './options_list';
import { RangeSliderEmbeddableFactory, RangeSliderEmbeddableInput } from './range_slider';
@@ -29,13 +28,6 @@ import {
ControlInput,
} from './types';
-/*
-import {
- TimesliderEmbeddableFactory,
- TimeSliderControlEmbeddableInput,
-} from './control_types/time_slider';
-*/
-
export class ControlsPlugin
implements
Plugin<
@@ -101,22 +93,6 @@ export class ControlsPlugin
rangeSliderFactory
);
registerControlType(rangeSliderFactory);
-
- // Time Slider Control Factory Setup
- /* Temporary disabling Time Slider
- const timeSliderFactoryDef = new TimesliderEmbeddableFactory();
- const timeSliderFactory = embeddable.registerEmbeddableFactory(
- TIME_SLIDER_CONTROL,
- timeSliderFactoryDef
- )();
- this.transferEditorFunctions(
- timeSliderFactoryDef,
- timeSliderFactory
- );
-
-
- registerControlType(timeSliderFactory);
- */
});
return {
diff --git a/src/plugins/controls/server/control_group/control_group_telemetry.test.ts b/src/plugins/controls/server/control_group/control_group_telemetry.test.ts
index 140b58fd790fca0..1e66bb15fbc2bd4 100644
--- a/src/plugins/controls/server/control_group/control_group_telemetry.test.ts
+++ b/src/plugins/controls/server/control_group/control_group_telemetry.test.ts
@@ -24,7 +24,7 @@ const rawControlAttributes2: RawControlGroupAttributes = {
controlStyle: 'oneLine',
chainingSystem: 'NONE',
panelsJSON:
- '{"9cf90205-e94d-43c9-a3aa-45f359a7522f":{"order":0,"width":"auto","type":"rangeSliderControl","explicitInput":{"title":"DistanceKilometers","fieldName":"DistanceKilometers","id":"9cf90205-e94d-43c9-a3aa-45f359a7522f","enhancements":{}}},"b47916fd-fc03-4dcd-bef1-5c3b7a315723":{"order":1,"width":"auto","type":"timeSlider","explicitInput":{"title":"timestamp","fieldName":"timestamp","id":"b47916fd-fc03-4dcd-bef1-5c3b7a315723","enhancements":{}}},"f6b076c6-9ef5-483e-b08d-d313d60d4b8c":{"order":2,"width":"auto","type":"rangeSliderControl","explicitInput":{"title":"DistanceMiles","fieldName":"DistanceMiles","id":"f6b076c6-9ef5-483e-b08d-d313d60d4b8c","enhancements":{}}}}',
+ '{"9cf90205-e94d-43c9-a3aa-45f359a7522f":{"order":0,"width":"auto","type":"rangeSliderControl","explicitInput":{"title":"DistanceKilometers","fieldName":"DistanceKilometers","id":"9cf90205-e94d-43c9-a3aa-45f359a7522f","enhancements":{}}},"f6b076c6-9ef5-483e-b08d-d313d60d4b8c":{"order":2,"width":"auto","type":"rangeSliderControl","explicitInput":{"title":"DistanceMiles","fieldName":"DistanceMiles","id":"f6b076c6-9ef5-483e-b08d-d313d60d4b8c","enhancements":{}}}}',
ignoreParentSettingsJSON:
'{"ignoreFilters":true,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}',
};
@@ -34,7 +34,7 @@ const rawControlAttributes3: RawControlGroupAttributes = {
controlStyle: 'oneLine',
chainingSystem: 'HIERARCHICAL',
panelsJSON:
- '{"9cf90205-e94d-43c9-a3aa-45f359a7522f":{"order":0,"width":"auto","type":"rangeSliderControl","explicitInput":{"title":"DistanceKilometers","fieldName":"DistanceKilometers","id":"9cf90205-e94d-43c9-a3aa-45f359a7522f","enhancements":{}}},"b47916fd-fc03-4dcd-bef1-5c3b7a315723":{"order":1,"width":"auto","type":"timeSlider","explicitInput":{"title":"timestamp","fieldName":"timestamp","id":"b47916fd-fc03-4dcd-bef1-5c3b7a315723","enhancements":{}}},"ee325e9e-6ec1-41f9-953f-423d59850d44":{"order":2,"width":"auto","type":"optionsListControl","explicitInput":{"title":"Carrier","fieldName":"Carrier","id":"ee325e9e-6ec1-41f9-953f-423d59850d44","enhancements":{}}},"cb0f5fcd-9ad9-4d4a-b489-b75bd060399b":{"order":3,"width":"auto","type":"optionsListControl","explicitInput":{"title":"DestCityName","fieldName":"DestCityName","id":"cb0f5fcd-9ad9-4d4a-b489-b75bd060399b","enhancements":{}}}}',
+ '{"9cf90205-e94d-43c9-a3aa-45f359a7522f":{"order":0,"width":"auto","type":"rangeSliderControl","explicitInput":{"title":"DistanceKilometers","fieldName":"DistanceKilometers","id":"9cf90205-e94d-43c9-a3aa-45f359a7522f","enhancements":{}}},"ee325e9e-6ec1-41f9-953f-423d59850d44":{"order":2,"width":"auto","type":"optionsListControl","explicitInput":{"title":"Carrier","fieldName":"Carrier","id":"ee325e9e-6ec1-41f9-953f-423d59850d44","enhancements":{}}},"cb0f5fcd-9ad9-4d4a-b489-b75bd060399b":{"order":3,"width":"auto","type":"optionsListControl","explicitInput":{"title":"DestCityName","fieldName":"DestCityName","id":"cb0f5fcd-9ad9-4d4a-b489-b75bd060399b","enhancements":{}}}}',
ignoreParentSettingsJSON:
'{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}',
};
@@ -97,7 +97,7 @@ describe('Control group telemetry function', () => {
});
test('counts all telemetry over multiple runs', () => {
- expect(finalTelemetry.total).toBe(10);
+ expect(finalTelemetry.total).toBe(8);
});
test('counts control types over multiple runs.', () => {
@@ -110,10 +110,6 @@ describe('Control group telemetry function', () => {
details: {},
total: 3,
},
- timeSlider: {
- details: {},
- total: 2,
- },
});
});
diff --git a/src/plugins/controls/server/control_types/time_slider/time_slider_embeddable_factory.ts b/src/plugins/controls/server/control_types/time_slider/time_slider_embeddable_factory.ts
deleted file mode 100644
index 8e8920f12f5545e..000000000000000
--- a/src/plugins/controls/server/control_types/time_slider/time_slider_embeddable_factory.ts
+++ /dev/null
@@ -1,22 +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 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 { EmbeddableRegistryDefinition } from '@kbn/embeddable-plugin/server';
-import { TIME_SLIDER_CONTROL } from '../../../common';
-import {
- createTimeSliderExtract,
- createTimeSliderInject,
-} from '../../../common/control_types/time_slider/time_slider_persistable_state';
-
-export const timeSliderPersistableStateServiceFactory = (): EmbeddableRegistryDefinition => {
- return {
- id: TIME_SLIDER_CONTROL,
- extract: createTimeSliderExtract(),
- inject: createTimeSliderInject(),
- };
-};
diff --git a/src/plugins/controls/server/plugin.ts b/src/plugins/controls/server/plugin.ts
index 00d968881579631..019430166ff6fa9 100644
--- a/src/plugins/controls/server/plugin.ts
+++ b/src/plugins/controls/server/plugin.ts
@@ -14,7 +14,6 @@ import { setupOptionsListSuggestionsRoute } from './options_list/options_list_su
import { controlGroupContainerPersistableStateServiceFactory } from './control_group/control_group_container_factory';
import { optionsListPersistableStateServiceFactory } from './options_list/options_list_embeddable_factory';
import { rangeSliderPersistableStateServiceFactory } from './range_slider/range_slider_embeddable_factory';
-// import { timeSliderPersistableStateServiceFactory } from './control_types/time_slider/time_slider_embeddable_factory';
interface SetupDeps {
embeddable: EmbeddableSetup;
@@ -24,14 +23,11 @@ interface SetupDeps {
export class ControlsPlugin implements Plugin
+ }
+ stepKey="step1"
+ />
+ ),
+};
+const MONITOR_DETAILS_STEP: Step = {
+ title: i18n.translate('xpack.synthetics.monitorConfig.monitorDetailsStep.title', {
+ defaultMessage: 'Monitor details',
+ }),
+ children: (
+
+ {i18n.translate('xpack.synthetics.monitorConfig.monitorDetailsStep.description', {
+ defaultMessage: 'Provide some details about how your monitor should run',
+ })}
+
+ }
+ stepKey="step2"
+ />
+ ),
+};
+
+const SCRIPT_RECORDER_BTNS = (
+
+
+
+ {i18n.translate('xpack.synthetics.monitorConfig.monitorScriptStep.scriptRecorder.launch', {
+ defaultMessage: 'Launch Synthetics Recorder',
+ })}
+
+
+
+
+ {i18n.translate(
+ 'xpack.synthetics.monitorConfig.monitorScriptStep.scriptRecorder.download',
+ {
+ defaultMessage: 'Download Synthetics Recorder',
+ }
+ )}
+
+
+
+);
+
+const MONITOR_SCRIPT_STEP: Step = {
+ title: i18n.translate('xpack.synthetics.monitorConfig.monitorScriptStep.title', {
+ defaultMessage: 'Add a script',
+ }),
+ children: (
+
+
+
+
+
+ ),
+ }}
+ />
+
+ {SCRIPT_RECORDER_BTNS}
+ >
+ }
+ stepKey="step3"
+ />
+ ),
+};
+
+const MONITOR_SCRIPT_STEP_EDIT: Step = {
+ title: i18n.translate('xpack.synthetics.monitorConfig.monitorScriptEditStep.title', {
+ defaultMessage: 'Monitor script',
+ }),
+ children: (
+
+
+
+
+
+ ),
+ }}
+ />
+
+ {SCRIPT_RECORDER_BTNS}
+ >
+ }
+ stepKey="scriptEdit"
+ />
+ ),
+};
+
+export const ADD_MONITOR_STEPS: StepMap = {
+ [FormMonitorType.MULTISTEP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP, MONITOR_SCRIPT_STEP],
+ [FormMonitorType.SINGLE]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP],
+ [FormMonitorType.HTTP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP],
+ [FormMonitorType.ICMP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP],
+ [FormMonitorType.TCP]: [MONITOR_TYPE_STEP, MONITOR_DETAILS_STEP],
+};
+
+export const EDIT_MONITOR_STEPS: StepMap = {
+ [FormMonitorType.MULTISTEP]: [MONITOR_SCRIPT_STEP_EDIT, MONITOR_DETAILS_STEP],
+ [FormMonitorType.SINGLE]: [MONITOR_DETAILS_STEP],
+ [FormMonitorType.HTTP]: [MONITOR_DETAILS_STEP],
+ [FormMonitorType.ICMP]: [MONITOR_DETAILS_STEP],
+ [FormMonitorType.TCP]: [MONITOR_DETAILS_STEP],
+};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx
new file mode 100644
index 000000000000000..2ac95831859131b
--- /dev/null
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/steps/step_fields.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { useFormContext, FieldError } from 'react-hook-form';
+import { Step } from './step';
+import { FORM_CONFIG } from '../form/form_config';
+import { Field } from '../form/field';
+import { ConfigKey, FormMonitorType, StepKey } from '../types';
+
+export const StepFields = ({
+ description,
+ stepKey,
+}: {
+ description: React.ReactNode;
+ stepKey: StepKey;
+}) => {
+ const {
+ watch,
+ formState: { errors },
+ } = useFormContext();
+ const [type]: [FormMonitorType] = watch([ConfigKey.FORM_MONITOR_TYPE]);
+
+ return (
+
+ {FORM_CONFIG[type][stepKey]?.map((field) => {
+ return (
+
+ );
+ })}
+
+ );
+};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.ts
new file mode 100644
index 000000000000000..e82dc2fa1fd9788
--- /dev/null
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/types.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 React from 'react';
+import {
+ UseFormReturn,
+ ControllerRenderProps,
+ ControllerFieldState,
+ FormState,
+} from 'react-hook-form';
+import {
+ ServiceLocations,
+ FormMonitorType,
+ SyntheticsMonitor,
+} from '../../../../../common/runtime_types/monitor_management';
+
+export type StepKey = 'step1' | 'step2' | 'step3' | 'scriptEdit';
+
+export interface Step {
+ title: string;
+ children: React.ReactNode;
+}
+
+export type StepMap = Record;
+
+export * from '../../../../../common/runtime_types/monitor_management';
+export * from '../../../../../common/types/monitor_validation';
+
+export interface FieldMeta {
+ fieldKey: string;
+ component: React.ComponentType;
+ label?: string;
+ ariaLabel?: string;
+ helpText?: string | React.ReactNode;
+ props?: (params: {
+ field?: ControllerRenderProps;
+ formState: FormState;
+ setValue: UseFormReturn['setValue'];
+ reset: UseFormReturn['reset'];
+ locations: ServiceLocations;
+ dependencies: unknown[];
+ dependenciesFieldMeta: Record;
+ space?: string;
+ isEdit?: boolean;
+ }) => Record;
+ controlled?: boolean;
+ required?: boolean;
+ shouldUseSetValue?: boolean;
+ customHook?: (value: unknown) => {
+ // custom hooks are only supported for controlled components and only supported for determining error validation
+ func: Function;
+ params: unknown;
+ fieldKey: string;
+ error: string;
+ };
+ onChange?: (
+ event: React.ChangeEvent,
+ formOnChange: (event: React.ChangeEvent) => void
+ ) => void;
+ showWhen?: [string, any]; // show field when another field equals an arbitrary value
+ validation?: (dependencies: unknown[]) => Parameters[1];
+ error?: React.ReactNode;
+ dependencies?: string[]; // fields that another field may depend for or validation. Values are passed to the validation function
+}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/use_breadcrumbs.ts b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/use_breadcrumbs.ts
index 2fb17340c25fcf6..39d69bf64dea8db 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/use_breadcrumbs.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/use_breadcrumbs.ts
@@ -7,19 +7,24 @@
import { i18n } from '@kbn/i18n';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
-import { MONITOR_ADD_ROUTE } from '../../../../../common/constants';
+import { MONITOR_ADD_ROUTE, MONITOR_EDIT_ROUTE } from '../../../../../common/constants';
import { PLUGIN } from '../../../../../common/constants/plugin';
-export const useMonitorAddEditBreadcrumbs = () => {
+export const useMonitorAddEditBreadcrumbs = (isEdit?: boolean) => {
const kibana = useKibana();
const appPath = kibana.services.application?.getUrlForApp(PLUGIN.SYNTHETICS_PLUGIN_ID) ?? '';
- useBreadcrumbs([
- {
- text: ADD_MONITOR_CRUMB,
- href: `${appPath}/${MONITOR_ADD_ROUTE}`,
- },
- ]);
+ const config = isEdit
+ ? {
+ text: EDIT_MONITOR_CRUMB,
+ href: `${appPath}/${MONITOR_EDIT_ROUTE}`,
+ }
+ : {
+ text: ADD_MONITOR_CRUMB,
+ href: `${appPath}/${MONITOR_ADD_ROUTE}`,
+ };
+
+ useBreadcrumbs([config]);
};
export const ADD_MONITOR_CRUMB = i18n.translate(
@@ -28,3 +33,10 @@ export const ADD_MONITOR_CRUMB = i18n.translate(
defaultMessage: 'Add monitor',
}
);
+
+export const EDIT_MONITOR_CRUMB = i18n.translate(
+ 'xpack.synthetics.monitorManagement.editMonitorCrumb',
+ {
+ defaultMessage: 'Edit monitor',
+ }
+);
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx
index aad917bccdee296..aeb5010f3496e9e 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/monitor_list_table/actions.tsx
@@ -98,6 +98,7 @@ export const Actions = ({ euiTheme, id, name, reloadPage, canEditSynthetics }: P
iconType="boxesHorizontal"
color="primary"
iconSide="right"
+ data-test-subj="syntheticsMonitorListActions"
onClick={openPopover}
/>
);
@@ -139,7 +140,7 @@ export const Actions = ({ euiTheme, id, name, reloadPage, canEditSynthetics }: P
key="xpack.synthetics.editMonitor"
icon="pencil"
onClick={closePopover}
- href={`${basePath}/app/uptime/edit-monitor/${id}`}
+ href={`${basePath}/app/synthetics/edit-monitor/${id}`}
disabled={!canEditSynthetics}
>
{labels.EDIT_LABEL}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/page_header/monitors_page_header.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/page_header/monitors_page_header.tsx
index 7ac40ae361ae6fd..763965b0af17afe 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/page_header/monitors_page_header.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/management/page_header/monitors_page_header.tsx
@@ -42,7 +42,7 @@ export const MonitorsPageHeader = () => {
fill
iconSide="left"
iconType="plusInCircleFilled"
- href={`${basePath}/app/uptime${MONITOR_ADD_ROUTE}`}
+ href={`${basePath}/app/synthetics${MONITOR_ADD_ROUTE}`}
isDisabled={!isEnabled}
data-test-subj="syntheticsAddMonitorBtn"
>
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx
index 8112e9f19d98206..34e3b23f48ca687 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx
@@ -7,25 +7,38 @@
import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types';
import React, { FC, useEffect } from 'react';
-import { EuiPageTemplateProps, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui';
+import {
+ EuiPageTemplateProps,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiLink,
+ useEuiTheme,
+} from '@elastic/eui';
import { Route, Switch, useHistory } from 'react-router-dom';
+import { OutPortal } from 'react-reverse-portal';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { APP_WRAPPER_CLASS } from '@kbn/core/public';
import { useInspectorContext } from '@kbn/observability-plugin/public';
+import { MonitorAddPage } from './components/monitor_add_edit/monitor_add_page';
+import { MonitorEditPage } from './components/monitor_add_edit/monitor_edit_page';
import { RunTestManually } from './components/monitor_summary/run_test_manually';
import { MonitorSummaryHeaderContent } from './components/monitor_summary/monitor_summary_header_content';
import { MonitorSummaryTitle } from './components/monitor_summary/monitor_summary_title';
import { MonitorSummaryPage } from './components/monitor_summary/monitor_summary';
import { GettingStartedPage } from './components/getting_started/getting_started_page';
-import { MonitorAddEditPage } from './components/monitor_add_edit/monitor_add_edit_page';
import { MonitorsPageHeader } from './components/monitors_page/management/page_header/monitors_page_header';
import { OverviewPage } from './components/monitors_page/overview/overview_page';
import { SyntheticsPageTemplateComponent } from './components/common/pages/synthetics_page_template';
import { NotFoundPage } from './components/common/pages/not_found';
import { ServiceAllowedWrapper } from './components/common/wrappers/service_allowed_wrapper';
+import {
+ MonitorTypePortalNode,
+ MonitorDetailsLinkPortalNode,
+} from './components/monitor_add_edit/portals';
import {
MONITOR_ADD_ROUTE,
+ MONITOR_EDIT_ROUTE,
MONITORS_ROUTE,
OVERVIEW_ROUTE,
GETTING_STARTED_ROUTE,
@@ -185,27 +198,68 @@ const getRoutes = (
},
},
{
- title: i18n.translate('xpack.synthetics.addMonitorRoute.title', {
- defaultMessage: 'Add Monitor | {baseTitle}',
+ title: i18n.translate('xpack.synthetics.createMonitorRoute.title', {
+ defaultMessage: 'Create Monitor | {baseTitle}',
values: { baseTitle },
}),
path: MONITOR_ADD_ROUTE,
component: () => (
-
+
),
dataTestSubj: 'syntheticsMonitorAddPage',
pageHeader: {
pageTitle: (
+ ),
+ children: (
+