From d536f85005c22af5a5fc83e8393e8caf5965dbad Mon Sep 17 00:00:00 2001 From: Davis McPhee Date: Fri, 2 May 2025 13:39:25 -0300 Subject: [PATCH] [Discover] Support Lens fetches across tabs (#218506) ## Summary This PR implements support for Lens chart fetches across Discover tabs, and restoring chart state when returning to a tab. The Lens embeddable does not currently support continuing data fetching after it's been unmounted, or fully support restoring chart state using previously fetched data. The Vis team is aware of this request, but in the meantime we're using an alternative approach that keeps Lens charts rendered in memory for each tab that's been visited at least once. This allows fetches to run in the background and displays the resulting chart when switching back to tabs. Doing this involved some refactoring to both Discover and Unified Histogram: - Create a `ChartPortalsRenderer` wrapper component in Discover to lift chart rendering high up in the React tree and render charts into [reverse portals](https://github.com/httptoolkit/react-reverse-portal), ensuring charts are not remounted when switching tabs and continue to be rendered after switching away from a tab. - Refactor Unified Histogram from a single "container" component into three pieces: a `UnifiedHistogramLayout` component, a `UnifiedHistogramChart` component, and a `useUnifiedHistogram` hook to tie things together. This approach allows us to render the chart and hook separately (in a reverse portal) from the layout, making it possible to separate the lifecycle of both components without keeping the rest of Discover's components in memory after switching tabs. - **Important note:** This change had the side effect of bloating the Unified Histogram page load bundle since we're now exporting a static hook that isn't dynamically imported. We could have worked around this by getting creative with dynamic imports, but doing that seemed hacky. Instead I decided now was a good time to split Unified Histogram out into a package in order to support tree shaking, which also has the added benefits of one fewer plugins to load on startup, and simplifying the Discover async bundles. There is one flaw with this approach: the `useDiscoverHistogram` hook currently depends on global services for retrieving the current query and filters (including global filters) through the `useQuerySubscriber` hook. This means the values can become out of sync in inactive tabs when the user modifies them in the current tab. In practice this doesn't seem to have an effect in most cases since we don't trigger new fetches in inactive tabs, and sync the the current tab values to the global services when switching back to a tab. However, we should still fix this for the MVP with an approach that doesn't leak state since the current approach is brittle. I avoided doing that in this PR since it would likely cause more conflicts with #217706, and that PR may introduce a solution to the issue anyway (syncing global state to the redux store, which we can rely on in the hook instead). Resolves #216475. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- .i18nrc.json | 2 +- docs/extend/plugin-list.md | 1 - package.json | 2 +- packages/kbn-optimizer/limits.yml | 1 - .../src/panels_resizable.tsx | 6 +- .../src/panels_static.tsx | 6 +- .../src/resizable_layout.tsx | 6 +- .../fixtures/test/performance/README.md | 1 - .../shared/kbn-unified-histogram/README.md | 3 + .../__mocks__/data_view.ts | 0 .../__mocks__/data_view_with_timefield.ts | 0 .../__mocks__/lens_adapters.ts | 0 .../__mocks__/lens_vis.ts | 0 .../__mocks__/services.tsx | 0 .../__mocks__/suggestions.ts | 0 .../kbn-unified-histogram}/__mocks__/table.ts | 0 .../chart/breakdown_field_selector.test.tsx | 4 +- .../chart/breakdown_field_selector.tsx | 2 +- .../components}/chart/chart.test.tsx | 29 +- .../components}/chart/chart.tsx | 49 +-- .../chart/chart_config_panel.test.tsx | 12 +- .../components}/chart/chart_config_panel.tsx | 6 +- .../components}/chart/histogram.test.tsx | 8 +- .../components}/chart/histogram.tsx | 4 +- .../chart/hooks/use_chart_actions.test.ts | 2 +- .../chart/hooks/use_chart_actions.ts | 2 +- .../chart/hooks/use_chart_styles.tsx | 0 .../hooks/use_edit_visualization.test.ts | 6 +- .../chart/hooks/use_edit_visualization.ts | 2 +- .../components}/chart/hooks/use_fetch.test.ts | 2 +- .../components}/chart/hooks/use_fetch.ts | 2 +- .../chart/hooks/use_lens_props.test.ts | 6 +- .../components}/chart/hooks/use_lens_props.ts | 4 +- .../chart/hooks/use_time_range.test.tsx | 2 +- .../chart/hooks/use_time_range.tsx | 2 +- .../chart/hooks/use_total_hits.test.ts | 4 +- .../components}/chart/hooks/use_total_hits.ts | 4 +- .../components/chart/index.ts} | 13 +- .../components}/chart/lazy.tsx | 0 .../chart/time_interval_selector.test.tsx | 0 .../chart/time_interval_selector.tsx | 2 +- .../components}/chart/toolbar_selector.tsx | 0 .../chart/utils/build_bucket_interval.test.ts | 2 +- .../chart/utils/build_bucket_interval.ts | 2 +- .../chart/utils}/check_chart_availability.ts | 2 +- .../chart/utils/get_chart_agg_config.test.ts | 2 +- .../chart/utils/get_chart_agg_configs.ts | 0 .../components/layout}/index.ts | 3 +- .../components}/layout/layout.test.tsx | 114 ++--- .../components/layout/layout.tsx | 116 ++++++ .../hooks/use_request_params.test.ts | 0 .../hooks/use_request_params.tsx | 0 .../hooks/use_stable_callback.test.ts | 0 .../hooks/use_stable_callback.ts | 0 .../hooks/use_state_props.test.ts | 28 +- .../hooks/use_state_props.ts | 36 +- .../hooks}/use_state_selector.ts | 0 .../hooks/use_unified_histogram.test.tsx | 68 +++ .../hooks/use_unified_histogram.ts | 324 ++++++++++++++ .../shared/kbn-unified-histogram}/index.ts | 48 ++- .../kbn-unified-histogram}/jest.config.js | 8 +- .../shared/kbn-unified-histogram/kibana.jsonc | 8 + .../shared/kbn-unified-histogram}/mocks.ts | 2 +- .../shared/kbn-unified-histogram/package.json | 6 + .../lens_vis_service.attributes.test.ts | 0 .../lens_vis_service.suggestions.test.ts | 0 .../services/lens_vis_service.ts | 7 +- .../services/state_service.test.ts | 6 +- .../services/state_service.ts | 6 +- .../kbn-unified-histogram}/tsconfig.json | 55 ++- .../shared/kbn-unified-histogram}/types.ts | 0 .../external_vis_context.test.ts.snap | 0 .../utils/external_vis_context.test.ts | 0 .../utils/external_vis_context.ts | 0 .../utils/lens_vis_from_table.ts | 0 .../utils/local_storage_utils.test.ts | 0 .../utils/local_storage_utils.ts | 2 +- .../utils/state_selectors.ts | 0 .../plugins/shared/discover/kibana.jsonc | 1 - .../public/__mocks__/discover_state.mock.ts | 11 + .../discover/public/__mocks__/services.ts | 3 + .../chart/chart_portals_renderer.tsx | 169 ++++++++ .../main/components/chart}/index.ts | 3 +- .../use_discover_histogram.test.tsx | 84 ++-- .../use_discover_histogram.ts | 121 +++--- .../layout/discover_histogram_layout.test.tsx | 37 +- .../layout/discover_histogram_layout.tsx | 59 +-- .../layout/discover_layout.test.tsx | 23 +- .../components/layout/discover_layout.tsx | 2 +- .../main/components/tabs_view/tabs_view.tsx | 12 +- .../application/main/discover_main_route.tsx | 14 +- .../discover_saved_search_container.ts | 4 +- .../redux/actions/initialize_session.ts | 27 +- .../main/state_management/redux/hooks.tsx | 18 +- .../main/state_management/redux/index.ts | 1 + .../state_management/redux/internal_state.ts | 7 +- .../state_management/redux/runtime_state.tsx | 5 + .../main/state_management/redux/selectors.ts | 10 +- .../main/state_management/redux/types.ts | 2 +- .../utils/get_state_defaults.ts | 2 +- .../histogram_customization.tsx | 4 +- .../plugins/shared/discover/tsconfig.json | 4 +- .../shared/unified_histogram/README.md | 147 ------- .../shared/unified_histogram/kibana.jsonc | 21 - .../public/container/container.test.tsx | 78 ---- .../public/container/container.tsx | 196 --------- .../public/container/index.tsx | 52 --- .../public/layout/layout.tsx | 394 ------------------ tsconfig.base.json | 4 +- .../ui_tests/tests/discover_cdp_perf.spec.ts | 5 - .../shared/dataset_quality/kibana.jsonc | 1 - .../overview/document_trends/index.tsx | 2 +- .../shared/dataset_quality/tsconfig.json | 4 +- .../use_histogram_customizations.tsx | 6 +- .../plugins/security_solution/tsconfig.json | 2 +- yarn.lock | 2 +- 117 files changed, 1197 insertions(+), 1382 deletions(-) create mode 100644 src/platform/packages/shared/kbn-unified-histogram/README.md rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/data_view.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/data_view_with_timefield.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/lens_adapters.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/lens_vis.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/services.tsx (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/suggestions.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/__mocks__/table.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/breakdown_field_selector.test.tsx (97%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/breakdown_field_selector.tsx (98%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/chart.test.tsx (93%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/chart.tsx (93%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/chart_config_panel.test.tsx (87%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/chart_config_panel.tsx (96%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/histogram.test.tsx (97%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/histogram.tsx (97%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_chart_actions.test.ts (96%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_chart_actions.ts (94%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_chart_styles.tsx (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_edit_visualization.test.ts (95%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_edit_visualization.ts (97%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_fetch.test.ts (95%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_fetch.ts (93%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_lens_props.test.ts (95%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_lens_props.ts (96%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_time_range.test.tsx (98%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_time_range.tsx (98%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_total_hits.test.ts (98%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/hooks/use_total_hits.ts (98%) rename src/platform/{plugins/shared/unified_histogram/public/plugin.ts => packages/shared/kbn-unified-histogram/components/chart/index.ts} (69%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/lazy.tsx (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/time_interval_selector.test.tsx (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/time_interval_selector.tsx (97%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/toolbar_selector.tsx (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/utils/build_bucket_interval.test.ts (97%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/utils/build_bucket_interval.ts (95%) rename src/platform/{plugins/shared/unified_histogram/public/chart => packages/shared/kbn-unified-histogram/components/chart/utils}/check_chart_availability.ts (93%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/utils/get_chart_agg_config.test.ts (95%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/chart/utils/get_chart_agg_configs.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public/chart => packages/shared/kbn-unified-histogram/components/layout}/index.ts (82%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram/components}/layout/layout.test.tsx (64%) create mode 100644 src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.tsx rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/hooks/use_request_params.test.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/hooks/use_request_params.tsx (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/hooks/use_stable_callback.test.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/hooks/use_stable_callback.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/hooks/use_state_props.test.ts (95%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/hooks/use_state_props.ts (84%) rename src/platform/{plugins/shared/unified_histogram/public/container/utils => packages/shared/kbn-unified-histogram/hooks}/use_state_selector.ts (100%) create mode 100644 src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.test.tsx create mode 100644 src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.ts rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/index.ts (61%) rename src/platform/{plugins/shared/unified_histogram => packages/shared/kbn-unified-histogram}/jest.config.js (60%) create mode 100644 src/platform/packages/shared/kbn-unified-histogram/kibana.jsonc rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/mocks.ts (92%) create mode 100644 src/platform/packages/shared/kbn-unified-histogram/package.json rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/services/lens_vis_service.attributes.test.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/services/lens_vis_service.suggestions.test.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/services/lens_vis_service.ts (99%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/services/state_service.test.ts (97%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/services/state_service.ts (97%) rename src/platform/{plugins/shared/unified_histogram => packages/shared/kbn-unified-histogram}/tsconfig.json (84%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/types.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/utils/__snapshots__/external_vis_context.test.ts.snap (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/utils/external_vis_context.test.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/utils/external_vis_context.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public => packages/shared/kbn-unified-histogram}/utils/lens_vis_from_table.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/utils/local_storage_utils.test.ts (100%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/utils/local_storage_utils.ts (97%) rename src/platform/{plugins/shared/unified_histogram/public/container => packages/shared/kbn-unified-histogram}/utils/state_selectors.ts (100%) create mode 100644 src/platform/plugins/shared/discover/public/application/main/components/chart/chart_portals_renderer.tsx rename src/platform/plugins/shared/{unified_histogram/public/layout => discover/public/application/main/components/chart}/index.ts (80%) rename src/platform/plugins/shared/discover/public/application/main/components/{layout => chart}/use_discover_histogram.test.tsx (87%) rename src/platform/plugins/shared/discover/public/application/main/components/{layout => chart}/use_discover_histogram.ts (86%) delete mode 100755 src/platform/plugins/shared/unified_histogram/README.md delete mode 100644 src/platform/plugins/shared/unified_histogram/kibana.jsonc delete mode 100644 src/platform/plugins/shared/unified_histogram/public/container/container.test.tsx delete mode 100644 src/platform/plugins/shared/unified_histogram/public/container/container.tsx delete mode 100644 src/platform/plugins/shared/unified_histogram/public/container/index.tsx delete mode 100644 src/platform/plugins/shared/unified_histogram/public/layout/layout.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 12473d747c01..0a2b260b39da 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -547,6 +547,7 @@ src/platform/packages/shared/kbn-ui-theme @elastic/kibana-operations src/platform/packages/shared/kbn-unified-data-table @elastic/kibana-data-discovery @elastic/security-threat-hunting-investigations src/platform/packages/shared/kbn-unified-doc-viewer @elastic/kibana-data-discovery src/platform/packages/shared/kbn-unified-field-list @elastic/kibana-data-discovery +src/platform/packages/shared/kbn-unified-histogram @elastic/kibana-data-discovery src/platform/packages/shared/kbn-unified-tabs @elastic/kibana-data-discovery src/platform/packages/shared/kbn-unsaved-changes-prompt @elastic/kibana-management src/platform/packages/shared/kbn-use-tracked-promise @elastic/obs-ux-logs-team @@ -703,7 +704,6 @@ src/platform/plugins/shared/telemetry_management_section @elastic/kibana-core src/platform/plugins/shared/ui_actions @elastic/appex-sharedux src/platform/plugins/shared/ui_actions_enhanced @elastic/appex-sharedux src/platform/plugins/shared/unified_doc_viewer @elastic/kibana-data-discovery -src/platform/plugins/shared/unified_histogram @elastic/kibana-data-discovery src/platform/plugins/shared/unified_search @elastic/kibana-presentation src/platform/plugins/shared/usage_collection @elastic/kibana-core src/platform/plugins/shared/vis_types/timeseries @elastic/kibana-visualizations diff --git a/.i18nrc.json b/.i18nrc.json index 114ba0c3d492..f226dd1d5349 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -154,7 +154,7 @@ "unifiedDocViewer": ["src/platform/plugins/shared/unified_doc_viewer", "src/platform/packages/shared/kbn-unified-doc-viewer"], "unifiedSearch": "src/platform/plugins/shared/unified_search", "unifiedFieldList": "src/platform/packages/shared/kbn-unified-field-list", - "unifiedHistogram": "src/platform/plugins/shared/unified_histogram", + "unifiedHistogram": "src/platform/packages/shared/kbn-unified-histogram", "unifiedDataTable": "src/platform/packages/shared/kbn-unified-data-table", "unifiedTabs": "src/platform/packages/shared/kbn-unified-tabs", "dataGridInTableSearch": "src/platform/packages/shared/kbn-data-grid-in-table-search", diff --git a/docs/extend/plugin-list.md b/docs/extend/plugin-list.md index cedf4ff5f327..ea704a8602af 100644 --- a/docs/extend/plugin-list.md +++ b/docs/extend/plugin-list.md @@ -82,7 +82,6 @@ mapped_pages: | [uiActions](uiactions-plugin.md) | UI Actions plugins provides API to manage *triggers* and *actions*. *Trigger* is an abstract description of user's intent to perform an action (like user clicking on a value inside chart). It allows us to do runtime binding between code from different plugins. For, example one such trigger is when somebody applies filters on dashboard; another one is when somebody opens a Dashboard panel context menu. *Actions* are pieces of code that execute in response to a trigger. For example, to the dashboard filtering trigger multiple actions can be attached. Once a user filters on the dashboard all possible actions are displayed to the user in a popup menu and the user has to chose one. In general this plugin provides: - Creating custom functionality (actions). - Creating custom user interaction events (triggers). - Attaching and detaching actions to triggers. - Emitting trigger events. - Executing actions attached to a given trigger. - Exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. | | [uiActionsEnhanced](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/ui_actions_enhanced/README.md) | Registers commercially licensed generic actions like per panel time range and contains some code that supports drilldown work. | | [unifiedDocViewer](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/unified_doc_viewer/README.md) | This plugin contains services reliant on the plugin lifecycle for the unified doc viewer component (see @kbn/unified-doc-viewer). | -| [unifiedHistogram](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/unified_histogram/README.md) | Unified Histogram is a UX Building Block including a layout with a resizable histogram and a main display. It manages its own state and data fetching, and can easily be dropped into pages with minimal setup. | | [unifiedSearch](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/unified_search/README.md) | Contains all the components of Kibana's unified search experience. Specifically: | | [urlForwarding](https://github.com/elastic/kibana/blob/main/src/platform/plugins/private/url_forwarding/README.md) | This plugins contains helpers to redirect legacy URLs. It can be used to forward old URLs to their new counterparts. | | [usageCollection](https://github.com/elastic/kibana/blob/main/src/platform/plugins/shared/usage_collection/README.mdx) | The Usage Collection Service defines a set of APIs for other plugins to report the usage of their features. At the same time, it provides necessary the APIs for other services (i.e.: telemetry, monitoring, ...) to consume that usage data. | diff --git a/package.json b/package.json index bed213520dcc..62abe21d18cd 100644 --- a/package.json +++ b/package.json @@ -998,7 +998,7 @@ "@kbn/unified-doc-viewer-plugin": "link:src/platform/plugins/shared/unified_doc_viewer", "@kbn/unified-field-list": "link:src/platform/packages/shared/kbn-unified-field-list", "@kbn/unified-field-list-examples-plugin": "link:examples/unified_field_list_examples", - "@kbn/unified-histogram-plugin": "link:src/platform/plugins/shared/unified_histogram", + "@kbn/unified-histogram": "link:src/platform/packages/shared/kbn-unified-histogram", "@kbn/unified-search-plugin": "link:src/platform/plugins/shared/unified_search", "@kbn/unified-tabs": "link:src/platform/packages/shared/kbn-unified-tabs", "@kbn/unified-tabs-examples-plugin": "link:examples/unified_tabs_examples", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 66ee2eb38b78..d617e1f32ff1 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -168,7 +168,6 @@ pageLoadAssetSize: uiActions: 35121 uiActionsEnhanced: 38494 unifiedDocViewer: 25099 - unifiedHistogram: 19928 unifiedSearch: 23000 upgradeAssistant: 81241 uptime: 60000 diff --git a/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.tsx b/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.tsx index dd0af5388f71..611278f92364 100644 --- a/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.tsx +++ b/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.tsx @@ -17,7 +17,7 @@ import { import type { ResizeTrigger } from '@elastic/eui/src/components/resizable_container/types'; import { css } from '@emotion/react'; import { isEqual, round } from 'lodash'; -import type { ReactElement } from 'react'; +import type { ReactNode } from 'react'; import React, { useCallback, useEffect, useState } from 'react'; import { ResizableLayoutDirection } from '../types'; import { getContainerSize, percentToPixels, pixelsToPercent } from './utils'; @@ -47,8 +47,8 @@ export const PanelsResizable = ({ fixedPanelSizePct: number; flexPanelSizePct: number; }; - fixedPanel: ReactElement; - flexPanel: ReactElement; + fixedPanel: ReactNode; + flexPanel: ReactNode; resizeButtonClassName?: string; ['data-test-subj']?: string; onFixedPanelSizeChange?: (fixedPanelSize: number) => void; diff --git a/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.tsx b/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.tsx index 6b637495d663..309c7bc57ae6 100644 --- a/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.tsx +++ b/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { css } from '@emotion/react'; -import type { ReactElement } from 'react'; +import type { ReactNode } from 'react'; import React from 'react'; import { ResizableLayoutDirection } from '../types'; @@ -23,8 +23,8 @@ export const PanelsStatic = ({ className?: string; direction: ResizableLayoutDirection; hideFixedPanel?: boolean; - fixedPanel: ReactElement; - flexPanel: ReactElement; + fixedPanel: ReactNode; + flexPanel: ReactNode; }) => { // By default a flex item has overflow: visible, min-height: auto, and min-width: auto. // This can cause the item to overflow the flexbox parent when its content is too large. diff --git a/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.tsx b/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.tsx index b216f6e883a3..62a766a2efe2 100644 --- a/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.tsx +++ b/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { ReactElement, useState } from 'react'; +import { ReactNode, useState } from 'react'; import React from 'react'; import { round } from 'lodash'; import { PanelsResizable } from './panels_resizable'; @@ -47,11 +47,11 @@ export interface ResizableLayoutProps { /** * The fixed panel */ - fixedPanel: ReactElement; + fixedPanel: ReactNode; /** * The flex panel */ - flexPanel: ReactElement; + flexPanel: ReactNode; /** * Class name for the resize button */ diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md index 9852e03c64a7..cb49df4789de 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md +++ b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/test/performance/README.md @@ -72,7 +72,6 @@ test.describe( 'kbn-ui-shared-deps-npm', 'lens', 'maps', - 'unifiedHistogram', 'unifiedSearch', ]); // Validate individual plugin bundle sizes diff --git a/src/platform/packages/shared/kbn-unified-histogram/README.md b/src/platform/packages/shared/kbn-unified-histogram/README.md new file mode 100644 index 000000000000..b5f531f2de96 --- /dev/null +++ b/src/platform/packages/shared/kbn-unified-histogram/README.md @@ -0,0 +1,3 @@ +# @kbn/unified-histogram + +Components for the Discover histogram chart diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/data_view.ts b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/data_view.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/data_view.ts rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/data_view.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/data_view_with_timefield.ts b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/data_view_with_timefield.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/data_view_with_timefield.ts rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/data_view_with_timefield.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/lens_adapters.ts b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/lens_adapters.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/lens_adapters.ts rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/lens_adapters.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/lens_vis.ts b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/lens_vis.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/lens_vis.ts rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/lens_vis.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/services.tsx b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/services.tsx similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/services.tsx rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/services.tsx diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/suggestions.ts b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/suggestions.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/suggestions.ts rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/suggestions.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/__mocks__/table.ts b/src/platform/packages/shared/kbn-unified-histogram/__mocks__/table.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/__mocks__/table.ts rename to src/platform/packages/shared/kbn-unified-histogram/__mocks__/table.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/breakdown_field_selector.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/breakdown_field_selector.test.tsx similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/chart/breakdown_field_selector.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/breakdown_field_selector.test.tsx index 4342c00c9885..42c9c0c7d776 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/breakdown_field_selector.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/breakdown_field_selector.test.tsx @@ -12,8 +12,8 @@ import React from 'react'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils'; import { DataViewField } from '@kbn/data-views-plugin/common'; -import { UnifiedHistogramBreakdownContext } from '../types'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; +import { UnifiedHistogramBreakdownContext } from '../../types'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { BreakdownFieldSelector } from './breakdown_field_selector'; describe('BreakdownFieldSelector', () => { diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/breakdown_field_selector.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/breakdown_field_selector.tsx similarity index 98% rename from src/platform/plugins/shared/unified_histogram/public/chart/breakdown_field_selector.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/breakdown_field_selector.tsx index 418892bd5434..8212e836b19f 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/breakdown_field_selector.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/breakdown_field_selector.tsx @@ -21,7 +21,7 @@ import { type DataView, DataViewField } from '@kbn/data-views-plugin/common'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils'; import { i18n } from '@kbn/i18n'; -import { UnifiedHistogramBreakdownContext } from '../types'; +import { UnifiedHistogramBreakdownContext } from '../../types'; import { ToolbarSelector, ToolbarSelectorProps, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/chart.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart.test.tsx similarity index 93% rename from src/platform/plugins/shared/unified_histogram/public/chart/chart.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/chart.test.tsx index a6e5e9b1ad75..e2fbdaa48408 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/chart.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart.test.tsx @@ -13,18 +13,18 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { Capabilities } from '@kbn/core/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import type { Suggestion } from '@kbn/lens-plugin/public'; -import type { UnifiedHistogramFetchStatus } from '../types'; -import { Chart, type ChartProps } from './chart'; +import type { UnifiedHistogramFetchStatus } from '../../types'; +import { UnifiedHistogramChart, type UnifiedHistogramChartProps } from './chart'; import type { ReactWrapper } from 'enzyme'; -import { unifiedHistogramServicesMock } from '../__mocks__/services'; -import { getLensVisMock } from '../__mocks__/lens_vis'; +import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { getLensVisMock } from '../../__mocks__/lens_vis'; import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks'; import { Subject, of } from 'rxjs'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; -import { dataViewMock } from '../__mocks__/data_view'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { dataViewMock } from '../../__mocks__/data_view'; import { BreakdownFieldSelector } from './breakdown_field_selector'; -import { checkChartAvailability } from './check_chart_availability'; -import { allSuggestionsMock } from '../__mocks__/suggestions'; +import { checkChartAvailability } from './utils/check_chart_availability'; +import { allSuggestionsMock } from '../../__mocks__/suggestions'; let mockUseEditVisualization: jest.Mock | undefined = jest.fn(); @@ -38,7 +38,6 @@ async function mountComponent({ noHits, noBreakdown, chartHidden = false, - appendHistogram, dataView = dataViewWithTimefieldMock, allSuggestions, isPlainRecord, @@ -51,7 +50,6 @@ async function mountComponent({ noHits?: boolean; noBreakdown?: boolean; chartHidden?: boolean; - appendHistogram?: ReactElement; dataView?: DataView; allSuggestions?: Suggestion[]; isPlainRecord?: boolean; @@ -114,7 +112,7 @@ async function mountComponent({ }) ).lensService; - const props: ChartProps = { + const props: UnifiedHistogramChartProps = { lensVisService, dataView, requestParams, @@ -129,7 +127,6 @@ async function mountComponent({ breakdown: noBreakdown ? undefined : { field: undefined }, isChartLoading: Boolean(isChartLoading), isPlainRecord, - appendHistogram, onChartHiddenChange: jest.fn(), onTimeIntervalChange: jest.fn(), withDefaultActions: undefined, @@ -140,7 +137,7 @@ async function mountComponent({ let instance: ReactWrapper = {} as ReactWrapper; await act(async () => { - instance = mountWithIntl(); + instance = mountWithIntl(); // wait for initial async loading to complete await new Promise((r) => setTimeout(r, 0)); props.input$?.next({ type: 'fetch' }); @@ -339,12 +336,6 @@ describe('Chart', () => { expect(mockUseEditVisualization).toHaveBeenCalled(); }); - it('should render the element passed to appendHistogram', async () => { - const appendHistogram =
; - const component = await mountComponent({ appendHistogram }); - expect(component.find('[data-test-subj="appendHistogram"]').exists()).toBeTruthy(); - }); - it('should not render chart if data view is not time based', async () => { const component = await mountComponent({ dataView: dataViewMock }); expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeFalsy(); diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/chart.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart.tsx similarity index 93% rename from src/platform/plugins/shared/unified_histogram/public/chart/chart.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/chart.tsx index 47834d1d41f6..bc662386d131 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/chart.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart.tsx @@ -11,7 +11,7 @@ import React, { memo, ReactElement, useCallback, useEffect, useMemo, useState } import { Subject } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import { IconButtonGroup, type IconButtonGroupProps } from '@kbn/shared-ux-button-toolbar'; -import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiDelayRender } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiDelayRender, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import type { EmbeddableComponentProps, @@ -26,7 +26,7 @@ import type { import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import type { TimeRange } from '@kbn/es-query'; import { PublishingSubject } from '@kbn/presentation-publishing'; -import { RequestStatus } from '@kbn/inspector-plugin/public'; +import type { RequestStatus } from '@kbn/inspector-plugin/public'; import { IKibanaSearchResponse } from '@kbn/search-types'; import type { estypes } from '@elastic/elasticsearch'; import { Histogram } from './histogram'; @@ -42,8 +42,8 @@ import { UnifiedHistogramRequestContext, UnifiedHistogramServices, UnifiedHistogramBucketInterval, -} from '../types'; -import { UnifiedHistogramSuggestionType } from '../types'; +} from '../../types'; +import { UnifiedHistogramSuggestionType } from '../../types'; import { BreakdownFieldSelector } from './breakdown_field_selector'; import { TimeIntervalSelector } from './time_interval_selector'; import { useTotalHits } from './hooks/use_total_hits'; @@ -52,18 +52,17 @@ import { useChartActions } from './hooks/use_chart_actions'; import { ChartConfigPanel } from './chart_config_panel'; import { useFetch } from './hooks/use_fetch'; import { useEditVisualization } from './hooks/use_edit_visualization'; -import { LensVisService } from '../services/lens_vis_service'; -import type { UseRequestParamsResult } from '../hooks/use_request_params'; -import { removeTablesFromLensAttributes } from '../utils/lens_vis_from_table'; +import { LensVisService } from '../../services/lens_vis_service'; +import type { UseRequestParamsResult } from '../../hooks/use_request_params'; +import { removeTablesFromLensAttributes } from '../../utils/lens_vis_from_table'; import { useLensProps } from './hooks/use_lens_props'; -import { useStableCallback } from '../hooks/use_stable_callback'; +import { useStableCallback } from '../../hooks/use_stable_callback'; import { buildBucketInterval } from './utils/build_bucket_interval'; -export interface ChartProps { +export interface UnifiedHistogramChartProps { abortController?: AbortController; isChartAvailable: boolean; hiddenPanel?: boolean; - className?: string; services: UnifiedHistogramServices; dataView: DataView; requestParams: UseRequestParamsResult; @@ -75,7 +74,6 @@ export interface ChartProps { chart?: UnifiedHistogramChartContext; breakdown?: UnifiedHistogramBreakdownContext; renderCustomChartToggleActions?: () => ReactElement | undefined; - appendHistogram?: ReactElement; disableTriggers?: LensEmbeddableInput['disableTriggers']; disabledActions?: LensEmbeddableInput['disabledActions']; input$?: UnifiedHistogramInput$; @@ -89,15 +87,15 @@ export interface ChartProps { onChartLoad?: (event: UnifiedHistogramChartLoadEvent) => void; onFilter?: LensEmbeddableInput['onFilter']; onBrushEnd?: LensEmbeddableInput['onBrushEnd']; - withDefaultActions: EmbeddableComponentProps['withDefaultActions']; + withDefaultActions?: EmbeddableComponentProps['withDefaultActions']; columns?: DatatableColumn[]; } +const RequestStatusError: typeof RequestStatus.ERROR = 2; const HistogramMemoized = memo(Histogram); -export function Chart({ +export function UnifiedHistogramChart({ isChartAvailable, - className, services, dataView, requestParams, @@ -109,9 +107,6 @@ export function Chart({ lensVisService, isPlainRecord, renderCustomChartToggleActions, - appendHistogram, - disableTriggers, - disabledActions, input$: originalInput$, lensAdapters, dataLoading$, @@ -121,12 +116,9 @@ export function Chart({ onBreakdownFieldChange, onTotalHitsChange, onChartLoad, - onFilter, - onBrushEnd, - withDefaultActions, - abortController, columns, -}: ChartProps) { + ...histogramProps +}: UnifiedHistogramChartProps) { const lensVisServiceCurrentSuggestionContext = useObservable( lensVisService.currentSuggestionContext$ ); @@ -177,7 +169,7 @@ export function Chart({ dataLoadingSubject$?: PublishingSubject ) => { const lensRequest = adapters?.requests?.getRequests()[0]; - const requestFailed = lensRequest?.status === RequestStatus.ERROR; + const requestFailed = lensRequest?.status === RequestStatusError; const json = lensRequest?.response?.json as | IKibanaSearchResponse | undefined; @@ -315,7 +307,7 @@ export function Chart({ return ( )} - {appendHistogram} + )} {canSaveVisualization && isSaveModalVisible && visContext.attributes && ( diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/chart_config_panel.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart_config_panel.test.tsx similarity index 87% rename from src/platform/plugins/shared/unified_histogram/public/chart/chart_config_panel.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/chart_config_panel.test.tsx index 95391efc0900..56efd7cd90f1 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/chart_config_panel.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart_config_panel.test.tsx @@ -12,13 +12,13 @@ import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import { render } from '@testing-library/react'; import { act } from 'react-dom/test-utils'; import { setTimeout } from 'timers/promises'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; -import { unifiedHistogramServicesMock } from '../__mocks__/services'; -import { currentSuggestionMock } from '../__mocks__/suggestions'; -import { lensAdaptersMock } from '../__mocks__/lens_adapters'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { currentSuggestionMock } from '../../__mocks__/suggestions'; +import { lensAdaptersMock } from '../../__mocks__/lens_adapters'; import { ChartConfigPanel } from './chart_config_panel'; -import type { UnifiedHistogramVisContext } from '../types'; -import { UnifiedHistogramSuggestionType } from '../types'; +import type { UnifiedHistogramVisContext } from '../../types'; +import { UnifiedHistogramSuggestionType } from '../../types'; describe('ChartConfigPanel', () => { it('should return a jsx element to edit the visualization', async () => { diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/chart_config_panel.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart_config_panel.tsx similarity index 96% rename from src/platform/plugins/shared/unified_histogram/public/chart/chart_config_panel.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/chart_config_panel.tsx index a7a82cff8604..19bf1b8bb4e5 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/chart_config_panel.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/chart_config_panel.tsx @@ -12,9 +12,9 @@ import type { AggregateQuery, Query } from '@kbn/es-query'; import { isEqual, isObject } from 'lodash'; import type { LensEmbeddableOutput, Suggestion } from '@kbn/lens-plugin/public'; import type { Datatable } from '@kbn/expressions-plugin/common'; -import { EditLensConfigPanelComponent } from '@kbn/lens-plugin/public/plugin'; +import type { EditLensConfigPanelComponent } from '@kbn/lens-plugin/public/plugin'; import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils'; -import { deriveLensSuggestionFromLensAttributes } from '../utils/external_vis_context'; +import { deriveLensSuggestionFromLensAttributes } from '../../utils/external_vis_context'; import { UnifiedHistogramChartLoadEvent, @@ -22,7 +22,7 @@ import { UnifiedHistogramSuggestionContext, UnifiedHistogramSuggestionType, UnifiedHistogramVisContext, -} from '../types'; +} from '../../types'; export function ChartConfigPanel({ services, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/histogram.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/chart/histogram.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx index be213b08a1ac..7d6e5457ff01 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/histogram.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx @@ -11,11 +11,11 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { Histogram, HistogramProps } from './histogram'; import React from 'react'; import { BehaviorSubject, Subject } from 'rxjs'; -import { unifiedHistogramServicesMock } from '../__mocks__/services'; -import { getLensVisMock } from '../__mocks__/lens_vis'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; +import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { getLensVisMock } from '../../__mocks__/lens_vis'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { createDefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; -import { UnifiedHistogramInput$ } from '../types'; +import { UnifiedHistogramInput$ } from '../../types'; import { act } from 'react-dom/test-utils'; import { RequestStatus } from '@kbn/inspector-plugin/public'; import { getLensProps, useLensProps } from './hooks/use_lens_props'; diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/histogram.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.tsx similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/chart/histogram.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.tsx index ad35f6141b4b..4804e9727c4e 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/histogram.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.tsx @@ -18,7 +18,7 @@ import type { UnifiedHistogramChartContext, UnifiedHistogramServices, UnifiedHistogramVisContext, -} from '../types'; +} from '../../types'; import { useTimeRange } from './hooks/use_time_range'; import type { LensProps } from './hooks/use_lens_props'; @@ -37,7 +37,7 @@ export interface HistogramProps { disabledActions?: LensEmbeddableInput['disabledActions']; onFilter?: LensEmbeddableInput['onFilter']; onBrushEnd?: LensEmbeddableInput['onBrushEnd']; - withDefaultActions: EmbeddableComponentProps['withDefaultActions']; + withDefaultActions?: EmbeddableComponentProps['withDefaultActions']; } export function Histogram({ diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_actions.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_actions.test.ts similarity index 96% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_actions.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_actions.test.ts index 94c7c88005f7..7ad7c7baea5d 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_actions.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_actions.test.ts @@ -8,7 +8,7 @@ */ import { act, renderHook } from '@testing-library/react'; -import { UnifiedHistogramChartContext } from '../../types'; +import { UnifiedHistogramChartContext } from '../../../types'; import { useChartActions } from './use_chart_actions'; describe('useChartActions', () => { diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_actions.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_actions.ts similarity index 94% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_actions.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_actions.ts index fbf4aecfe28a..0082f8123df0 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_actions.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_actions.ts @@ -8,7 +8,7 @@ */ import { useCallback, useEffect, useRef } from 'react'; -import type { UnifiedHistogramChartContext } from '../../types'; +import type { UnifiedHistogramChartContext } from '../../../types'; export const useChartActions = ({ chart, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_styles.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_styles.tsx similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_chart_styles.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_chart_styles.tsx diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_edit_visualization.test.ts similarity index 95% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_edit_visualization.test.ts index 626950e53561..f1e5a97f79c9 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_edit_visualization.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_edit_visualization.test.ts @@ -10,9 +10,9 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import { waitFor, renderHook } from '@testing-library/react'; -import { dataViewMock } from '../../__mocks__/data_view'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; -import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { dataViewMock } from '../../../__mocks__/data_view'; +import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; +import { unifiedHistogramServicesMock } from '../../../__mocks__/services'; import { useEditVisualization } from './use_edit_visualization'; const getTriggerCompatibleActions = unifiedHistogramServicesMock.uiActions diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_edit_visualization.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_edit_visualization.ts similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_edit_visualization.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_edit_visualization.ts index 3a88f51be01e..4a0cf380113d 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_edit_visualization.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_edit_visualization.ts @@ -12,7 +12,7 @@ import type { TimeRange } from '@kbn/es-query'; import type { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { VISUALIZE_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import type { UnifiedHistogramServices } from '../..'; +import type { UnifiedHistogramServices } from '../../..'; // Avoid taking a dependency on uiActionsPlugin just for this const const visualizeFieldTrigger: typeof VISUALIZE_FIELD_TRIGGER = 'VISUALIZE_FIELD_TRIGGER'; diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_fetch.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_fetch.test.ts similarity index 95% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_fetch.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_fetch.test.ts index d8be832908e0..6a667f2c4ab8 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_fetch.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_fetch.test.ts @@ -9,7 +9,7 @@ import { useFetch } from './use_fetch'; import { renderHook } from '@testing-library/react'; -import { UnifiedHistogramInput$ } from '../../types'; +import { UnifiedHistogramInput$ } from '../../../types'; import { Subject } from 'rxjs'; describe('useFetch', () => { diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_fetch.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_fetch.ts similarity index 93% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_fetch.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_fetch.ts index e48d1026a9d3..e16902b83aed 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_fetch.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_fetch.ts @@ -9,7 +9,7 @@ import { useMemo } from 'react'; import { filter, share, tap } from 'rxjs'; -import { UnifiedHistogramInput$ } from '../../types'; +import { UnifiedHistogramInput$ } from '../../../types'; export const useFetch = ({ input$, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_lens_props.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_lens_props.test.ts similarity index 95% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_lens_props.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_lens_props.test.ts index 8ec349b7ee85..dbdbf3209cad 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_lens_props.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_lens_props.test.ts @@ -9,9 +9,9 @@ import { act, renderHook } from '@testing-library/react'; import { Subject } from 'rxjs'; -import type { UnifiedHistogramInputMessage } from '../../types'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; -import { getLensVisMock } from '../../__mocks__/lens_vis'; +import type { UnifiedHistogramInputMessage } from '../../../types'; +import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; +import { getLensVisMock } from '../../../__mocks__/lens_vis'; import { getLensProps, useLensProps } from './use_lens_props'; describe('useLensProps', () => { diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_lens_props.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_lens_props.ts similarity index 96% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_lens_props.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_lens_props.ts index 3a3c20c51507..a307d6db4868 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_lens_props.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_lens_props.ts @@ -16,8 +16,8 @@ import type { UnifiedHistogramInputMessage, UnifiedHistogramRequestContext, UnifiedHistogramVisContext, -} from '../../types'; -import { useStableCallback } from '../../hooks/use_stable_callback'; +} from '../../../types'; +import { useStableCallback } from '../../../hooks/use_stable_callback'; export type LensProps = Pick< EmbeddableComponentProps, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_time_range.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_time_range.test.tsx similarity index 98% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_time_range.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_time_range.test.tsx index 560105db09b1..0cdda5df59fd 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_time_range.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_time_range.test.tsx @@ -10,7 +10,7 @@ import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { TimeRange } from '@kbn/data-plugin/common'; import { renderHook } from '@testing-library/react'; -import { UnifiedHistogramBucketInterval } from '../../types'; +import { UnifiedHistogramBucketInterval } from '../../../types'; import { useTimeRange } from './use_time_range'; jest.mock('@kbn/datemath', () => ({ diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_time_range.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_time_range.tsx similarity index 98% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_time_range.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_time_range.tsx index 9be364c46f76..12ce1374606e 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_time_range.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_time_range.tsx @@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n'; import React, { useCallback, useMemo } from 'react'; import dateMath from '@kbn/datemath'; import type { TimeRange } from '@kbn/data-plugin/common'; -import type { UnifiedHistogramBucketInterval } from '../../types'; +import type { UnifiedHistogramBucketInterval } from '../../../types'; export const useTimeRange = ({ uiSettings, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_total_hits.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_total_hits.test.ts similarity index 98% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_total_hits.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_total_hits.test.ts index 9f7c1ef4c118..6223b800f588 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_total_hits.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_total_hits.test.ts @@ -8,8 +8,8 @@ */ import { Filter } from '@kbn/es-query'; -import { UnifiedHistogramFetchStatus, UnifiedHistogramInput$ } from '../../types'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { UnifiedHistogramFetchStatus, UnifiedHistogramInput$ } from '../../../types'; +import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; import { useTotalHits } from './use_total_hits'; import { useEffect as mockUseEffect } from 'react'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_total_hits.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_total_hits.ts similarity index 98% rename from src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_total_hits.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_total_hits.ts index 5f338c8500b3..6a0e1464c455 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/hooks/use_total_hits.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/hooks/use_total_hits.ts @@ -19,8 +19,8 @@ import { UnifiedHistogramInputMessage, UnifiedHistogramRequestContext, UnifiedHistogramServices, -} from '../../types'; -import { useStableCallback } from '../../hooks/use_stable_callback'; +} from '../../../types'; +import { useStableCallback } from '../../../hooks/use_stable_callback'; export const useTotalHits = ({ services, diff --git a/src/platform/plugins/shared/unified_histogram/public/plugin.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/index.ts similarity index 69% rename from src/platform/plugins/shared/unified_histogram/public/plugin.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/index.ts index afb794d48c30..2ae9cec1b583 100644 --- a/src/platform/plugins/shared/unified_histogram/public/plugin.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/index.ts @@ -7,14 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { Plugin } from '@kbn/core/public'; - -export class UnifiedHistogramPublicPlugin implements Plugin<{}, {}, object, {}> { - public setup() { - return {}; - } - - public start() { - return {}; - } -} +export { UnifiedHistogramChart, type UnifiedHistogramChartProps } from './chart'; +export { checkChartAvailability } from './utils/check_chart_availability'; diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/lazy.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/lazy.tsx similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/chart/lazy.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/lazy.tsx diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/time_interval_selector.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/time_interval_selector.test.tsx similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/chart/time_interval_selector.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/time_interval_selector.test.tsx diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/time_interval_selector.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/time_interval_selector.tsx similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/chart/time_interval_selector.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/time_interval_selector.tsx index bb0250b9f4d8..da33a845ee8a 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/time_interval_selector.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/time_interval_selector.tsx @@ -11,7 +11,7 @@ import React, { useCallback } from 'react'; import { EuiSelectableOption } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { search } from '@kbn/data-plugin/public'; -import type { UnifiedHistogramChartContext } from '../types'; +import type { UnifiedHistogramChartContext } from '../../types'; import { ToolbarSelector, ToolbarSelectorProps, SelectableEntry } from './toolbar_selector'; export interface TimeIntervalSelectorProps { diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/toolbar_selector.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/toolbar_selector.tsx similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/chart/toolbar_selector.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/toolbar_selector.tsx diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/utils/build_bucket_interval.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/build_bucket_interval.test.ts similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/chart/utils/build_bucket_interval.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/build_bucket_interval.test.ts index 19d35fc8f809..0065be31a195 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/utils/build_bucket_interval.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/build_bucket_interval.test.ts @@ -8,7 +8,7 @@ */ import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; import { calculateBounds } from '@kbn/data-plugin/public'; import { buildBucketInterval } from './build_bucket_interval'; diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/utils/build_bucket_interval.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/build_bucket_interval.ts similarity index 95% rename from src/platform/plugins/shared/unified_histogram/public/chart/utils/build_bucket_interval.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/build_bucket_interval.ts index 4eea85e7e70b..c186d78b6f4e 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/utils/build_bucket_interval.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/build_bucket_interval.ts @@ -11,7 +11,7 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { DataPublicPluginStart, search, tabifyAggResponse } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { TimeRange } from '@kbn/es-query'; -import type { UnifiedHistogramBucketInterval } from '../../types'; +import type { UnifiedHistogramBucketInterval } from '../../../types'; import { getChartAggConfigs } from './get_chart_agg_configs'; /** diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/check_chart_availability.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/check_chart_availability.ts similarity index 93% rename from src/platform/plugins/shared/unified_histogram/public/chart/check_chart_availability.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/check_chart_availability.ts index b590a50abc3f..5ae60d66479b 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/check_chart_availability.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/check_chart_availability.ts @@ -8,7 +8,7 @@ */ import { type DataView, DataViewType } from '@kbn/data-views-plugin/common'; -import { UnifiedHistogramChartContext } from '../types'; +import { UnifiedHistogramChartContext } from '../../../types'; export function checkChartAvailability({ chart, diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/utils/get_chart_agg_config.test.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/get_chart_agg_config.test.ts similarity index 95% rename from src/platform/plugins/shared/unified_histogram/public/chart/utils/get_chart_agg_config.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/get_chart_agg_config.test.ts index 951825e1500b..3055d4473247 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/utils/get_chart_agg_config.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/get_chart_agg_config.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; import { getChartAggConfigs } from './get_chart_agg_configs'; diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/utils/get_chart_agg_configs.ts b/src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/get_chart_agg_configs.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/chart/utils/get_chart_agg_configs.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/chart/utils/get_chart_agg_configs.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/chart/index.ts b/src/platform/packages/shared/kbn-unified-histogram/components/layout/index.ts similarity index 82% rename from src/platform/plugins/shared/unified_histogram/public/chart/index.ts rename to src/platform/packages/shared/kbn-unified-histogram/components/layout/index.ts index 73a235df8d8a..eaaa5f4cb118 100644 --- a/src/platform/plugins/shared/unified_histogram/public/chart/index.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/components/layout/index.ts @@ -7,5 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { Chart } from './chart'; -export { checkChartAvailability } from './check_chart_availability'; +export { UnifiedHistogramLayout, type UnifiedHistogramLayoutProps } from './layout'; diff --git a/src/platform/plugins/shared/unified_histogram/public/layout/layout.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.test.tsx similarity index 64% rename from src/platform/plugins/shared/unified_histogram/public/layout/layout.test.tsx rename to src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.test.tsx index 48c5d361d1c0..f3d4163272fe 100644 --- a/src/platform/plugins/shared/unified_histogram/public/layout/layout.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.test.tsx @@ -12,16 +12,18 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { ReactWrapper } from 'enzyme'; import React from 'react'; import { of } from 'rxjs'; -import { Chart } from '../chart'; +import { UnifiedHistogramChart } from '../chart'; import { UnifiedHistogramChartContext, UnifiedHistogramFetchStatus, UnifiedHistogramHitsContext, -} from '../types'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; -import { unifiedHistogramServicesMock } from '../__mocks__/services'; -import { UnifiedHistogramLayout, UnifiedHistogramLayoutProps } from './layout'; +} from '../../types'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; +import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { UnifiedHistogramLayout } from './layout'; import { ResizableLayout, ResizableLayoutMode } from '@kbn/resizable-layout'; +import { UseUnifiedHistogramProps, useUnifiedHistogram } from '../../hooks/use_unified_histogram'; +import { act } from 'react-dom/test-utils'; let mockBreakpoint = 'l'; @@ -36,54 +38,65 @@ jest.mock('@elastic/eui', () => { }); describe('Layout', () => { - const createHits = (): UnifiedHistogramHitsContext => ({ - status: UnifiedHistogramFetchStatus.complete, - total: 10, - }); - - const createChart = (): UnifiedHistogramChartContext => ({ - hidden: false, - timeInterval: 'auto', - }); - const mountComponent = async ({ services = unifiedHistogramServicesMock, - hits = createHits(), - chart = createChart(), - container = null, + hits, + chart, + topPanelHeight, ...rest - }: Partial> & { + }: Partial & { hits?: UnifiedHistogramHitsContext | null; chart?: UnifiedHistogramChartContext | null; + topPanelHeight?: number | null; } = {}) => { (searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation( jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } })) ); - - const component = mountWithIntl( - { + const unifiedHistogram = useUnifiedHistogram({ + services, + initialState: { + totalHitsStatus: hits?.status ?? UnifiedHistogramFetchStatus.complete, + totalHitsResult: hits?.total ?? 10, + chartHidden: chart?.hidden ?? false, + timeInterval: chart?.timeInterval ?? 'auto', + }, + dataView: dataViewWithTimefieldMock, + query: { language: 'kuery', query: '', - }} - filters={[]} - timeRange={{ + }, + filters: [], + timeRange: { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590', - }} - lensSuggestionsApi={jest.fn()} - onSuggestionContextChange={jest.fn()} - isChartLoading={false} - {...rest} - /> - ); + }, + isChartLoading: false, + ...rest, + }); - return component; + if (!unifiedHistogram.isInitialized) { + return null; + } + + return ( + } + {...unifiedHistogram.layoutProps} + hits={hits === undefined ? unifiedHistogram.layoutProps.hits : hits ?? undefined} + chart={chart === undefined ? unifiedHistogram.layoutProps.chart : chart ?? undefined} + topPanelHeight={ + topPanelHeight === undefined + ? unifiedHistogram.layoutProps.topPanelHeight + : topPanelHeight ?? undefined + } + /> + ); + }; + const component = mountWithIntl(); + await act(() => new Promise((resolve) => setTimeout(resolve, 0))); + return component.update(); }; const setBreakpoint = (component: ReactWrapper, breakpoint: string) => { @@ -109,12 +122,7 @@ describe('Layout', () => { }); it('should set the layout mode to ResizableLayoutMode.Static if chart.hidden is true', async () => { - const component = await mountComponent({ - chart: { - ...createChart(), - hidden: true, - }, - }); + const component = await mountComponent({ chart: { timeInterval: 'auto', hidden: true } }); expect(component.find(ResizableLayout).prop('mode')).toBe(ResizableLayoutMode.Static); }); @@ -132,16 +140,20 @@ describe('Layout', () => { const component = await mountComponent(); setBreakpoint(component, 's'); const expectedHeight = component.find(ResizableLayout).prop('fixedPanelSize'); - expect(component.find(Chart).find('div.euiFlexGroup').first().getDOMNode()).toHaveStyle({ + expect( + component.find(UnifiedHistogramChart).find('div.euiFlexGroup').first().getDOMNode() + ).toHaveStyle({ height: `${expectedHeight}px`, }); }); it('should not set a fixed height for Chart when layout mode is ResizableLayoutMode.Static and chart.hidden is true', async () => { - const component = await mountComponent({ chart: { ...createChart(), hidden: true } }); + const component = await mountComponent({ chart: { timeInterval: 'auto', hidden: true } }); setBreakpoint(component, 's'); const expectedHeight = component.find(ResizableLayout).prop('fixedPanelSize'); - expect(component.find(Chart).find('div.euiFlexGroup').first().getDOMNode()).not.toHaveStyle({ + expect( + component.find(UnifiedHistogramChart).find('div.euiFlexGroup').first().getDOMNode() + ).not.toHaveStyle({ height: `${expectedHeight}px`, }); }); @@ -150,7 +162,9 @@ describe('Layout', () => { const component = await mountComponent({ chart: null }); setBreakpoint(component, 's'); const expectedHeight = component.find(ResizableLayout).prop('fixedPanelSize'); - expect(component.find(Chart).find('div.euiFlexGroup').first().getDOMNode()).not.toHaveStyle({ + expect( + component.find(UnifiedHistogramChart).find('div.euiFlexGroup').first().getDOMNode() + ).not.toHaveStyle({ height: `${expectedHeight}px`, }); }); @@ -158,7 +172,7 @@ describe('Layout', () => { describe('topPanelHeight', () => { it('should pass a default fixedPanelSize to ResizableLayout when the topPanelHeight prop is undefined', async () => { - const component = await mountComponent({ topPanelHeight: undefined }); + const component = await mountComponent({ topPanelHeight: null }); expect(component.find(ResizableLayout).prop('fixedPanelSize')).toBeGreaterThan(0); }); }); diff --git a/src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.tsx new file mode 100644 index 000000000000..93f73edb75ba --- /dev/null +++ b/src/platform/packages/shared/kbn-unified-histogram/components/layout/layout.tsx @@ -0,0 +1,116 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { euiFullHeight, useEuiTheme, useIsWithinBreakpoints } from '@elastic/eui'; +import React, { PropsWithChildren, ReactNode, useState } from 'react'; +import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; +import { + ResizableLayout, + ResizableLayoutDirection, + ResizableLayoutMode, +} from '@kbn/resizable-layout'; +import { css } from '@emotion/react'; +import { UnifiedHistogramChartContext, UnifiedHistogramHitsContext } from '../../types'; + +export type UnifiedHistogramLayoutProps = PropsWithChildren<{ + /** + * The parent container element, used to calculate the layout size + */ + container: HTMLElement | null; + /** + * The rendered UnifiedHistogramChart component + */ + unifiedHistogramChart: ReactNode; + /** + * Context object for the chart -- leave undefined to hide the chart + */ + chart?: UnifiedHistogramChartContext; + /** + * Flag to indicate if the chart is available for rendering + */ + isChartAvailable?: boolean; + /** + * Context object for the hits count -- leave undefined to hide the hits count + */ + hits?: UnifiedHistogramHitsContext; + /** + * Current top panel height -- leave undefined to use the default + */ + topPanelHeight?: number; + /** + * Callback to update the topPanelHeight prop when a resize is triggered + */ + onTopPanelHeightChange?: (topPanelHeight: number | undefined) => void; +}>; + +export const UnifiedHistogramLayout = ({ + container, + unifiedHistogramChart, + chart, + isChartAvailable, + hits, + topPanelHeight, + onTopPanelHeightChange, + children, +}: UnifiedHistogramLayoutProps) => { + const [mainPanelNode] = useState(() => + createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }) + ); + + const isMobile = useIsWithinBreakpoints(['xs', 's']); + const showFixedPanels = isMobile || !chart || chart.hidden; + const { euiTheme } = useEuiTheme(); + const defaultTopPanelHeight = euiTheme.base * 12; + const minMainPanelHeight = euiTheme.base * 10; + + const chartCss = + isMobile && chart && !chart.hidden + ? css` + .unifiedHistogram__chart { + height: ${defaultTopPanelHeight}px; + } + ` + : css` + .unifiedHistogram__chart { + ${euiFullHeight()} + } + `; + + const panelsMode = + chart || hits + ? showFixedPanels + ? ResizableLayoutMode.Static + : ResizableLayoutMode.Resizable + : ResizableLayoutMode.Single; + + const currentTopPanelHeight = topPanelHeight ?? defaultTopPanelHeight; + + return ( + <> + + {React.isValidElement<{ isChartAvailable?: boolean }>(children) + ? React.cloneElement(children, { isChartAvailable }) + : children} + + } + data-test-subj="unifiedHistogram" + css={chartCss} + onFixedPanelSizeChange={onTopPanelHeightChange} + /> + + ); +}; diff --git a/src/platform/plugins/shared/unified_histogram/public/hooks/use_request_params.test.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_request_params.test.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/hooks/use_request_params.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_request_params.test.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/hooks/use_request_params.tsx b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_request_params.tsx similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/hooks/use_request_params.tsx rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_request_params.tsx diff --git a/src/platform/plugins/shared/unified_histogram/public/hooks/use_stable_callback.test.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_stable_callback.test.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/hooks/use_stable_callback.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_stable_callback.test.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/hooks/use_stable_callback.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_stable_callback.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/hooks/use_stable_callback.ts rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_stable_callback.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/container/hooks/use_state_props.test.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_props.test.ts similarity index 95% rename from src/platform/plugins/shared/unified_histogram/public/container/hooks/use_state_props.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_props.test.ts index 94912430db48..2ca6ed269446 100644 --- a/src/platform/plugins/shared/unified_histogram/public/container/hooks/use_state_props.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_props.test.ts @@ -12,11 +12,11 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common'; import { waitFor, renderHook, act } from '@testing-library/react'; import type { DatatableColumn } from '@kbn/expressions-plugin/common'; import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils'; -import { UnifiedHistogramFetchStatus, UnifiedHistogramSuggestionContext } from '../../types'; -import { dataViewMock } from '../../__mocks__/data_view'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; -import { lensAdaptersMock } from '../../__mocks__/lens_adapters'; -import { unifiedHistogramServicesMock } from '../../__mocks__/services'; +import { UnifiedHistogramFetchStatus, UnifiedHistogramSuggestionContext } from '../types'; +import { dataViewMock } from '../__mocks__/data_view'; +import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; +import { lensAdaptersMock } from '../__mocks__/lens_adapters'; +import { unifiedHistogramServicesMock } from '../__mocks__/services'; import { createStateService, UnifiedHistogramState, @@ -64,6 +64,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); expect(result.current).toMatchInlineSnapshot(` @@ -123,6 +124,7 @@ describe('useStateProps', () => { "onTimeIntervalChange": [Function], "onTopPanelHeightChange": [Function], "onTotalHitsChange": [Function], + "onVisContextChanged": undefined, "request": Object { "adapter": RequestAdapter { "_events": Object {}, @@ -135,6 +137,7 @@ describe('useStateProps', () => { }, "searchSessionId": "123", }, + "topPanelHeight": 100, } `); }); @@ -153,6 +156,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); expect(result.current).toMatchInlineSnapshot(` @@ -212,6 +216,7 @@ describe('useStateProps', () => { "onTimeIntervalChange": [Function], "onTopPanelHeightChange": [Function], "onTotalHitsChange": [Function], + "onVisContextChanged": undefined, "request": Object { "adapter": RequestAdapter { "_events": Object {}, @@ -224,6 +229,7 @@ describe('useStateProps', () => { }, "searchSessionId": "123", }, + "topPanelHeight": 100, } `); @@ -251,6 +257,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); expect(result.current.chart).toStrictEqual({ hidden: false, timeInterval: 'auto' }); @@ -290,6 +297,7 @@ describe('useStateProps', () => { columns: esqlColumns, breakdownField, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); @@ -332,6 +340,7 @@ describe('useStateProps', () => { columns: esqlColumns, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); const { onBreakdownFieldChange } = result.current; @@ -357,6 +366,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); expect(result.current).toMatchInlineSnapshot(` @@ -411,6 +421,7 @@ describe('useStateProps', () => { "onTimeIntervalChange": [Function], "onTopPanelHeightChange": [Function], "onTotalHitsChange": [Function], + "onVisContextChanged": undefined, "request": Object { "adapter": RequestAdapter { "_events": Object {}, @@ -423,6 +434,7 @@ describe('useStateProps', () => { }, "searchSessionId": "123", }, + "topPanelHeight": 100, } `); }); @@ -441,6 +453,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); expect(result.current).toMatchInlineSnapshot(` @@ -495,6 +508,7 @@ describe('useStateProps', () => { "onTimeIntervalChange": [Function], "onTopPanelHeightChange": [Function], "onTotalHitsChange": [Function], + "onVisContextChanged": undefined, "request": Object { "adapter": RequestAdapter { "_events": Object {}, @@ -507,6 +521,7 @@ describe('useStateProps', () => { }, "searchSessionId": "123", }, + "topPanelHeight": 100, } `); }); @@ -525,6 +540,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); @@ -602,6 +618,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }) ); (stateService.setLensRequestAdapter as jest.Mock).mockClear(); @@ -626,6 +643,7 @@ describe('useStateProps', () => { columns: undefined, breakdownField: undefined, onBreakdownFieldChange: undefined, + onVisContextChanged: undefined, }; const hook = renderHook((props: Parameters[0]) => useStateProps(props), { initialProps, diff --git a/src/platform/plugins/shared/unified_histogram/public/container/hooks/use_state_props.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_props.ts similarity index 84% rename from src/platform/plugins/shared/unified_histogram/public/container/hooks/use_state_props.ts rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_props.ts index 46244d69d1f8..a1084a94c5c0 100644 --- a/src/platform/plugins/shared/unified_histogram/public/container/hooks/use_state_props.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_props.ts @@ -16,10 +16,12 @@ import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils' import { useCallback, useEffect, useMemo } from 'react'; import { UnifiedHistogramChartLoadEvent, + UnifiedHistogramExternalVisContextStatus, UnifiedHistogramFetchStatus, UnifiedHistogramServices, UnifiedHistogramSuggestionContext, -} from '../../types'; + UnifiedHistogramVisContext, +} from '../types'; import type { UnifiedHistogramStateService } from '../services/state_service'; import { chartHiddenSelector, @@ -28,9 +30,12 @@ import { totalHitsStatusSelector, lensAdaptersSelector, lensDataLoadingSelector$, + topPanelHeightSelector, } from '../utils/state_selectors'; -import { useStateSelector } from '../utils/use_state_selector'; +import { useStateSelector } from './use_state_selector'; import { setBreakdownField } from '../utils/local_storage_utils'; +import { exportVisContext } from '../utils/external_vis_context'; +import { UseUnifiedHistogramProps } from './use_unified_histogram'; export const useStateProps = ({ services, @@ -43,6 +48,7 @@ export const useStateProps = ({ columns, breakdownField, onBreakdownFieldChange: originalOnBreakdownFieldChange, + onVisContextChanged: originalOnVisContextChanged, }: { services: UnifiedHistogramServices; localStorageKeyPrefix: string | undefined; @@ -54,13 +60,21 @@ export const useStateProps = ({ columns: DatatableColumn[] | undefined; breakdownField: string | undefined; onBreakdownFieldChange: ((breakdownField: string | undefined) => void) | undefined; + onVisContextChanged: + | (( + nextVisContext: UnifiedHistogramVisContext | undefined, + externalVisContextStatus: UnifiedHistogramExternalVisContextStatus + ) => void) + | undefined; }) => { + const topPanelHeight = useStateSelector(stateService?.state$, topPanelHeightSelector); const chartHidden = useStateSelector(stateService?.state$, chartHiddenSelector); const timeInterval = useStateSelector(stateService?.state$, timeIntervalSelector); const totalHitsResult = useStateSelector(stateService?.state$, totalHitsResultSelector); const totalHitsStatus = useStateSelector(stateService?.state$, totalHitsStatusSelector); const lensAdapters = useStateSelector(stateService?.state$, lensAdaptersSelector); const lensDataLoading$ = useStateSelector(stateService?.state$, lensDataLoadingSelector$); + /** * Contexts */ @@ -132,8 +146,8 @@ export const useStateProps = ({ */ const onTopPanelHeightChange = useCallback( - (topPanelHeight: number | undefined) => { - stateService?.setTopPanelHeight(topPanelHeight); + (newTopPanelHeight: number | undefined) => { + stateService?.setTopPanelHeight(newTopPanelHeight); }, [stateService] ); @@ -186,6 +200,18 @@ export const useStateProps = ({ [stateService] ); + const onVisContextChanged: UseUnifiedHistogramProps['onVisContextChanged'] = useMemo(() => { + if (!originalOnVisContextChanged || !isPlainRecord) { + return undefined; + } + + return (visContext, externalVisContextStatus) => { + const minifiedVisContext = exportVisContext(visContext); + + originalOnVisContextChanged(minifiedVisContext, externalVisContextStatus); + }; + }, [isPlainRecord, originalOnVisContextChanged]); + /** * Effects */ @@ -205,6 +231,7 @@ export const useStateProps = ({ }, [chart, chartHidden, stateService]); return { + topPanelHeight, hits, chart, breakdown, @@ -219,5 +246,6 @@ export const useStateProps = ({ onChartLoad, onBreakdownFieldChange, onSuggestionContextChange, + onVisContextChanged, }; }; diff --git a/src/platform/plugins/shared/unified_histogram/public/container/utils/use_state_selector.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_selector.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/container/utils/use_state_selector.ts rename to src/platform/packages/shared/kbn-unified-histogram/hooks/use_state_selector.ts diff --git a/src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.test.tsx new file mode 100644 index 000000000000..c0a7ebfdf68c --- /dev/null +++ b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.test.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { RequestAdapter } from '@kbn/inspector-plugin/common'; +import { act } from 'react-dom/test-utils'; +import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; +import { unifiedHistogramServicesMock } from '../__mocks__/services'; +import { useUnifiedHistogram } from './use_unified_histogram'; +import { renderHook, waitFor } from '@testing-library/react'; + +describe('useUnifiedHistogram', () => { + it('should initialize', async () => { + const hook = renderHook(() => + useUnifiedHistogram({ + services: unifiedHistogramServicesMock, + initialState: { timeInterval: '42s' }, + dataView: dataViewWithTimefieldMock, + filters: [], + query: { language: 'kuery', query: '' }, + requestAdapter: new RequestAdapter(), + searchSessionId: '123', + timeRange: { from: 'now-15m', to: 'now' }, + }) + ); + expect(hook.result.current.isInitialized).toBe(false); + expect(hook.result.current.api).toBeUndefined(); + expect(hook.result.current.chartProps).toBeUndefined(); + expect(hook.result.current.layoutProps).toBeUndefined(); + await waitFor(() => { + expect(hook.result.current.isInitialized).toBe(true); + }); + expect(hook.result.current.api).toBeDefined(); + expect(hook.result.current.chartProps?.chart?.timeInterval).toBe('42s'); + expect(hook.result.current.layoutProps).toBeDefined(); + }); + + it('should trigger input$ when fetch is called', async () => { + const { result } = renderHook(() => + useUnifiedHistogram({ + services: unifiedHistogramServicesMock, + initialState: { timeInterval: '42s' }, + dataView: dataViewWithTimefieldMock, + filters: [], + query: { language: 'kuery', query: '' }, + requestAdapter: new RequestAdapter(), + searchSessionId: '123', + timeRange: { from: 'now-15m', to: 'now' }, + }) + ); + await waitFor(() => { + expect(result.current.isInitialized).toBe(true); + }); + const input$ = result.current.chartProps?.input$; + const inputSpy = jest.fn(); + input$?.subscribe(inputSpy); + act(() => { + result.current.api?.fetch(); + }); + expect(inputSpy).toHaveBeenCalledTimes(1); + expect(inputSpy).toHaveBeenCalledWith({ type: 'fetch' }); + }); +}); diff --git a/src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.ts b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.ts new file mode 100644 index 000000000000..fcaf695708e4 --- /dev/null +++ b/src/platform/packages/shared/kbn-unified-histogram/hooks/use_unified_histogram.ts @@ -0,0 +1,324 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/public'; +import type { EmbeddableComponentProps, LensEmbeddableInput } from '@kbn/lens-plugin/public'; +import { useEffect, useMemo, useState } from 'react'; +import { Observable, Subject, of } from 'rxjs'; +import useMount from 'react-use/lib/useMount'; +import { pick } from 'lodash'; +import type { DataView } from '@kbn/data-views-plugin/common'; +import useObservable from 'react-use/lib/useObservable'; +import { UnifiedHistogramChartProps } from '../components/chart/chart'; +import { + UnifiedHistogramExternalVisContextStatus, + UnifiedHistogramInputMessage, + UnifiedHistogramRequestContext, + UnifiedHistogramServices, + UnifiedHistogramSuggestionContext, + UnifiedHistogramSuggestionType, + UnifiedHistogramVisContext, +} from '../types'; +import { + UnifiedHistogramStateOptions, + UnifiedHistogramStateService, + createStateService, +} from '../services/state_service'; +import { useStateProps } from './use_state_props'; +import { useRequestParams } from './use_request_params'; +import { LensVisService } from '../services/lens_vis_service'; +import { checkChartAvailability } from '../components/chart'; +import { UnifiedHistogramLayoutProps } from '../components/layout/layout'; +import { getBreakdownField } from '../utils/local_storage_utils'; + +export type UseUnifiedHistogramProps = Omit & { + /** + * Required services + */ + services: UnifiedHistogramServices; + /** + * The current search session ID + */ + searchSessionId?: UnifiedHistogramRequestContext['searchSessionId']; + /** + * The request adapter to use for the inspector + */ + requestAdapter?: UnifiedHistogramRequestContext['adapter']; + /** + * The abort controller to use for requests + */ + abortController?: AbortController; + /** + * The current data view + */ + dataView: DataView; + /** + * The current query + */ + query?: Query | AggregateQuery; + /** + * The current filters + */ + filters?: Filter[]; + /** + * The current breakdown field + */ + breakdownField?: string; + /** + * The external custom Lens vis + */ + externalVisContext?: UnifiedHistogramVisContext; + /** + * The current time range + */ + timeRange?: TimeRange; + /** + * The relative time range, used when timeRange is an absolute range (e.g. for edit visualization button) + */ + relativeTimeRange?: TimeRange; + /** + * The current columns + */ + columns?: DatatableColumn[]; + /** + * Preloaded data table sometimes used for rendering the chart in ES|QL mode + */ + table?: Datatable; + /** + * Flag indicating that the chart is currently loading + */ + isChartLoading?: boolean; + /** + * Allows users to enable/disable default actions + */ + withDefaultActions?: EmbeddableComponentProps['withDefaultActions']; + /** + * Disabled action IDs for the Lens embeddable + */ + disabledActions?: LensEmbeddableInput['disabledActions']; + /** + * Callback to pass to the Lens embeddable to handle filter changes + */ + onFilter?: LensEmbeddableInput['onFilter']; + /** + * Callback to pass to the Lens embeddable to handle brush events + */ + onBrushEnd?: LensEmbeddableInput['onBrushEnd']; + /** + * Callback to update the breakdown field -- should set {@link UnifiedHistogramBreakdownContext.field} to breakdownField + */ + onBreakdownFieldChange?: (breakdownField: string | undefined) => void; + /** + * Callback to notify about the change in Lens attributes + */ + onVisContextChanged?: ( + nextVisContext: UnifiedHistogramVisContext | undefined, + externalVisContextStatus: UnifiedHistogramExternalVisContextStatus + ) => void; +}; + +export type UnifiedHistogramApi = { + /** + * Trigger a fetch of the data + */ + fetch: () => void; +} & Pick< + UnifiedHistogramStateService, + 'state$' | 'setChartHidden' | 'setTopPanelHeight' | 'setTimeInterval' | 'setTotalHits' +>; + +export type UnifiedHistogramPartialLayoutProps = Omit< + UnifiedHistogramLayoutProps, + 'container' | 'unifiedHistogramChart' +>; + +export type UseUnifiedHistogramResult = + | { isInitialized: false; api?: undefined; chartProps?: undefined; layoutProps?: undefined } + | { + isInitialized: true; + api: UnifiedHistogramApi; + chartProps: UnifiedHistogramChartProps; + layoutProps: UnifiedHistogramPartialLayoutProps; + }; + +const EMPTY_SUGGESTION_CONTEXT: Observable = of({ + suggestion: undefined, + type: UnifiedHistogramSuggestionType.unsupported, +}); + +export const useUnifiedHistogram = (props: UseUnifiedHistogramProps): UseUnifiedHistogramResult => { + const [stateService] = useState(() => { + const { services, initialState, localStorageKeyPrefix } = props; + return createStateService({ services, initialState, localStorageKeyPrefix }); + }); + const [lensVisService, setLensVisService] = useState(); + const [input$] = useState(() => new Subject()); + const [api, setApi] = useState(); + + // Load async services and initialize API + useMount(async () => { + const apiHelper = await services.lens.stateHelperApi(); + setLensVisService(new LensVisService({ services, lensSuggestionsApi: apiHelper.suggestions })); + setApi({ + fetch: () => { + input$.next({ type: 'fetch' }); + }, + ...pick( + stateService, + 'state$', + 'setChartHidden', + 'setTopPanelHeight', + 'setTimeInterval', + 'setTotalHits' + ), + }); + }); + + const { + services, + dataView, + query, + columns, + searchSessionId, + requestAdapter, + isChartLoading, + localStorageKeyPrefix, + filters, + timeRange, + table, + externalVisContext, + } = props; + const initialBreakdownField = useMemo( + () => + localStorageKeyPrefix + ? getBreakdownField(services.storage, localStorageKeyPrefix) + : undefined, + [localStorageKeyPrefix, services.storage] + ); + const stateProps = useStateProps({ + services, + localStorageKeyPrefix, + stateService, + dataView, + query, + searchSessionId, + requestAdapter, + columns, + breakdownField: 'breakdownField' in props ? props.breakdownField : initialBreakdownField, + onBreakdownFieldChange: props.onBreakdownFieldChange, + onVisContextChanged: props.onVisContextChanged, + }); + const columnsMap = useMemo(() => { + return columns?.reduce>((acc, column) => { + acc[column.id] = column; + return acc; + }, {}); + }, [columns]); + const requestParams = useRequestParams({ + services, + query, + filters, + timeRange, + }); + const lensVisServiceCurrentSuggestionContext = useObservable( + lensVisService?.currentSuggestionContext$ ?? EMPTY_SUGGESTION_CONTEXT + ); + + useEffect(() => { + if (isChartLoading || !lensVisService) { + return; + } + + lensVisService.update({ + externalVisContext, + queryParams: { + dataView, + query: requestParams.query, + filters: requestParams.filters, + timeRange, + isPlainRecord: stateProps.isPlainRecord, + columns, + columnsMap, + }, + timeInterval: stateProps.chart?.timeInterval, + breakdownField: stateProps.breakdown?.field, + table, + onSuggestionContextChange: stateProps.onSuggestionContextChange, + onVisContextChanged: stateProps.onVisContextChanged, + }); + }, [ + columns, + columnsMap, + dataView, + externalVisContext, + isChartLoading, + lensVisService, + requestParams.filters, + requestParams.query, + stateProps.breakdown?.field, + stateProps.chart?.timeInterval, + stateProps.isPlainRecord, + stateProps.onSuggestionContextChange, + stateProps.onVisContextChanged, + table, + timeRange, + ]); + + const chart = + !lensVisServiceCurrentSuggestionContext?.type || + lensVisServiceCurrentSuggestionContext.type === UnifiedHistogramSuggestionType.unsupported + ? undefined + : stateProps.chart; + const isChartAvailable = checkChartAvailability({ + chart, + dataView, + isPlainRecord: stateProps.isPlainRecord, + }); + const chartProps = useMemo(() => { + return lensVisService + ? { + ...props, + ...stateProps, + input$, + chart, + isChartAvailable, + requestParams, + lensVisService, + } + : undefined; + }, [chart, input$, isChartAvailable, lensVisService, props, requestParams, stateProps]); + const layoutProps = useMemo( + () => ({ + chart, + isChartAvailable, + hits: stateProps.hits, + topPanelHeight: stateProps.topPanelHeight, + onTopPanelHeightChange: stateProps.onTopPanelHeightChange, + }), + [ + chart, + isChartAvailable, + stateProps.hits, + stateProps.onTopPanelHeightChange, + stateProps.topPanelHeight, + ] + ); + + if (!api || !chartProps) { + return { isInitialized: false }; + } + + return { + isInitialized: true, + api, + chartProps, + layoutProps, + }; +}; diff --git a/src/platform/plugins/shared/unified_histogram/public/index.ts b/src/platform/packages/shared/kbn-unified-histogram/index.ts similarity index 61% rename from src/platform/plugins/shared/unified_histogram/public/index.ts rename to src/platform/packages/shared/kbn-unified-histogram/index.ts index f79db6ce8a51..0351beb56109 100644 --- a/src/platform/plugins/shared/unified_histogram/public/index.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/index.ts @@ -7,27 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { UnifiedHistogramPublicPlugin } from './plugin'; - -export type { BreakdownFieldSelectorProps } from './chart/lazy'; -export { UnifiedBreakdownFieldSelector } from './chart/lazy'; - -export type { - UnifiedHistogramApi, - UnifiedHistogramContainerProps, - UnifiedHistogramCreationOptions, - UnifiedHistogramState, - UnifiedHistogramStateOptions, -} from './container'; -export { - UnifiedHistogramContainer, - getChartHidden, - getTopPanelHeight, - getBreakdownField, - setChartHidden, - setTopPanelHeight, - setBreakdownField, -} from './container'; export type { UnifiedHistogramServices, UnifiedHistogramChartLoadEvent, @@ -35,6 +14,29 @@ export type { UnifiedHistogramVisContext, } from './types'; export { UnifiedHistogramFetchStatus, UnifiedHistogramExternalVisContextStatus } from './types'; -export { canImportVisContext } from './utils/external_vis_context'; -export const plugin = () => new UnifiedHistogramPublicPlugin(); +export { + UnifiedBreakdownFieldSelector, + type BreakdownFieldSelectorProps, +} from './components/chart/lazy'; +export { UnifiedHistogramChart, type UnifiedHistogramChartProps } from './components/chart'; +export { UnifiedHistogramLayout, type UnifiedHistogramLayoutProps } from './components/layout'; + +export { + useUnifiedHistogram, + type UseUnifiedHistogramProps, + type UnifiedHistogramApi, + type UnifiedHistogramPartialLayoutProps, +} from './hooks/use_unified_histogram'; + +export type { UnifiedHistogramState } from './services/state_service'; + +export { + getChartHidden, + getTopPanelHeight, + getBreakdownField, + setChartHidden, + setTopPanelHeight, + setBreakdownField, +} from './utils/local_storage_utils'; +export { canImportVisContext } from './utils/external_vis_context'; diff --git a/src/platform/plugins/shared/unified_histogram/jest.config.js b/src/platform/packages/shared/kbn-unified-histogram/jest.config.js similarity index 60% rename from src/platform/plugins/shared/unified_histogram/jest.config.js rename to src/platform/packages/shared/kbn-unified-histogram/jest.config.js index f46df2953298..fd61a6e3ab5d 100644 --- a/src/platform/plugins/shared/unified_histogram/jest.config.js +++ b/src/platform/packages/shared/kbn-unified-histogram/jest.config.js @@ -10,11 +10,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../../../..', - roots: ['/src/platform/plugins/shared/unified_histogram'], - coverageDirectory: - '/target/kibana-coverage/jest/src/platform/plugins/shared/unified_histogram', - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/src/platform/plugins/shared/unified_histogram/{common,public,server}/**/*.{ts,tsx}', - ], + roots: ['/src/platform/packages/shared/kbn-unified-histogram'], }; diff --git a/src/platform/packages/shared/kbn-unified-histogram/kibana.jsonc b/src/platform/packages/shared/kbn-unified-histogram/kibana.jsonc new file mode 100644 index 000000000000..883a1bff3650 --- /dev/null +++ b/src/platform/packages/shared/kbn-unified-histogram/kibana.jsonc @@ -0,0 +1,8 @@ +{ + "type": "shared-browser", + "id": "@kbn/unified-histogram", + "owner": "@elastic/kibana-data-discovery", + "group": "platform", + "visibility": "shared", + "description": "Components for the Discover histogram chart" +} diff --git a/src/platform/plugins/shared/unified_histogram/public/mocks.ts b/src/platform/packages/shared/kbn-unified-histogram/mocks.ts similarity index 92% rename from src/platform/plugins/shared/unified_histogram/public/mocks.ts rename to src/platform/packages/shared/kbn-unified-histogram/mocks.ts index 73d7d2bd9301..cdbb1b25788c 100644 --- a/src/platform/plugins/shared/unified_histogram/public/mocks.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/mocks.ts @@ -8,7 +8,7 @@ */ import { Observable } from 'rxjs'; -import type { UnifiedHistogramApi } from './container'; +import { UnifiedHistogramApi } from './hooks/use_unified_histogram'; export const createMockUnifiedHistogramApi = () => { const api: UnifiedHistogramApi = { diff --git a/src/platform/packages/shared/kbn-unified-histogram/package.json b/src/platform/packages/shared/kbn-unified-histogram/package.json new file mode 100644 index 000000000000..3554b00477ec --- /dev/null +++ b/src/platform/packages/shared/kbn-unified-histogram/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/unified-histogram", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" +} \ No newline at end of file diff --git a/src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.attributes.test.ts b/src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.attributes.test.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.attributes.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.attributes.test.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.suggestions.test.ts b/src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.suggestions.test.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.suggestions.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.suggestions.test.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.ts b/src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.ts similarity index 99% rename from src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.ts rename to src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.ts index 010d90bc26ee..eaf6c479884a 100644 --- a/src/platform/plugins/shared/unified_histogram/public/services/lens_vis_service.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/services/lens_vis_service.ts @@ -34,8 +34,8 @@ import { mapVisToChartType, computeInterval, } from '@kbn/visualization-utils'; -import { LegendSize } from '@kbn/visualizations-plugin/public'; -import { XYConfiguration } from '@kbn/visualizations-plugin/common'; +import type { LegendSize } from '@kbn/visualizations-plugin/public'; +import type { XYConfiguration } from '@kbn/visualizations-plugin/common'; import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { fieldSupportsBreakdown } from '@kbn/field-utils'; @@ -413,6 +413,7 @@ export class LensVisService { }, }; + const legendSize: `${LegendSize.EXTRA_LARGE}` = 'xlarge'; const visualizationState = { layers: [ { @@ -435,7 +436,7 @@ export class LensVisService { legend: { isVisible: true, position: 'right', - legendSize: LegendSize.EXTRA_LARGE, + legendSize, shouldTruncate: false, }, preferredSeriesType: 'bar_stacked', diff --git a/src/platform/plugins/shared/unified_histogram/public/container/services/state_service.test.ts b/src/platform/packages/shared/kbn-unified-histogram/services/state_service.test.ts similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/container/services/state_service.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/services/state_service.test.ts index 5c3024eef7dd..997202aa96e6 100644 --- a/src/platform/plugins/shared/unified_histogram/public/container/services/state_service.test.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/services/state_service.test.ts @@ -8,9 +8,9 @@ */ import { RequestAdapter } from '@kbn/inspector-plugin/common'; -import { UnifiedHistogramFetchStatus } from '../..'; -import { unifiedHistogramServicesMock } from '../../__mocks__/services'; -import { lensAdaptersMock } from '../../__mocks__/lens_adapters'; +import { UnifiedHistogramFetchStatus } from '..'; +import { unifiedHistogramServicesMock } from '../__mocks__/services'; +import { lensAdaptersMock } from '../__mocks__/lens_adapters'; import { getChartHidden, getTopPanelHeight, diff --git a/src/platform/plugins/shared/unified_histogram/public/container/services/state_service.ts b/src/platform/packages/shared/kbn-unified-histogram/services/state_service.ts similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/container/services/state_service.ts rename to src/platform/packages/shared/kbn-unified-histogram/services/state_service.ts index cdca02396e3a..188b736cfec9 100644 --- a/src/platform/plugins/shared/unified_histogram/public/container/services/state_service.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/services/state_service.ts @@ -10,15 +10,15 @@ import type { RequestAdapter } from '@kbn/inspector-plugin/common'; import { BehaviorSubject, Observable } from 'rxjs'; import { PublishingSubject } from '@kbn/presentation-publishing'; -import { UnifiedHistogramFetchStatus } from '../..'; -import type { UnifiedHistogramServices, UnifiedHistogramChartLoadEvent } from '../../types'; +import { UnifiedHistogramFetchStatus } from '..'; +import type { UnifiedHistogramServices, UnifiedHistogramChartLoadEvent } from '../types'; import { getChartHidden, getTopPanelHeight, setChartHidden, setTopPanelHeight, } from '../utils/local_storage_utils'; -import type { UnifiedHistogramSuggestionContext } from '../../types'; +import type { UnifiedHistogramSuggestionContext } from '../types'; /** * The current state of the container diff --git a/src/platform/plugins/shared/unified_histogram/tsconfig.json b/src/platform/packages/shared/kbn-unified-histogram/tsconfig.json similarity index 84% rename from src/platform/plugins/shared/unified_histogram/tsconfig.json rename to src/platform/packages/shared/kbn-unified-histogram/tsconfig.json index e1ae051c5fba..f5c8153c06b9 100644 --- a/src/platform/plugins/shared/unified_histogram/tsconfig.json +++ b/src/platform/packages/shared/kbn-unified-histogram/tsconfig.json @@ -2,40 +2,39 @@ "extends": "../../../../../tsconfig.base.json", "compilerOptions": { "outDir": "target/types", + "types": ["jest", "node", "react", "@emotion/react/types/css-prop"] }, - "include": [ "../../../../../typings/**/*", "common/**/*", "public/**/*", "server/**/*"], + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["target/**/*"], "kbn_references": [ - "@kbn/core", - "@kbn/data-plugin", "@kbn/data-views-plugin", - "@kbn/lens-plugin", - "@kbn/field-formats-plugin", - "@kbn/inspector-plugin", "@kbn/expressions-plugin", - "@kbn/test-jest-helpers", - "@kbn/i18n", - "@kbn/es-query", - "@kbn/core-ui-settings-browser", - "@kbn/datemath", - "@kbn/core-ui-settings-browser-mocks", - "@kbn/shared-ux-utility", - "@kbn/ui-actions-plugin", - "@kbn/kibana-utils-plugin", - "@kbn/visualizations-plugin", - "@kbn/resizable-layout", - "@kbn/shared-ux-button-toolbar", - "@kbn/calculate-width-from-char-count", - "@kbn/lens-embeddable-utils", - "@kbn/i18n-react", + "@kbn/lens-plugin", + "@kbn/data-plugin", + "@kbn/field-formats-plugin", + "@kbn/data-view-utils", "@kbn/field-utils", "@kbn/esql-utils", - "@kbn/discover-utils", - "@kbn/visualization-utils", - "@kbn/search-types", + "@kbn/i18n", + "@kbn/test-jest-helpers", + "@kbn/core", + "@kbn/shared-ux-button-toolbar", + "@kbn/es-query", "@kbn/presentation-publishing", - "@kbn/data-view-utils", - ], - "exclude": [ - "target/**/*", + "@kbn/inspector-plugin", + "@kbn/search-types", + "@kbn/discover-utils", + "@kbn/ui-actions-plugin", + "@kbn/core-ui-settings-browser-mocks", + "@kbn/core-ui-settings-browser", + "@kbn/datemath", + "@kbn/shared-ux-utility", + "@kbn/i18n-react", + "@kbn/calculate-width-from-char-count", + "@kbn/resizable-layout", + "@kbn/visualization-utils", + "@kbn/visualizations-plugin", + "@kbn/kibana-utils-plugin", + "@kbn/lens-embeddable-utils" ] } diff --git a/src/platform/plugins/shared/unified_histogram/public/types.ts b/src/platform/packages/shared/kbn-unified-histogram/types.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/types.ts rename to src/platform/packages/shared/kbn-unified-histogram/types.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/utils/__snapshots__/external_vis_context.test.ts.snap b/src/platform/packages/shared/kbn-unified-histogram/utils/__snapshots__/external_vis_context.test.ts.snap similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/utils/__snapshots__/external_vis_context.test.ts.snap rename to src/platform/packages/shared/kbn-unified-histogram/utils/__snapshots__/external_vis_context.test.ts.snap diff --git a/src/platform/plugins/shared/unified_histogram/public/utils/external_vis_context.test.ts b/src/platform/packages/shared/kbn-unified-histogram/utils/external_vis_context.test.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/utils/external_vis_context.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/utils/external_vis_context.test.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/utils/external_vis_context.ts b/src/platform/packages/shared/kbn-unified-histogram/utils/external_vis_context.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/utils/external_vis_context.ts rename to src/platform/packages/shared/kbn-unified-histogram/utils/external_vis_context.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/utils/lens_vis_from_table.ts b/src/platform/packages/shared/kbn-unified-histogram/utils/lens_vis_from_table.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/utils/lens_vis_from_table.ts rename to src/platform/packages/shared/kbn-unified-histogram/utils/lens_vis_from_table.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/container/utils/local_storage_utils.test.ts b/src/platform/packages/shared/kbn-unified-histogram/utils/local_storage_utils.test.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/container/utils/local_storage_utils.test.ts rename to src/platform/packages/shared/kbn-unified-histogram/utils/local_storage_utils.test.ts diff --git a/src/platform/plugins/shared/unified_histogram/public/container/utils/local_storage_utils.ts b/src/platform/packages/shared/kbn-unified-histogram/utils/local_storage_utils.ts similarity index 97% rename from src/platform/plugins/shared/unified_histogram/public/container/utils/local_storage_utils.ts rename to src/platform/packages/shared/kbn-unified-histogram/utils/local_storage_utils.ts index 5ba77c6dbeaf..3d91796f0329 100644 --- a/src/platform/plugins/shared/unified_histogram/public/container/utils/local_storage_utils.ts +++ b/src/platform/packages/shared/kbn-unified-histogram/utils/local_storage_utils.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { Storage } from '@kbn/kibana-utils-plugin/public'; +import type { Storage } from '@kbn/kibana-utils-plugin/public'; export const CHART_HIDDEN_KEY = 'chartHidden'; export const HISTOGRAM_HEIGHT_KEY = 'histogramHeight'; diff --git a/src/platform/plugins/shared/unified_histogram/public/container/utils/state_selectors.ts b/src/platform/packages/shared/kbn-unified-histogram/utils/state_selectors.ts similarity index 100% rename from src/platform/plugins/shared/unified_histogram/public/container/utils/state_selectors.ts rename to src/platform/packages/shared/kbn-unified-histogram/utils/state_selectors.ts diff --git a/src/platform/plugins/shared/discover/kibana.jsonc b/src/platform/plugins/shared/discover/kibana.jsonc index f9b2ea14cbc6..b3d4080633a4 100644 --- a/src/platform/plugins/shared/discover/kibana.jsonc +++ b/src/platform/plugins/shared/discover/kibana.jsonc @@ -30,7 +30,6 @@ "expressions", "unifiedDocViewer", "unifiedSearch", - "unifiedHistogram", "contentManagement", "discoverShared" ], diff --git a/src/platform/plugins/shared/discover/public/__mocks__/discover_state.mock.ts b/src/platform/plugins/shared/discover/public/__mocks__/discover_state.mock.ts index bc1f4697b12f..4cd4c47421aa 100644 --- a/src/platform/plugins/shared/discover/public/__mocks__/discover_state.mock.ts +++ b/src/platform/plugins/shared/discover/public/__mocks__/discover_state.mock.ts @@ -17,12 +17,14 @@ import type { RuntimeStateManager } from '../application/main/state_management/r import { createInternalStateStore, createRuntimeStateManager, + selectTabRuntimeState, } from '../application/main/state_management/redux'; import type { DiscoverServices, HistoryLocationState } from '../build_services'; import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public'; import type { History } from 'history'; import type { DiscoverCustomizationContext } from '../customizations'; +import { createCustomizationService } from '../customizations/customization_service'; export function getDiscoverStateMock({ isTimeBased = true, @@ -71,6 +73,15 @@ export function getDiscoverStateMock({ internalState, runtimeStateManager, }); + const tabRuntimeState = selectTabRuntimeState( + runtimeStateManager, + internalState.getState().tabs.unsafeCurrentId + ); + tabRuntimeState.customizationService$.next({ + ...createCustomizationService(), + cleanup: async () => {}, + }); + tabRuntimeState.stateContainer$.next(container); if (savedSearch !== false) { container.savedSearchState.set( savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock diff --git a/src/platform/plugins/shared/discover/public/__mocks__/services.ts b/src/platform/plugins/shared/discover/public/__mocks__/services.ts index 4ed0dbf98048..f67c46f6d694 100644 --- a/src/platform/plugins/shared/discover/public/__mocks__/services.ts +++ b/src/platform/plugins/shared/discover/public/__mocks__/services.ts @@ -63,6 +63,9 @@ export function createDiscoverServicesMock(): DiscoverServices { dataPlugin.query.timefilter.timefilter.getTime = jest.fn(() => { return { from: 'now-15m', to: 'now' }; }); + dataPlugin.query.timefilter.timefilter.getTimeDefaults = jest.fn(() => { + return { from: 'now-15m', to: 'now' }; + }); dataPlugin.query.timefilter.timefilter.getRefreshInterval = jest.fn(() => { return { pause: true, value: 1000 }; }); diff --git a/src/platform/plugins/shared/discover/public/application/main/components/chart/chart_portals_renderer.tsx b/src/platform/plugins/shared/discover/public/application/main/components/chart/chart_portals_renderer.tsx new file mode 100644 index 000000000000..535dc913335c --- /dev/null +++ b/src/platform/plugins/shared/discover/public/application/main/components/chart/chart_portals_renderer.tsx @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { type PropsWithChildren, useCallback, useEffect, useRef } from 'react'; +import { type HtmlPortalNode, InPortal, createHtmlPortalNode } from 'react-reverse-portal'; +import { UnifiedHistogramChart, useUnifiedHistogram } from '@kbn/unified-histogram'; +import { DiscoverCustomizationProvider } from '../../../../customizations'; +import { + useInternalStateSelector, + type RuntimeStateManager, + selectTabRuntimeState, + useRuntimeState, + CurrentTabProvider, + RuntimeStateProvider, + useCurrentTabSelector, +} from '../../state_management/redux'; +import type { DiscoverMainContentProps } from '../layout/discover_main_content'; +import { DiscoverMainProvider } from '../../state_management/discover_state_provider'; +import type { DiscoverStateContainer } from '../../state_management/discover_state'; +import { useIsEsqlMode } from '../../hooks/use_is_esql_mode'; +import { useDiscoverHistogram } from './use_discover_histogram'; + +export type ChartPortalNode = HtmlPortalNode; +export type ChartPortalNodes = Record; + +export const ChartPortalsRenderer = ({ + runtimeStateManager, + children, +}: PropsWithChildren<{ + runtimeStateManager: RuntimeStateManager; +}>) => { + const allTabIds = useInternalStateSelector((state) => state.tabs.allIds); + const currentTabId = useInternalStateSelector((state) => state.tabs.unsafeCurrentId); + const chartPortalNodes = useRef({}); + + chartPortalNodes.current = updatePortals(chartPortalNodes.current, allTabIds); + + return ( + <> + {Object.keys(chartPortalNodes.current).map((tabId) => { + return ( + + + + ); + })} + + {children} + + + ); +}; + +const updatePortals = (portals: ChartPortalNodes, tabsIds: string[]) => + tabsIds.reduce( + (acc, tabId) => ({ + ...acc, + [tabId]: portals[tabId] || createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }), + }), + {} + ); + +interface UnifiedHistogramGuardProps { + tabId: string; + runtimeStateManager: RuntimeStateManager; + panelsToggle?: DiscoverMainContentProps['panelsToggle']; +} + +const UnifiedHistogramGuard = ({ + tabId, + runtimeStateManager, + panelsToggle, +}: UnifiedHistogramGuardProps) => { + const isSelected = useInternalStateSelector((state) => state.tabs.unsafeCurrentId === tabId); + const currentTabRuntimeState = selectTabRuntimeState(runtimeStateManager, tabId); + const currentCustomizationService = useRuntimeState(currentTabRuntimeState.customizationService$); + const currentStateContainer = useRuntimeState(currentTabRuntimeState.stateContainer$); + const currentDataView = useRuntimeState(currentTabRuntimeState.currentDataView$); + const adHocDataViews = useRuntimeState(runtimeStateManager.adHocDataViews$); + const isInitialized = useRef(false); + + if ( + (!isSelected && !isInitialized.current) || + !currentCustomizationService || + !currentStateContainer || + !currentDataView || + !currentTabRuntimeState + ) { + return null; + } + + isInitialized.current = true; + + return ( + + + + + + + + + + ); +}; + +type UnifiedHistogramChartProps = Pick & { + stateContainer: DiscoverStateContainer; +}; + +const UnifiedHistogramChartWrapper = ({ + stateContainer, + panelsToggle, +}: UnifiedHistogramChartProps) => { + const { setUnifiedHistogramApi, ...unifiedHistogramProps } = useDiscoverHistogram(stateContainer); + const unifiedHistogram = useUnifiedHistogram(unifiedHistogramProps); + + useEffect(() => { + if (unifiedHistogram.isInitialized) { + setUnifiedHistogramApi(unifiedHistogram.api); + } + }, [setUnifiedHistogramApi, unifiedHistogram.api, unifiedHistogram.isInitialized]); + + const currentTabId = useCurrentTabSelector((tab) => tab.id); + + useEffect(() => { + if (unifiedHistogram.layoutProps) { + const currentTabRuntimeState = selectTabRuntimeState( + stateContainer.runtimeStateManager, + currentTabId + ); + currentTabRuntimeState.unifiedHistogramLayoutProps$.next(unifiedHistogram.layoutProps); + } + }, [currentTabId, stateContainer.runtimeStateManager, unifiedHistogram.layoutProps]); + + const isEsqlMode = useIsEsqlMode(); + const renderCustomChartToggleActions = useCallback( + () => + React.isValidElement(panelsToggle) + ? React.cloneElement(panelsToggle, { renderedFor: 'histogram' }) + : panelsToggle, + [panelsToggle] + ); + + // Initialized when the first search has been requested or + // when in ES|QL mode since search sessions are not supported + if (!unifiedHistogram.isInitialized || (!unifiedHistogramProps.searchSessionId && !isEsqlMode)) { + return null; + } + + return ( + + ); +}; diff --git a/src/platform/plugins/shared/unified_histogram/public/layout/index.ts b/src/platform/plugins/shared/discover/public/application/main/components/chart/index.ts similarity index 80% rename from src/platform/plugins/shared/unified_histogram/public/layout/index.ts rename to src/platform/plugins/shared/discover/public/application/main/components/chart/index.ts index dd8f79cc2e4a..44a874e594e6 100644 --- a/src/platform/plugins/shared/unified_histogram/public/layout/index.ts +++ b/src/platform/plugins/shared/discover/public/application/main/components/chart/index.ts @@ -7,5 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export type { UnifiedHistogramLayoutProps } from './layout'; -export { UnifiedHistogramLayout } from './layout'; +export { type ChartPortalNode, ChartPortalsRenderer } from './chart_portals_renderer'; diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/use_discover_histogram.test.tsx b/src/platform/plugins/shared/discover/public/application/main/components/chart/use_discover_histogram.test.tsx similarity index 87% rename from src/platform/plugins/shared/discover/public/application/main/components/layout/use_discover_histogram.test.tsx rename to src/platform/plugins/shared/discover/public/application/main/components/chart/use_discover_histogram.test.tsx index 54246d64f264..96d1414e66ff 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/use_discover_histogram.test.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/chart/use_discover_histogram.test.tsx @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ReactElement } from 'react'; import React from 'react'; import type { AggregateQuery, Query } from '@kbn/es-query'; import { renderHook, act } from '@testing-library/react'; @@ -15,17 +14,15 @@ import { BehaviorSubject, Subject } from 'rxjs'; import { FetchStatus } from '../../../types'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; -import type { UseDiscoverHistogramProps } from './use_discover_histogram'; import { useDiscoverHistogram } from './use_discover_histogram'; import { setTimeout } from 'timers/promises'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; import { DiscoverMainProvider } from '../../state_management/discover_state_provider'; import { RequestAdapter } from '@kbn/inspector-plugin/public'; -import type { UnifiedHistogramState } from '@kbn/unified-histogram-plugin/public'; -import { UnifiedHistogramFetchStatus } from '@kbn/unified-histogram-plugin/public'; -import { createMockUnifiedHistogramApi } from '@kbn/unified-histogram-plugin/public/mocks'; +import type { UnifiedHistogramState } from '@kbn/unified-histogram'; +import { UnifiedHistogramFetchStatus } from '@kbn/unified-histogram'; +import { createMockUnifiedHistogramApi } from '@kbn/unified-histogram/mocks'; import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages'; -import type { InspectorAdapters } from '../../hooks/use_inspector'; import type { UnifiedHistogramCustomization } from '../../../../customizations/customization_types/histogram_customization'; import { useDiscoverCustomization } from '../../../../customizations'; import type { DiscoverCustomizationId } from '../../../../customizations/customization_service'; @@ -111,44 +108,26 @@ describe('useDiscoverHistogram', () => { return stateContainer; }; - const renderUseDiscoverHistogram = async ({ - stateContainer = getStateContainer(), - inspectorAdapters = { requests: new RequestAdapter() }, - hideChart = false, - }: { - stateContainer?: DiscoverStateContainer; - inspectorAdapters?: InspectorAdapters; - hideChart?: boolean; - } = {}) => { - const initialProps = { - stateContainer, - inspectorAdapters, - hideChart, - }; - + const renderUseDiscoverHistogram = async ( + stateContainer: DiscoverStateContainer = getStateContainer() + ) => { const Wrapper = ({ children }: React.PropsWithChildren) => ( - {children as ReactElement} + {children} ); - const hook = renderHook( - (props: UseDiscoverHistogramProps) => { - return useDiscoverHistogram(props); - }, - { - wrapper: Wrapper, - initialProps, - } - ); + const hook = renderHook(() => useDiscoverHistogram(stateContainer), { + wrapper: Wrapper, + }); await act(() => setTimeout(0)); - return { hook, initialProps }; + return { hook }; }; beforeEach(() => { @@ -169,9 +148,9 @@ describe('useDiscoverHistogram', () => { }); describe('initialization', () => { - it('should return the expected parameters from getCreationOptions', async () => { + it('should return the expected parameters', async () => { const { hook } = await renderUseDiscoverHistogram(); - const params = hook.result.current.getCreationOptions(); + const params = hook.result.current; expect(params?.localStorageKeyPrefix).toBe('discover'); expect(Object.keys(params?.initialState ?? {})).toEqual([ 'chartHidden', @@ -200,7 +179,7 @@ describe('useDiscoverHistogram', () => { const api = createMockUnifiedHistogramApi(); jest.spyOn(api.state$, 'subscribe'); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); expect(api.state$.subscribe).toHaveBeenCalledTimes(2); }); @@ -208,7 +187,8 @@ describe('useDiscoverHistogram', () => { it('should sync Unified Histogram state with the state container', async () => { const stateContainer = getStateContainer(); const inspectorAdapters = { requests: new RequestAdapter(), lensRequests: undefined }; - const { hook } = await renderUseDiscoverHistogram({ stateContainer, inspectorAdapters }); + stateContainer.dataState.inspectorAdapters = inspectorAdapters; + const { hook } = await renderUseDiscoverHistogram(stateContainer); const lensRequestAdapter = new RequestAdapter(); const state = { timeInterval: '1m', @@ -219,7 +199,7 @@ describe('useDiscoverHistogram', () => { const api = createMockUnifiedHistogramApi(); api.state$ = new BehaviorSubject({ ...state, lensRequestAdapter }); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); expect(inspectorAdapters.lensRequests).toBe(lensRequestAdapter); expect(stateContainer.appState.update).toHaveBeenCalledWith({ @@ -230,7 +210,7 @@ describe('useDiscoverHistogram', () => { it('should not sync Unified Histogram state with the state container if there are no changes', async () => { const stateContainer = getStateContainer(); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); const containerState = stateContainer.appState.getState(); const state = { timeInterval: containerState.interval, @@ -241,14 +221,14 @@ describe('useDiscoverHistogram', () => { const api = createMockUnifiedHistogramApi(); api.state$ = new BehaviorSubject(state); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); expect(stateContainer.appState.update).not.toHaveBeenCalled(); }); it('should sync the state container state with Unified Histogram', async () => { const stateContainer = getStateContainer(); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); const api = createMockUnifiedHistogramApi(); let params: Partial = {}; api.setTotalHits = jest.fn((p) => { @@ -261,7 +241,7 @@ describe('useDiscoverHistogram', () => { params = { ...params, timeInterval }; }); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); stateContainer.appState.update({ hideChart: true, interval: '1m' }); expect(api.setTotalHits).not.toHaveBeenCalled(); @@ -272,7 +252,7 @@ describe('useDiscoverHistogram', () => { it('should exclude totalHitsStatus and totalHitsResult from Unified Histogram state updates', async () => { const stateContainer = getStateContainer(); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); const containerState = stateContainer.appState.getState(); const state = { timeInterval: containerState.interval, @@ -288,7 +268,7 @@ describe('useDiscoverHistogram', () => { const subject$ = new BehaviorSubject(state); api.state$ = subject$; act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); stateContainer.appState.update({ hideChart: true }); expect(Object.keys(params ?? {})).toEqual(['chartHidden']); @@ -306,7 +286,7 @@ describe('useDiscoverHistogram', () => { it('should update total hits when the total hits state changes', async () => { const stateContainer = getStateContainer(); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); const containerState = stateContainer.appState.getState(); const state = { timeInterval: containerState.interval, @@ -325,7 +305,7 @@ describe('useDiscoverHistogram', () => { result: 100, }); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); expect(stateContainer.dataState.data$.totalHits$.value).toEqual({ fetchStatus: FetchStatus.COMPLETE, @@ -349,7 +329,7 @@ describe('useDiscoverHistogram', () => { mockData.query.getState = () => mockQueryState; const stateContainer = getStateContainer(); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); const containerState = stateContainer.appState.getState(); const error = new Error('test'); const state = { @@ -369,7 +349,7 @@ describe('useDiscoverHistogram', () => { error, }); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); expect(sendErrorTo).toHaveBeenCalledWith(stateContainer.dataState.data$.totalHits$); expect(stateContainer.dataState.data$.totalHits$.value).toEqual({ @@ -384,7 +364,7 @@ describe('useDiscoverHistogram', () => { const stateContainer = getStateContainer(); stateContainer.appState.update({ query: { esql: 'from *' } }); stateContainer.dataState.fetchChart$ = fetch$; - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); act(() => { fetch$.next(); }); @@ -404,7 +384,7 @@ describe('useDiscoverHistogram', () => { }, }) ); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); act(() => { fetch$.next(); }); @@ -418,10 +398,10 @@ describe('useDiscoverHistogram', () => { const savedSearchFetch$ = new Subject(); const stateContainer = getStateContainer(); stateContainer.dataState.fetchChart$ = savedSearchFetch$; - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); const api = createMockUnifiedHistogramApi(); act(() => { - hook.result.current.ref(api); + hook.result.current.setUnifiedHistogramApi(api); }); expect(api.fetch).not.toHaveBeenCalled(); act(() => { @@ -435,7 +415,7 @@ describe('useDiscoverHistogram', () => { test('should use custom values provided by customization fwk ', async () => { mockUseCustomizations = true; const stateContainer = getStateContainer(); - const { hook } = await renderUseDiscoverHistogram({ stateContainer }); + const { hook } = await renderUseDiscoverHistogram(stateContainer); expect(hook.result.current.onFilter).toEqual(mockHistogramCustomization.onFilter); expect(hook.result.current.onBrushEnd).toEqual(mockHistogramCustomization.onBrushEnd); diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/use_discover_histogram.ts b/src/platform/plugins/shared/discover/public/application/main/components/chart/use_discover_histogram.ts similarity index 86% rename from src/platform/plugins/shared/discover/public/application/main/components/layout/use_discover_histogram.ts rename to src/platform/plugins/shared/discover/public/application/main/components/chart/use_discover_histogram.ts index 8f28ef2f0dc1..b80f77b6b242 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/use_discover_histogram.ts +++ b/src/platform/plugins/shared/discover/public/application/main/components/chart/use_discover_histogram.ts @@ -10,16 +10,15 @@ import { useQuerySubscriber } from '@kbn/unified-field-list/src/hooks/use_query_subscriber'; import type { UnifiedHistogramApi, - UnifiedHistogramContainerProps, - UnifiedHistogramCreationOptions, UnifiedHistogramState, UnifiedHistogramVisContext, -} from '@kbn/unified-histogram-plugin/public'; + UseUnifiedHistogramProps, +} from '@kbn/unified-histogram'; import { canImportVisContext, UnifiedHistogramExternalVisContextStatus, UnifiedHistogramFetchStatus, -} from '@kbn/unified-histogram-plugin/public'; +} from '@kbn/unified-histogram'; import { isEqual } from 'lodash'; import { useCallback, useEffect, useMemo, useState } from 'react'; import type { Observable } from 'rxjs'; @@ -43,7 +42,6 @@ import { ESQL_TABLE_TYPE } from '@kbn/data-plugin/common'; import { useDiscoverCustomization } from '../../../../customizations'; import { useDiscoverServices } from '../../../../hooks/use_discover_services'; import { FetchStatus } from '../../../types'; -import type { InspectorAdapters } from '../../hooks/use_inspector'; import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages'; import type { DiscoverStateContainer } from '../../state_management/discover_state'; import { addLog } from '../../../../utils/add_log'; @@ -65,25 +63,15 @@ import { const EMPTY_ESQL_COLUMNS: DatatableColumn[] = []; const EMPTY_FILTERS: Filter[] = []; -export interface UseDiscoverHistogramProps { - stateContainer: DiscoverStateContainer; - inspectorAdapters: InspectorAdapters; - hideChart: boolean | undefined; -} - -export const useDiscoverHistogram = ({ - stateContainer, - inspectorAdapters, - hideChart, -}: UseDiscoverHistogramProps): Omit< - UnifiedHistogramContainerProps, - 'container' | 'getCreationOptions' -> & { - ref: (api: UnifiedHistogramApi | null) => void; - getCreationOptions: () => UnifiedHistogramCreationOptions; -} => { +export const useDiscoverHistogram = ( + stateContainer: DiscoverStateContainer +): UseUnifiedHistogramProps & { setUnifiedHistogramApi: (api: UnifiedHistogramApi) => void } => { const services = useDiscoverServices(); - const { main$, documents$, totalHits$ } = stateContainer.dataState.data$; + const { + data$: { main$, documents$, totalHits$ }, + inspectorAdapters, + getAbortController, + } = stateContainer.dataState; const savedSearchState = useSavedSearch(); const isEsqlMode = useIsEsqlMode(); @@ -91,53 +79,38 @@ export const useDiscoverHistogram = ({ * API initialization */ - const [unifiedHistogram, ref] = useState(); + const [unifiedHistogramApi, setUnifiedHistogramApi] = useState(); const [isSuggestionLoading, setIsSuggestionLoading] = useState(false); - const getCreationOptions = useCallback(() => { - const { hideChart: chartHidden, interval: timeInterval } = stateContainer.appState.getState(); - - return { - localStorageKeyPrefix: 'discover', - disableAutoFetching: true, - initialState: { - chartHidden, - timeInterval, - totalHitsStatus: UnifiedHistogramFetchStatus.loading, - totalHitsResult: undefined, - }, - }; - }, [stateContainer.appState]); - /** * Sync Unified Histogram state with Discover state */ useEffect(() => { - const subscription = createUnifiedHistogramStateObservable(unifiedHistogram?.state$)?.subscribe( - (changes) => { - const { lensRequestAdapter, ...stateChanges } = changes; - const appState = stateContainer.appState.getState(); - const oldState = { - hideChart: appState.hideChart, - interval: appState.interval, - }; - const newState = { ...oldState, ...stateChanges }; + const subscription = createUnifiedHistogramStateObservable( + unifiedHistogramApi?.state$ + )?.subscribe((changes) => { + const { lensRequestAdapter, ...stateChanges } = changes; + const appState = stateContainer.appState.getState(); + const oldState = { + hideChart: appState.hideChart, + interval: appState.interval, + }; + const newState = { ...oldState, ...stateChanges }; - if ('lensRequestAdapter' in changes) { - inspectorAdapters.lensRequests = lensRequestAdapter; - } - - if (!isEqual(oldState, newState)) { - stateContainer.appState.update(newState); - } + if ('lensRequestAdapter' in changes) { + inspectorAdapters.lensRequests = lensRequestAdapter; } - ); + + if (!isEqual(oldState, newState)) { + stateContainer.appState.update(newState); + } + }); return () => { subscription?.unsubscribe(); }; - }, [inspectorAdapters, stateContainer.appState, unifiedHistogram?.state$]); + }, [inspectorAdapters, stateContainer.appState, unifiedHistogramApi?.state$]); /** * Sync URL query params with Unified Histogram @@ -147,11 +120,11 @@ export const useDiscoverHistogram = ({ const subscription = createAppStateObservable(stateContainer.appState.state$).subscribe( (changes) => { if ('timeInterval' in changes && changes.timeInterval) { - unifiedHistogram?.setTimeInterval(changes.timeInterval); + unifiedHistogramApi?.setTimeInterval(changes.timeInterval); } if ('chartHidden' in changes && typeof changes.chartHidden === 'boolean') { - unifiedHistogram?.setChartHidden(changes.chartHidden); + unifiedHistogramApi?.setChartHidden(changes.chartHidden); } } ); @@ -159,7 +132,7 @@ export const useDiscoverHistogram = ({ return () => { subscription?.unsubscribe(); }; - }, [stateContainer.appState.state$, unifiedHistogram]); + }, [stateContainer.appState.state$, unifiedHistogramApi]); /** * Total hits @@ -168,7 +141,7 @@ export const useDiscoverHistogram = ({ const setTotalHitsError = useMemo(() => sendErrorTo(totalHits$), [totalHits$]); useEffect(() => { - const subscription = createTotalHitsObservable(unifiedHistogram?.state$)?.subscribe( + const subscription = createTotalHitsObservable(unifiedHistogramApi?.state$)?.subscribe( ({ status, result }) => { if (isEsqlMode) { // ignore histogram's total hits updates for ES|QL as Discover manages them during docs fetching @@ -221,7 +194,7 @@ export const useDiscoverHistogram = ({ totalHits$, setTotalHitsError, stateContainer.appState, - unifiedHistogram?.state$, + unifiedHistogramApi?.state$, ]); /** @@ -282,7 +255,7 @@ export const useDiscoverHistogram = ({ // Handle unified histogram refetching useEffect(() => { - if (!unifiedHistogram) { + if (!unifiedHistogramApi) { return; } @@ -297,7 +270,7 @@ export const useDiscoverHistogram = ({ // a refetch anyway and result in multiple unnecessary fetches. if (isEsqlMode) { fetchChart$ = merge( - createCurrentSuggestionObservable(unifiedHistogram.state$).pipe(map(() => 'lens')), + createCurrentSuggestionObservable(unifiedHistogramApi.state$).pipe(map(() => 'lens')), esqlFetchComplete$.pipe(map(() => 'discover')) ).pipe(debounceTime(50)); } else { @@ -307,13 +280,13 @@ export const useDiscoverHistogram = ({ const subscription = fetchChart$.subscribe((source) => { if (source === 'discover') addLog('Unified Histogram - Discover refetch'); if (source === 'lens') addLog('Unified Histogram - Lens suggestion refetch'); - unifiedHistogram.fetch(); + unifiedHistogramApi.fetch(); }); return () => { subscription.unsubscribe(); }; - }, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogram]); + }, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogramApi]); const dataView = useCurrentDataView(); @@ -380,10 +353,12 @@ export const useDiscoverHistogram = ({ [dispatch, setOverriddenVisContextAfterInvalidation, stateContainer.savedSearchState] ); + const chartHidden = useAppStateSelector((state) => state.hideChart); + const timeInterval = useAppStateSelector((state) => state.interval); const breakdownField = useAppStateSelector((state) => state.breakdownField); const onBreakdownFieldChange = useCallback< - NonNullable + NonNullable >( (nextBreakdownField) => { if (nextBreakdownField !== breakdownField) { @@ -394,9 +369,17 @@ export const useDiscoverHistogram = ({ ); return { - ref, - getCreationOptions, + setUnifiedHistogramApi, services, + localStorageKeyPrefix: 'discover', + requestAdapter: inspectorAdapters.requests, + abortController: getAbortController(), + initialState: { + chartHidden, + timeInterval, + totalHitsStatus: UnifiedHistogramFetchStatus.loading, + totalHitsResult: undefined, + }, dataView: isEsqlMode ? esqlDataView : dataView, query: isEsqlMode ? esqlQuery : query, filters: filtersMemoized, diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx index eb9de0e297d7..e7746f2f6868 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.test.tsx @@ -36,10 +36,17 @@ import { act } from 'react-dom/test-utils'; import { PanelsToggle } from '../../../../components/panels_toggle'; import { createDataViewDataSource } from '../../../../../common/data_sources'; import { - CurrentTabProvider, + InternalStateProvider, RuntimeStateProvider, internalStateActions, } from '../../state_management/redux'; +import { ChartPortalsRenderer } from '../chart'; +import { UnifiedHistogramChart } from '@kbn/unified-histogram'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + useResizeObserver: jest.fn(() => ({ width: 1000, height: 1000 })), +})); function getStateContainer({ savedSearch, @@ -155,13 +162,15 @@ const mountComponent = async ({ const component = mountWithIntl( - - - - - - - + + + + + + + + + ); @@ -177,19 +186,19 @@ const mountComponent = async ({ describe('Discover histogram layout component', () => { describe('render', () => { - it('should render null if there is no search session', async () => { + it('should not render chart if there is no search session', async () => { const { component } = await mountComponent({ searchSessionId: null }); - expect(component.isEmptyRender()).toBe(true); + expect(component.exists(UnifiedHistogramChart)).toBe(false); }); - it('should not render null if there is a search session', async () => { + it('should render chart if there is a search session', async () => { const { component } = await mountComponent(); - expect(component.isEmptyRender()).toBe(false); + expect(component.exists(UnifiedHistogramChart)).toBe(true); }, 10000); - it('should not render null if there is no search session, but isEsqlMode is true', async () => { + it('should render chart if there is no search session, but isEsqlMode is true', async () => { const { component } = await mountComponent({ isEsqlMode: true }); - expect(component.isEmptyRender()).toBe(false); + expect(component.exists(UnifiedHistogramChart)).toBe(true); }); it('should render PanelsToggle', async () => { diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.tsx b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.tsx index 2e1ece492170..38c897fd07bd 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_histogram_layout.tsx @@ -7,67 +7,40 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { useCallback } from 'react'; -import { UnifiedHistogramContainer } from '@kbn/unified-histogram-plugin/public'; -import { css } from '@emotion/react'; -import { useDiscoverHistogram } from './use_discover_histogram'; +import React from 'react'; +import { UnifiedHistogramLayout } from '@kbn/unified-histogram'; +import { OutPortal } from 'react-reverse-portal'; import { type DiscoverMainContentProps, DiscoverMainContent } from './discover_main_content'; -import { useAppStateSelector } from '../../state_management/discover_app_state_container'; -import { useIsEsqlMode } from '../../hooks/use_is_esql_mode'; +import { useCurrentChartPortalNode, useCurrentTabRuntimeState } from '../../state_management/redux'; export interface DiscoverHistogramLayoutProps extends DiscoverMainContentProps { container: HTMLElement | null; } -const histogramLayoutCss = css` - height: 100%; -`; - export const DiscoverHistogramLayout = ({ - dataView, - stateContainer, container, panelsToggle, ...mainContentProps }: DiscoverHistogramLayoutProps) => { - const { dataState } = stateContainer; - const hideChart = useAppStateSelector((state) => state.hideChart); - const isEsqlMode = useIsEsqlMode(); - const unifiedHistogramProps = useDiscoverHistogram({ - stateContainer, - inspectorAdapters: dataState.inspectorAdapters, - hideChart, - }); - - const renderCustomChartToggleActions = useCallback( - () => - React.isValidElement(panelsToggle) - ? React.cloneElement(panelsToggle, { renderedFor: 'histogram' }) - : panelsToggle, - [panelsToggle] + const chartPortalNode = useCurrentChartPortalNode(); + const layoutProps = useCurrentTabRuntimeState( + mainContentProps.stateContainer.runtimeStateManager, + (tab) => tab.unifiedHistogramLayoutProps$ ); - // Initialized when the first search has been requested or - // when in ES|QL mode since search sessions are not supported - if (!unifiedHistogramProps.searchSessionId && !isEsqlMode) { + if (!layoutProps) { return null; } return ( - : null + } + {...layoutProps} > - - + + ); }; diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.test.tsx b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.test.tsx index c2fcd7ed0ecc..4bae1f67bd3f 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.test.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.test.tsx @@ -40,10 +40,11 @@ import { ErrorCallout } from '../../../../components/common/error_callout'; import { PanelsToggle } from '../../../../components/panels_toggle'; import { createDataViewDataSource } from '../../../../../common/data_sources'; import { - CurrentTabProvider, + InternalStateProvider, RuntimeStateProvider, internalStateActions, } from '../../state_management/redux'; +import { ChartPortalsRenderer } from '../chart'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), @@ -137,15 +138,17 @@ async function mountComponent( const component = mountWithIntl( - - - - - - - - - + + + + + + + + + + + , mountOptions ); diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx index c3a06726d176..6f36a3d32e50 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx @@ -49,7 +49,6 @@ import type { SidebarToggleState } from '../../../types'; import { FetchStatus } from '../../../types'; import { useDataState } from '../../hooks/use_data_state'; import { SavedSearchURLConflictCallout } from '../../../../components/saved_search_url_conflict_callout/saved_search_url_conflict_callout'; -import { DiscoverHistogramLayout } from './discover_histogram_layout'; import { ErrorCallout } from '../../../../components/common/error_callout'; import { addLog } from '../../../../utils/add_log'; import { DiscoverResizableLayout } from './discover_resizable_layout'; @@ -59,6 +58,7 @@ import { sendErrorMsg } from '../../hooks/use_saved_search_messages'; import { useIsEsqlMode } from '../../hooks/use_is_esql_mode'; import { useCurrentDataView, useCurrentTabSelector } from '../../state_management/redux'; import { TABS_ENABLED } from '../../../../constants'; +import { DiscoverHistogramLayout } from './discover_histogram_layout'; const SidebarMemoized = React.memo(DiscoverSidebarResponsive); const TopNavMemoized = React.memo(DiscoverTopNav); diff --git a/src/platform/plugins/shared/discover/public/application/main/components/tabs_view/tabs_view.tsx b/src/platform/plugins/shared/discover/public/application/main/components/tabs_view/tabs_view.tsx index c8902b2e3ed4..bb595e3ff780 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/tabs_view/tabs_view.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/tabs_view/tabs_view.tsx @@ -12,7 +12,6 @@ import React, { useState } from 'react'; import { pick } from 'lodash'; import { DiscoverSessionView, type DiscoverSessionViewProps } from '../session_view'; import { - CurrentTabProvider, createTabItem, internalStateActions, selectAllTabs, @@ -34,17 +33,10 @@ export const TabsView = (props: DiscoverSessionViewProps) => { { - const updateTabsAction = internalStateActions.updateTabs(updateState); - return dispatch(updateTabsAction); - }} + onChanged={(updateState) => dispatch(internalStateActions.updateTabs(updateState))} createItem={() => createTabItem(allTabs)} getPreviewData={getPreviewData} - renderContent={() => ( - - - - )} + renderContent={() => } /> ); }; diff --git a/src/platform/plugins/shared/discover/public/application/main/discover_main_route.tsx b/src/platform/plugins/shared/discover/public/application/main/discover_main_route.tsx index 9d972d3cb536..42208f15c28e 100644 --- a/src/platform/plugins/shared/discover/public/application/main/discover_main_route.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/discover_main_route.tsx @@ -21,7 +21,6 @@ import { createInternalStateStore, createRuntimeStateManager, internalStateActions, - CurrentTabProvider, } from './state_management/redux'; import type { RootProfileState } from '../../context_awareness'; import { useRootProfile, useDefaultAdHocDataViews } from '../../context_awareness'; @@ -35,6 +34,7 @@ import { import { useAsyncFunction } from './hooks/use_async_function'; import { TabsView } from './components/tabs_view'; import { TABS_ENABLED } from '../../constants'; +import { ChartPortalsRenderer } from './components/chart'; export interface MainRouteProps { customizationContext: DiscoverCustomizationContext; @@ -142,13 +142,13 @@ export const DiscoverMainRoute = ({ return ( - {TABS_ENABLED ? ( - - ) : ( - + + {TABS_ENABLED ? ( + + ) : ( - - )} + )} + ); diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts index b379990e3044..1a590cec5b68 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/discover_saved_search_container.ts @@ -15,8 +15,8 @@ import type { FilterCompareOptions } from '@kbn/es-query'; import { COMPARE_ALL_OPTIONS, isOfAggregateQueryType, updateFilterReferences } from '@kbn/es-query'; import type { SearchSourceFields } from '@kbn/data-plugin/common'; import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common'; -import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram-plugin/public'; -import { canImportVisContext } from '@kbn/unified-histogram-plugin/public'; +import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram'; +import { canImportVisContext } from '@kbn/unified-histogram'; import type { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public'; import { isEqual, isFunction } from 'lodash'; import { i18n } from '@kbn/i18n'; diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/initialize_session.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/initialize_session.ts index 277b5cdd1c1f..9dc8dff5fc13 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/initialize_session.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/actions/initialize_session.ts @@ -68,12 +68,31 @@ export const initializeSession: InternalStateThunkActionCreator< dispatch(disconnectTab({ tabId })); dispatch(internalStateSlice.actions.resetOnSavedSearchChange({ tabId })); + const discoverSessionLoadTracker = + services.ebtManager.trackPerformanceEvent('discoverLoadSavedSearch'); + const { currentDataView$, stateContainer$, customizationService$ } = selectTabRuntimeState( + runtimeStateManager, + tabId + ); + + /** + * New tab initialization or existing tab re-initialization + */ + + const wasTabInitialized = Boolean(stateContainer$.getValue()); + + if (wasTabInitialized) { + // Clear existing runtime state on re-initialization + // to ensure no stale state is used during loading + currentDataView$.next(undefined); + stateContainer$.next(undefined); + customizationService$.next(undefined); + } + /** * "No data" checks */ - const discoverSessionLoadTracker = - services.ebtManager.trackPerformanceEvent('discoverLoadSavedSearch'); const urlState = cleanupUrlState( defaultUrlState ?? urlStateStorage.get(APP_STATE_URL_KEY), services.uiSettings @@ -124,10 +143,6 @@ export const initializeSession: InternalStateThunkActionCreator< setBreadcrumbs({ services, titleBreadcrumbText: persistedDiscoverSession.title }); } - const { currentDataView$, stateContainer$, customizationService$ } = selectTabRuntimeState( - runtimeStateManager, - tabId - ); let dataView: DataView; if (isOfAggregateQueryType(initialQuery)) { diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx index 48aae430182a..645e8f6729be 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/hooks.tsx @@ -15,7 +15,8 @@ import { createDispatchHook, createSelectorHook, } from 'react-redux'; -import React, { type PropsWithChildren, useMemo, createContext } from 'react'; +import type { PropsWithChildren } from 'react'; +import React, { useMemo, createContext } from 'react'; import { useAdHocDataViews } from './runtime_state'; import type { DiscoverInternalState, TabState } from './types'; import { @@ -25,6 +26,7 @@ import { } from './internal_state'; import { selectTab } from './selectors'; import { type TabActionInjector, createTabActionInjector } from './utils'; +import type { ChartPortalNode } from '../../components/chart'; const internalStateContext = createContext( // Recommended approach for versions of Redux prior to v9: @@ -49,6 +51,7 @@ export const useInternalStateSelector: TypedUseSelectorHook(unde export const CurrentTabProvider = ({ currentTabId, + currentChartPortalNode, children, -}: PropsWithChildren<{ currentTabId: string }>) => { +}: PropsWithChildren<{ currentTabId: string; currentChartPortalNode?: ChartPortalNode }>) => { const contextValue = useMemo( - () => ({ currentTabId, injectCurrentTab: createTabActionInjector(currentTabId) }), - [currentTabId] + () => ({ + currentTabId, + currentChartPortalNode, + injectCurrentTab: createTabActionInjector(currentTabId), + }), + [currentChartPortalNode, currentTabId] ); return {children}; @@ -88,6 +96,8 @@ export const useCurrentTabAction = ( return useMemo(() => injectCurrentTab(actionCreator), [actionCreator, injectCurrentTab]); }; +export const useCurrentChartPortalNode = () => useCurrentTabContext().currentChartPortalNode; + export const useDataViewsForPicker = () => { const originalAdHocDataViews = useAdHocDataViews(); const savedDataViews = useInternalStateSelector((state) => state.savedDataViews); diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/index.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/index.ts index 4116b8eb3d5f..fc821aa4c764 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/index.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/index.ts @@ -52,6 +52,7 @@ export { CurrentTabProvider, useCurrentTabSelector, useCurrentTabAction, + useCurrentChartPortalNode, useDataViewsForPicker, } from './hooks'; diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts index 6007abdf1181..e95846920f78 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/internal_state.ts @@ -193,11 +193,16 @@ export interface InternalStateThunkDependencies { urlStateStorage: IKbnUrlStateStorage; } +const IS_JEST_ENVIRONMENT = typeof jest !== 'undefined'; + export const createInternalStateStore = (options: InternalStateThunkDependencies) => { const store = configureStore({ reducer: internalStateSlice.reducer, middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ thunk: { extraArgument: options } }), + getDefaultMiddleware({ + thunk: { extraArgument: options }, + serializableCheck: !IS_JEST_ENVIRONMENT, + }), }); // TEMPORARY: Create initial default tab diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/runtime_state.tsx b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/runtime_state.tsx index ae4bd3c61dfc..ed2cfa050f7d 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/runtime_state.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/runtime_state.tsx @@ -11,6 +11,7 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject } from 'rxjs'; +import type { UnifiedHistogramPartialLayoutProps } from '@kbn/unified-histogram'; import { useCurrentTabContext } from './hooks'; import type { DiscoverStateContainer } from '../discover_state'; import type { ConnectedCustomizationService } from '../../../../customizations'; @@ -22,6 +23,7 @@ interface DiscoverRuntimeState { interface TabRuntimeState { stateContainer?: DiscoverStateContainer; customizationService?: ConnectedCustomizationService; + unifiedHistogramLayoutProps?: UnifiedHistogramPartialLayoutProps; currentDataView: DataView; } @@ -45,6 +47,9 @@ export const createRuntimeStateManager = (): RuntimeStateManager => ({ export const createTabRuntimeState = (): ReactiveTabRuntimeState => ({ stateContainer$: new BehaviorSubject(undefined), customizationService$: new BehaviorSubject(undefined), + unifiedHistogramLayoutProps$: new BehaviorSubject( + undefined + ), currentDataView$: new BehaviorSubject(undefined), }); diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/selectors.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/selectors.ts index bdeac56eaa27..382318a9d025 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/selectors.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/selectors.ts @@ -7,9 +7,15 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { createSelector } from '@reduxjs/toolkit'; import type { DiscoverInternalState } from './types'; export const selectTab = (state: DiscoverInternalState, tabId: string) => state.tabs.byId[tabId]; -export const selectAllTabs = (state: DiscoverInternalState) => - state.tabs.allIds.map((id) => selectTab(state, id)); +export const selectAllTabs = createSelector( + [ + (state: DiscoverInternalState) => state.tabs.allIds, + (state: DiscoverInternalState) => state.tabs.byId, + ], + (allIds, byId) => allIds.map((id) => byId[id]) +); diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts index 7799ee913300..4e5afab25577 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/redux/types.ts @@ -11,7 +11,7 @@ import type { RefreshInterval } from '@kbn/data-plugin/common'; import type { DataViewListItem } from '@kbn/data-views-plugin/public'; import type { DataTableRecord } from '@kbn/discover-utils'; import type { Filter, TimeRange } from '@kbn/es-query'; -import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram-plugin/public'; +import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram'; import type { TabItem } from '@kbn/unified-tabs'; export enum LoadingStatus { diff --git a/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_state_defaults.ts b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_state_defaults.ts index f448128f2f63..9932d2363f07 100644 --- a/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_state_defaults.ts +++ b/src/platform/plugins/shared/discover/public/application/main/state_management/utils/get_state_defaults.ts @@ -10,7 +10,7 @@ import { cloneDeep } from 'lodash'; import type { IUiSettingsClient } from '@kbn/core/public'; import type { SavedSearch } from '@kbn/saved-search-plugin/public'; -import { getChartHidden } from '@kbn/unified-histogram-plugin/public'; +import { getChartHidden } from '@kbn/unified-histogram'; import { DEFAULT_COLUMNS_SETTING, DOC_HIDE_TIME_COLUMN_SETTING, diff --git a/src/platform/plugins/shared/discover/public/customizations/customization_types/histogram_customization.tsx b/src/platform/plugins/shared/discover/public/customizations/customization_types/histogram_customization.tsx index 6cb6a23f95e6..41ff96950d44 100644 --- a/src/platform/plugins/shared/discover/public/customizations/customization_types/histogram_customization.tsx +++ b/src/platform/plugins/shared/discover/public/customizations/customization_types/histogram_customization.tsx @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { UnifiedHistogramContainerProps } from '@kbn/unified-histogram-plugin/public'; +import type { UseUnifiedHistogramProps } from '@kbn/unified-histogram'; interface UnifiedHistogramCustomizationId { id: 'unified_histogram'; @@ -15,6 +15,6 @@ interface UnifiedHistogramCustomizationId { export type UnifiedHistogramCustomization = UnifiedHistogramCustomizationId & Pick< - UnifiedHistogramContainerProps, + UseUnifiedHistogramProps, 'onFilter' | 'onBrushEnd' | 'withDefaultActions' | 'disabledActions' >; diff --git a/src/platform/plugins/shared/discover/tsconfig.json b/src/platform/plugins/shared/discover/tsconfig.json index 0ab3958edfaa..88c3deed3a3b 100644 --- a/src/platform/plugins/shared/discover/tsconfig.json +++ b/src/platform/plugins/shared/discover/tsconfig.json @@ -36,7 +36,6 @@ "@kbn/data-view-editor-plugin", "@kbn/triggers-actions-ui-plugin", "@kbn/saved-objects-tagging-oss-plugin", - "@kbn/unified-histogram-plugin", "@kbn/analytics", "@kbn/saved-objects-management-plugin", "@kbn/lens-plugin", @@ -104,7 +103,8 @@ "@kbn/embeddable-enhanced-plugin", "@kbn/shared-ux-page-analytics-no-data-types", "@kbn/core-application-browser-mocks", - "@kbn/unified-tabs" + "@kbn/unified-tabs", + "@kbn/unified-histogram" ], "exclude": ["target/**/*"] } diff --git a/src/platform/plugins/shared/unified_histogram/README.md b/src/platform/plugins/shared/unified_histogram/README.md deleted file mode 100755 index 177cd6524d0f..000000000000 --- a/src/platform/plugins/shared/unified_histogram/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# unifiedHistogram - -Unified Histogram is a UX Building Block including a layout with a resizable histogram and a main display. -It manages its own state and data fetching, and can easily be dropped into pages with minimal setup. - -## Basic Usage - -```tsx -// Import the container component -import { UnifiedHistogramContainer } from '@kbn/unified-histogram-plugin/public'; - -// Import modules required for your application -import { useServices, useResizeRef, useRequestParams, MyLayout, MyButton } from './my-modules'; - -const services = useServices(); -const resizeRef = useResizeRef(); -const { dataView, query, filters, timeRange, relativeTimeRange, searchSessionId, requestAdapter } = - useRequestParams(); - -return ( - - - -); -``` - -## Advanced Usage - -```tsx -// Import the container component and API contract -import { - UnifiedHistogramContainer, - type UnifiedHistogramApi, -} from '@kbn/unified-histogram-plugin/public'; - -// Import modules required for your application -import { - useServices, - useResizeRef, - useCallbacks, - useRequestParams, - useStateParams, - useFetch, - MyLayout, - MyButton, -} from './my-modules'; - -const services = useServices(); -const resizeRef = useResizeRef(); -const { onChartHiddenChange, onLensRequestAdapterChange } = useCallbacks(); -const { chartHidden, breakdownField } = useStateParams(); -const { - dataView, - query, - filters, - timeRange, - relativeTimeRange, - searchSessionId, - requestAdapter, -} = useRequestParams(); - -// Use a state variable instead of a ref to preserve reactivity when the API is updated -const [unifiedHistogram, setUnifiedHistogram] = useState(); - -const getCreationOptions = useCallback(() => ({ - // Optionally provide a local storage key prefix to save parts of the state, - // such as the chart hidden state and top panel height, to local storage - localStorageKeyPrefix: 'myApp', - // Customize the initial state in order to override the defaults - initialState: { chartHidden, breakdownField }, -}), [...]); - -// Trigger a fetch, must be called on init to render the chart -useFetch(() => { - unifiedHistogram?.fetch(); -}); - -// Update the Unified Histogram state when our state params change -useEffect(() => { - unifiedHistogram?.setChartHidden(chartHidden); -}, [chartHidden]); - -useEffect(() => { - unifiedHistogram?.setBreakdownField(breakdownField); -}, [breakdownField]); - -// Listen for state changes if your application requires it -useEffect(() => { - const subscription = unifiedHistogram?.state$ - .pipe(map((state) => state.chartHidden), distinctUntilChanged()) - .subscribe(onChartHiddenChange); - - return () => { - subscription?.unsubscribe(); - }; -}, [...]); - -// Currently Lens does not accept a custom request adapter, -// so it will not use the one passed to Unified Histogram. -// Instead you can get access to the one it's using by -// listening for state changes -useEffect(() => { - const subscription = unifiedHistogram?.state$ - .pipe(map((state) => state.lensRequestAdapter), distinctUntilChanged()) - .subscribe(onLensRequestAdapterChange); - - return () => { - subscription?.unsubscribe(); - }; -}, [...]); - -return ( - - - -); -``` diff --git a/src/platform/plugins/shared/unified_histogram/kibana.jsonc b/src/platform/plugins/shared/unified_histogram/kibana.jsonc deleted file mode 100644 index 7001d67cad7c..000000000000 --- a/src/platform/plugins/shared/unified_histogram/kibana.jsonc +++ /dev/null @@ -1,21 +0,0 @@ -{ - "type": "plugin", - "id": "@kbn/unified-histogram-plugin", - "owner": [ - "@elastic/kibana-data-discovery" - ], - "group": "platform", - "visibility": "shared", - "description": "The `unifiedHistogram` plugin provides UI components to create a layout including a resizable histogram and a main display.", - "plugin": { - "id": "unifiedHistogram", - "browser": true, - "server": false, - "requiredBundles": [ - "data", - "dataViews", - "inspector", - "visualizations" - ] - } -} \ No newline at end of file diff --git a/src/platform/plugins/shared/unified_histogram/public/container/container.test.tsx b/src/platform/plugins/shared/unified_histogram/public/container/container.test.tsx deleted file mode 100644 index d67d4fc4fd81..000000000000 --- a/src/platform/plugins/shared/unified_histogram/public/container/container.test.tsx +++ /dev/null @@ -1,78 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { RequestAdapter } from '@kbn/inspector-plugin/common'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { UnifiedHistogramLayout } from '../layout'; -import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield'; -import { unifiedHistogramServicesMock } from '../__mocks__/services'; -import { UnifiedHistogramApi, UnifiedHistogramContainer } from './container'; - -describe('UnifiedHistogramContainer', () => { - it('should initialize', async () => { - let api: UnifiedHistogramApi | undefined; - const setApi = (ref: UnifiedHistogramApi) => { - api = ref; - }; - const getCreationOptions = jest.fn(() => ({ initialState: { timeInterval: '42s' } })); - const component = mountWithIntl( - - ); - expect(component.update().isEmptyRender()).toBe(true); - await act(() => new Promise((resolve) => setTimeout(resolve, 0))); - component.update(); - expect(getCreationOptions).toHaveBeenCalled(); - expect(component.find(UnifiedHistogramLayout).prop('chart')?.timeInterval).toBe('42s'); - expect(component.update().isEmptyRender()).toBe(false); - expect(api).toBeDefined(); - }); - - it('should trigger input$ when fetch is called', async () => { - let api: UnifiedHistogramApi | undefined; - const setApi = (ref: UnifiedHistogramApi) => { - api = ref; - }; - const component = mountWithIntl( - - ); - await act(() => new Promise((resolve) => setTimeout(resolve, 0))); - component.update(); - const input$ = component.find(UnifiedHistogramLayout).prop('input$'); - const inputSpy = jest.fn(); - input$?.subscribe(inputSpy); - act(() => { - api?.fetch(); - }); - expect(inputSpy).toHaveBeenCalledTimes(1); - expect(inputSpy).toHaveBeenCalledWith({ type: 'fetch' }); - }); -}); diff --git a/src/platform/plugins/shared/unified_histogram/public/container/container.tsx b/src/platform/plugins/shared/unified_histogram/public/container/container.tsx deleted file mode 100644 index 5d83f8e84101..000000000000 --- a/src/platform/plugins/shared/unified_histogram/public/container/container.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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'; -import { Subject } from 'rxjs'; -import { pick } from 'lodash'; -import useMount from 'react-use/lib/useMount'; -import { LensSuggestionsApi } from '@kbn/lens-plugin/public'; -import { UnifiedHistogramLayout, UnifiedHistogramLayoutProps } from '../layout'; -import { - UnifiedHistogramExternalVisContextStatus, - UnifiedHistogramInputMessage, - UnifiedHistogramRequestContext, - UnifiedHistogramVisContext, -} from '../types'; -import { - createStateService, - UnifiedHistogramStateOptions, - UnifiedHistogramStateService, -} from './services/state_service'; -import { useStateProps } from './hooks/use_state_props'; -import { useStateSelector } from './utils/use_state_selector'; -import { topPanelHeightSelector } from './utils/state_selectors'; -import { exportVisContext } from '../utils/external_vis_context'; -import { getBreakdownField } from './utils/local_storage_utils'; - -type LayoutProps = Pick; - -/** - * The options used to initialize the container - */ -export type UnifiedHistogramCreationOptions = Omit & - LayoutProps; - -/** - * The props exposed by the container - */ -export type UnifiedHistogramContainerProps = { - getCreationOptions?: () => - | UnifiedHistogramCreationOptions - | Promise; - searchSessionId?: UnifiedHistogramRequestContext['searchSessionId']; - requestAdapter?: UnifiedHistogramRequestContext['adapter']; - isChartLoading?: boolean; - breakdownField?: string; - onBreakdownFieldChange?: (breakdownField: string | undefined) => void; - onVisContextChanged?: ( - nextVisContext: UnifiedHistogramVisContext | undefined, - externalVisContextStatus: UnifiedHistogramExternalVisContextStatus - ) => void; -} & Pick< - UnifiedHistogramLayoutProps, - | 'services' - | 'className' - | 'dataView' - | 'query' - | 'filters' - | 'timeRange' - | 'relativeTimeRange' - | 'columns' - | 'table' - | 'container' - | 'renderCustomChartToggleActions' - | 'children' - | 'onBrushEnd' - | 'onFilter' - | 'externalVisContext' - | 'withDefaultActions' - | 'disabledActions' - | 'abortController' ->; - -/** - * The API exposed by the container - */ -export type UnifiedHistogramApi = { - /** - * Trigger a fetch of the data - */ - fetch: () => void; -} & Pick< - UnifiedHistogramStateService, - 'state$' | 'setChartHidden' | 'setTopPanelHeight' | 'setTimeInterval' | 'setTotalHits' ->; - -export const UnifiedHistogramContainer = forwardRef< - UnifiedHistogramApi, - UnifiedHistogramContainerProps ->(({ onBreakdownFieldChange, onVisContextChanged, ...containerProps }, ref) => { - const [layoutProps, setLayoutProps] = useState(); - const [localStorageKeyPrefix, setLocalStorageKeyPrefix] = useState(); - const [stateService, setStateService] = useState(); - const [lensSuggestionsApi, setLensSuggestionsApi] = useState(); - const [input$] = useState(() => new Subject()); - const [api, setApi] = useState(); - - // Expose the API to the parent component - useImperativeHandle(ref, () => api!, [api]); - - // Call for creation options once the container is mounted - useMount(async () => { - const { getCreationOptions, services } = containerProps; - const options = await getCreationOptions?.(); - const apiHelper = await services.lens.stateHelperApi(); - - setLayoutProps(pick(options, 'disableTriggers', 'disabledActions')); - setLocalStorageKeyPrefix(options?.localStorageKeyPrefix); - setStateService(createStateService({ services, ...options })); - setLensSuggestionsApi(() => apiHelper.suggestions); - }); - - // Initialize the API once the state service is available - useEffect(() => { - if (!stateService) { - return; - } - - setApi({ - fetch: () => { - input$.next({ type: 'fetch' }); - }, - ...pick( - stateService, - 'state$', - 'setChartHidden', - 'setTopPanelHeight', - 'setTimeInterval', - 'setTotalHits' - ), - }); - }, [input$, stateService]); - - const { services, dataView, query, columns, searchSessionId, requestAdapter, isChartLoading } = - containerProps; - const topPanelHeight = useStateSelector(stateService?.state$, topPanelHeightSelector); - const initialBreakdownField = useMemo( - () => - localStorageKeyPrefix - ? getBreakdownField(services.storage, localStorageKeyPrefix) - : undefined, - [localStorageKeyPrefix, services.storage] - ); - const stateProps = useStateProps({ - services, - localStorageKeyPrefix, - stateService, - dataView, - query, - searchSessionId, - requestAdapter, - columns, - breakdownField: initialBreakdownField, - ...pick(containerProps, 'breakdownField'), - onBreakdownFieldChange, - }); - - const handleVisContextChange: UnifiedHistogramLayoutProps['onVisContextChanged'] | undefined = - useMemo(() => { - if (!onVisContextChanged) { - return undefined; - } - - return (visContext, externalVisContextStatus) => { - const minifiedVisContext = exportVisContext(visContext); - - onVisContextChanged(minifiedVisContext, externalVisContextStatus); - }; - }, [onVisContextChanged]); - - // Don't render anything until the container is initialized - if (!layoutProps || !lensSuggestionsApi || !api) { - return null; - } - - return ( - - ); -}); - -// eslint-disable-next-line import/no-default-export -export default UnifiedHistogramContainer; diff --git a/src/platform/plugins/shared/unified_histogram/public/container/index.tsx b/src/platform/plugins/shared/unified_histogram/public/container/index.tsx deleted file mode 100644 index 5b4fa6a8d190..000000000000 --- a/src/platform/plugins/shared/unified_histogram/public/container/index.tsx +++ /dev/null @@ -1,52 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { EuiDelayRender, EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui'; -import { withSuspense } from '@kbn/shared-ux-utility'; -import React, { lazy } from 'react'; -import type { UnifiedHistogramApi, UnifiedHistogramContainerProps } from './container'; - -export type { - UnifiedHistogramApi, - UnifiedHistogramContainerProps, - UnifiedHistogramCreationOptions, -} from './container'; -export type { UnifiedHistogramState, UnifiedHistogramStateOptions } from './services/state_service'; -export { - getChartHidden, - getTopPanelHeight, - getBreakdownField, - setChartHidden, - setTopPanelHeight, - setBreakdownField, -} from './utils/local_storage_utils'; - -const LazyUnifiedHistogramContainer = lazy(() => import('./container')); - -/** - * A resizable layout component with two panels that renders a histogram with a hits - * counter in the top panel, and a main display (data table, etc.) in the bottom panel. - */ -export const UnifiedHistogramContainer = withSuspense< - UnifiedHistogramContainerProps, - UnifiedHistogramApi ->( - LazyUnifiedHistogramContainer, - - - - - -); diff --git a/src/platform/plugins/shared/unified_histogram/public/layout/layout.tsx b/src/platform/plugins/shared/unified_histogram/public/layout/layout.tsx deleted file mode 100644 index 5d497990e30f..000000000000 --- a/src/platform/plugins/shared/unified_histogram/public/layout/layout.tsx +++ /dev/null @@ -1,394 +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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { EuiSpacer, useEuiTheme, useIsWithinBreakpoints } from '@elastic/eui'; -import React, { PropsWithChildren, ReactElement, useEffect, useMemo, useState } from 'react'; -import useObservable from 'react-use/lib/useObservable'; -import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; -import { css } from '@emotion/css'; -import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common'; -import type { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import type { - EmbeddableComponentProps, - LensEmbeddableInput, - LensEmbeddableOutput, - LensSuggestionsApi, -} from '@kbn/lens-plugin/public'; -import { AggregateQuery, Filter, Query, TimeRange } from '@kbn/es-query'; -import { - ResizableLayout, - ResizableLayoutDirection, - ResizableLayoutMode, -} from '@kbn/resizable-layout'; -import { Chart, checkChartAvailability } from '../chart'; -import { - UnifiedHistogramVisContext, - UnifiedHistogramBreakdownContext, - UnifiedHistogramChartContext, - UnifiedHistogramChartLoadEvent, - UnifiedHistogramFetchStatus, - UnifiedHistogramHitsContext, - UnifiedHistogramInput$, - UnifiedHistogramRequestContext, - UnifiedHistogramServices, - UnifiedHistogramSuggestionContext, - UnifiedHistogramExternalVisContextStatus, -} from '../types'; -import { UnifiedHistogramSuggestionType } from '../types'; -import { LensVisService } from '../services/lens_vis_service'; -import { useRequestParams } from '../hooks/use_request_params'; - -const ChartMemoized = React.memo(Chart); - -const chartSpacer = ; - -export interface UnifiedHistogramLayoutProps extends PropsWithChildren { - /** - * Optional class name to add to the layout container - */ - className?: string; - /** - * Required services - */ - services: UnifiedHistogramServices; - /** - * The current data view - */ - dataView: DataView; - /** - * The current query - */ - query?: Query | AggregateQuery; - /** - * The current filters - */ - filters?: Filter[]; - /** - * The external custom Lens vis - */ - externalVisContext?: UnifiedHistogramVisContext; - /** - * Flag that indicates that a text based language is used - */ - isPlainRecord?: boolean; - /** - * The current time range - */ - timeRange?: TimeRange; - /** - * The relative time range, used when timeRange is an absolute range (e.g. for edit visualization button) - */ - relativeTimeRange?: TimeRange; - /** - * The current columns - */ - columns?: DatatableColumn[]; - /** - * Context object for requests made by Unified Histogram components -- optional - */ - request?: UnifiedHistogramRequestContext; - /** - * Context object for the hits count -- leave undefined to hide the hits count - */ - hits?: UnifiedHistogramHitsContext; - lensAdapters?: UnifiedHistogramChartLoadEvent['adapters']; - dataLoading$?: LensEmbeddableOutput['dataLoading$']; - /** - * Context object for the chart -- leave undefined to hide the chart - */ - chart?: UnifiedHistogramChartContext; - /** - * Context object for the breakdown -- leave undefined to hide the breakdown - */ - breakdown?: UnifiedHistogramBreakdownContext; - /** - * The parent container element, used to calculate the layout size - */ - container: HTMLElement | null; - /** - * Current top panel height -- leave undefined to use the default - */ - topPanelHeight?: number; - /** - * This element would replace the default chart toggle buttons - */ - renderCustomChartToggleActions?: () => ReactElement | undefined; - /** - * Disable triggers for the Lens embeddable - */ - disableTriggers?: LensEmbeddableInput['disableTriggers']; - /** - * Disabled action IDs for the Lens embeddable - */ - disabledActions?: LensEmbeddableInput['disabledActions']; - /** - * Input observable - */ - input$?: UnifiedHistogramInput$; - /** - * Flag indicating that the chart is currently loading - */ - isChartLoading: boolean; - /** - * The Lens suggestions API - */ - lensSuggestionsApi: LensSuggestionsApi; - /** - * Callback to update the topPanelHeight prop when a resize is triggered - */ - onTopPanelHeightChange?: (topPanelHeight: number | undefined) => void; - /** - * Callback to hide or show the chart -- should set {@link UnifiedHistogramChartContext.hidden} to chartHidden - */ - onChartHiddenChange?: (chartHidden: boolean) => void; - /** - * Callback to update the time interval -- should set {@link UnifiedHistogramChartContext.timeInterval} to timeInterval - */ - onTimeIntervalChange?: (timeInterval: string) => void; - /** - * Callback to update the breakdown field -- should set {@link UnifiedHistogramBreakdownContext.field} to breakdownField - */ - onBreakdownFieldChange?: (breakdownField: DataViewField | undefined) => void; - /** - * Callback to update the suggested chart - */ - onSuggestionContextChange: ( - suggestionContext: UnifiedHistogramSuggestionContext | undefined - ) => void; - /** - * Callback to notify about the change in Lens attributes - */ - onVisContextChanged?: ( - visContext: UnifiedHistogramVisContext | undefined, - externalVisContextStatus: UnifiedHistogramExternalVisContextStatus - ) => void; - /** - * Callback to update the total hits -- should set {@link UnifiedHistogramHitsContext.status} to status - * and {@link UnifiedHistogramHitsContext.total} to result - */ - onTotalHitsChange?: (status: UnifiedHistogramFetchStatus, result?: number | Error) => void; - /** - * Called when the histogram loading status changes - */ - onChartLoad?: (event: UnifiedHistogramChartLoadEvent) => void; - /** - * Callback to pass to the Lens embeddable to handle filter changes - */ - onFilter?: LensEmbeddableInput['onFilter']; - /** - * Callback to pass to the Lens embeddable to handle brush events - */ - onBrushEnd?: LensEmbeddableInput['onBrushEnd']; - /** - * Allows users to enable/disable default actions - */ - withDefaultActions?: EmbeddableComponentProps['withDefaultActions']; - - table?: Datatable; - abortController?: AbortController; -} - -export const UnifiedHistogramLayout = ({ - className, - services, - dataView, - query: originalQuery, - filters: originalFilters, - externalVisContext, - isChartLoading, - isPlainRecord, - timeRange: originalTimeRange, - relativeTimeRange, - columns, - request, - hits, - lensAdapters, - dataLoading$, - chart: originalChart, - breakdown, - container, - topPanelHeight, - renderCustomChartToggleActions, - disableTriggers, - disabledActions, - lensSuggestionsApi, - input$, - table, - onTopPanelHeightChange, - onChartHiddenChange, - onTimeIntervalChange, - onBreakdownFieldChange, - onSuggestionContextChange, - onVisContextChanged, - onTotalHitsChange, - onChartLoad, - onFilter, - onBrushEnd, - children, - withDefaultActions, - abortController, -}: UnifiedHistogramLayoutProps) => { - const columnsMap = useMemo(() => { - if (!columns?.length) { - return undefined; - } - - return columns.reduce((acc, column) => { - acc[column.id] = column; - return acc; - }, {} as Record); - }, [columns]); - - const requestParams = useRequestParams({ - services, - query: originalQuery, - filters: originalFilters, - timeRange: originalTimeRange, - }); - - const [lensVisService] = useState(() => new LensVisService({ services, lensSuggestionsApi })); - const lensVisServiceCurrentSuggestionContext = useObservable( - lensVisService.currentSuggestionContext$ - ); - - const originalChartTimeInterval = originalChart?.timeInterval; - useEffect(() => { - if (isChartLoading) { - return; - } - - lensVisService.update({ - externalVisContext, - queryParams: { - dataView, - query: requestParams.query, - filters: requestParams.filters, - timeRange: originalTimeRange, - isPlainRecord, - columns, - columnsMap, - }, - timeInterval: originalChartTimeInterval, - breakdownField: breakdown?.field, - table, - onSuggestionContextChange, - onVisContextChanged: isPlainRecord ? onVisContextChanged : undefined, - }); - }, [ - lensVisService, - dataView, - requestParams.query, - requestParams.filters, - originalTimeRange, - originalChartTimeInterval, - isPlainRecord, - columns, - columnsMap, - breakdown, - externalVisContext, - onSuggestionContextChange, - onVisContextChanged, - isChartLoading, - table, - ]); - - const chart = - !lensVisServiceCurrentSuggestionContext?.type || - lensVisServiceCurrentSuggestionContext.type === UnifiedHistogramSuggestionType.unsupported - ? undefined - : originalChart; - const isChartAvailable = checkChartAvailability({ chart, dataView, isPlainRecord }); - - const [topPanelNode] = useState(() => - createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }) - ); - const [mainPanelNode] = useState(() => - createHtmlPortalNode({ attributes: { class: 'eui-fullHeight' } }) - ); - - const isMobile = useIsWithinBreakpoints(['xs', 's']); - const showFixedPanels = isMobile || !chart || chart.hidden; - const { euiTheme } = useEuiTheme(); - const defaultTopPanelHeight = euiTheme.base * 12; - const minMainPanelHeight = euiTheme.base * 10; - - const chartClassName = - isMobile && chart && !chart.hidden - ? css` - height: ${defaultTopPanelHeight}px; - ` - : 'eui-fullHeight'; - - const panelsMode = - chart || hits - ? showFixedPanels - ? ResizableLayoutMode.Static - : ResizableLayoutMode.Resizable - : ResizableLayoutMode.Single; - - const currentTopPanelHeight = topPanelHeight ?? defaultTopPanelHeight; - - return ( - <> - - - - - {React.isValidElement(children) - ? // @ts-expect-error upgrade typescript v4.9.5 - React.cloneElement(children, { isChartAvailable }) - : children} - - } - flexPanel={} - data-test-subj="unifiedHistogram" - onFixedPanelSizeChange={onTopPanelHeightChange} - /> - - ); -}; diff --git a/tsconfig.base.json b/tsconfig.base.json index df3171643b36..4a07954a904f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -2050,8 +2050,8 @@ "@kbn/unified-field-list/*": ["src/platform/packages/shared/kbn-unified-field-list/*"], "@kbn/unified-field-list-examples-plugin": ["examples/unified_field_list_examples"], "@kbn/unified-field-list-examples-plugin/*": ["examples/unified_field_list_examples/*"], - "@kbn/unified-histogram-plugin": ["src/platform/plugins/shared/unified_histogram"], - "@kbn/unified-histogram-plugin/*": ["src/platform/plugins/shared/unified_histogram/*"], + "@kbn/unified-histogram": ["src/platform/packages/shared/kbn-unified-histogram"], + "@kbn/unified-histogram/*": ["src/platform/packages/shared/kbn-unified-histogram/*"], "@kbn/unified-search-plugin": ["src/platform/plugins/shared/unified_search"], "@kbn/unified-search-plugin/*": ["src/platform/plugins/shared/unified_search/*"], "@kbn/unified-tabs": ["src/platform/packages/shared/kbn-unified-tabs"], diff --git a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts index e03d1c74e587..82f5ab976474 100644 --- a/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts +++ b/x-pack/platform/plugins/private/discover_enhanced/ui_tests/tests/discover_cdp_perf.spec.ts @@ -73,7 +73,6 @@ test.describe( 'kbn-ui-shared-deps-npm', 'lens', 'maps', - 'unifiedHistogram', 'unifiedSearch', ]); // Validate individual plugin bundle sizes @@ -81,10 +80,6 @@ test.describe( stats.plugins.find((p) => p.name === 'discover')?.totalSize, `Total 'discover' bundles size should not exceed 650 KB` ).toBeLessThan(650 * 1024); - expect( - stats.plugins.find((p) => p.name === 'unifiedHistogram')?.totalSize, - `Total 'unifiedHistogram' bundles size should not exceed 150 KB` - ).toBeLessThan(150 * 1024); expect( stats.plugins.find((p) => p.name === 'unifiedSearch')?.totalSize, `Total 'unifiedSearch' bundles size should not exceed 450 KB` diff --git a/x-pack/platform/plugins/shared/dataset_quality/kibana.jsonc b/x-pack/platform/plugins/shared/dataset_quality/kibana.jsonc index 471c25ec4952..e4e35a134c6d 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/kibana.jsonc +++ b/x-pack/platform/plugins/shared/dataset_quality/kibana.jsonc @@ -34,7 +34,6 @@ "telemetry" ], "requiredBundles": [ - "unifiedHistogram", "discover" ], "extraPublicDirs": [ diff --git a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/index.tsx b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/index.tsx index 856cc251d6d6..e0a0eaa84166 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/index.tsx +++ b/x-pack/platform/plugins/shared/dataset_quality/public/components/dataset_quality_details/overview/document_trends/index.tsx @@ -24,7 +24,7 @@ import { import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { UnifiedBreakdownFieldSelector } from '@kbn/unified-histogram-plugin/public'; +import { UnifiedBreakdownFieldSelector } from '@kbn/unified-histogram'; import React, { useCallback } from 'react'; import { discoverAriaText, diff --git a/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json b/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json index d28adf112457..7d4fb442f54d 100644 --- a/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json +++ b/x-pack/platform/plugins/shared/dataset_quality/tsconfig.json @@ -32,7 +32,6 @@ "@kbn/unified-search-plugin", "@kbn/timerange", "@kbn/lens-plugin", - "@kbn/unified-histogram-plugin", "@kbn/data-views-plugin", "@kbn/shared-ux-error-boundary", "@kbn/es-query", @@ -58,7 +57,8 @@ "@kbn/logging", "@kbn/ui-theme", "@kbn/react-hooks", - "@kbn/charts-theme" + "@kbn/charts-theme", + "@kbn/unified-histogram" ], "exclude": [ "target/**/*" diff --git a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.tsx b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.tsx index 783c667ee720..a1da8e298fd3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/timelines/components/timeline/tabs/esql/customizations/use_histogram_customizations.tsx @@ -11,7 +11,7 @@ import type { MultiClickTriggerEvent, } from '@kbn/charts-plugin/public'; import type { CustomizationCallback } from '@kbn/discover-plugin/public'; -import type { UnifiedHistogramContainerProps } from '@kbn/unified-histogram-plugin/public'; +import type { UseUnifiedHistogramProps } from '@kbn/unified-histogram'; import { ACTION_GLOBAL_APPLY_FILTER } from '@kbn/unified-search-plugin/public'; import { useCallback } from 'react'; import { EsqlInTimelineTrigger } from '../../../../../../app/actions/constants'; @@ -42,7 +42,7 @@ export const useHistogramCustomization = () => { services: { customDataService: discoverDataService, uiActions }, } = useKibana(); - const onFilterCallback: UnifiedHistogramContainerProps['onFilter'] = useCallback( + const onFilterCallback: UseUnifiedHistogramProps['onFilter'] = useCallback( async (eventData: WithPreventableEvent) => { if (eventData.preventDefault) eventData.preventDefault(); let filters; @@ -78,7 +78,7 @@ export const useHistogramCustomization = () => { [uiActions, discoverDataService.actions] ); - const onBrushEndCallback: UnifiedHistogramContainerProps['onBrushEnd'] = useCallback( + const onBrushEndCallback: UseUnifiedHistogramProps['onBrushEnd'] = useCallback( (data: WithPreventableEvent) => { discoverDataService.query.timefilter.timefilter.setTime({ from: new Date(data.range[0]).toISOString(), diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index 657d7d3b3f7d..7af2b988f2ec 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -157,7 +157,6 @@ "@kbn/data-view-editor-plugin", "@kbn/alerts-ui-shared", "@kbn/saved-search-plugin", - "@kbn/unified-histogram-plugin", "@kbn/navigation-plugin", "@kbn/core-logging-server-mocks", "@kbn/core-lifecycle-browser", @@ -249,5 +248,6 @@ "@kbn/core-chrome-browser", "@kbn/actions-types", "@kbn/triggers-actions-ui-types", + "@kbn/unified-histogram", ] } diff --git a/yarn.lock b/yarn.lock index 76221a44e8e2..2fae0c2b6ee7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7795,7 +7795,7 @@ version "0.0.0" uid "" -"@kbn/unified-histogram-plugin@link:src/platform/plugins/shared/unified_histogram": +"@kbn/unified-histogram@link:src/platform/packages/shared/kbn-unified-histogram": version "0.0.0" uid ""