mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[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>
This commit is contained in:
parent
ee74bb4e65
commit
d536f85005
117 changed files with 1197 additions and 1382 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -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-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-doc-viewer @elastic/kibana-data-discovery
|
||||||
src/platform/packages/shared/kbn-unified-field-list @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-unified-tabs @elastic/kibana-data-discovery
|
||||||
src/platform/packages/shared/kbn-unsaved-changes-prompt @elastic/kibana-management
|
src/platform/packages/shared/kbn-unsaved-changes-prompt @elastic/kibana-management
|
||||||
src/platform/packages/shared/kbn-use-tracked-promise @elastic/obs-ux-logs-team
|
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 @elastic/appex-sharedux
|
||||||
src/platform/plugins/shared/ui_actions_enhanced @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_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/unified_search @elastic/kibana-presentation
|
||||||
src/platform/plugins/shared/usage_collection @elastic/kibana-core
|
src/platform/plugins/shared/usage_collection @elastic/kibana-core
|
||||||
src/platform/plugins/shared/vis_types/timeseries @elastic/kibana-visualizations
|
src/platform/plugins/shared/vis_types/timeseries @elastic/kibana-visualizations
|
||||||
|
|
|
@ -154,7 +154,7 @@
|
||||||
"unifiedDocViewer": ["src/platform/plugins/shared/unified_doc_viewer", "src/platform/packages/shared/kbn-unified-doc-viewer"],
|
"unifiedDocViewer": ["src/platform/plugins/shared/unified_doc_viewer", "src/platform/packages/shared/kbn-unified-doc-viewer"],
|
||||||
"unifiedSearch": "src/platform/plugins/shared/unified_search",
|
"unifiedSearch": "src/platform/plugins/shared/unified_search",
|
||||||
"unifiedFieldList": "src/platform/packages/shared/kbn-unified-field-list",
|
"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",
|
"unifiedDataTable": "src/platform/packages/shared/kbn-unified-data-table",
|
||||||
"unifiedTabs": "src/platform/packages/shared/kbn-unified-tabs",
|
"unifiedTabs": "src/platform/packages/shared/kbn-unified-tabs",
|
||||||
"dataGridInTableSearch": "src/platform/packages/shared/kbn-data-grid-in-table-search",
|
"dataGridInTableSearch": "src/platform/packages/shared/kbn-data-grid-in-table-search",
|
||||||
|
|
|
@ -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. |
|
| [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. |
|
| [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). |
|
| [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: |
|
| [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. |
|
| [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. |
|
| [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. |
|
||||||
|
|
|
@ -998,7 +998,7 @@
|
||||||
"@kbn/unified-doc-viewer-plugin": "link:src/platform/plugins/shared/unified_doc_viewer",
|
"@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": "link:src/platform/packages/shared/kbn-unified-field-list",
|
||||||
"@kbn/unified-field-list-examples-plugin": "link:examples/unified_field_list_examples",
|
"@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-search-plugin": "link:src/platform/plugins/shared/unified_search",
|
||||||
"@kbn/unified-tabs": "link:src/platform/packages/shared/kbn-unified-tabs",
|
"@kbn/unified-tabs": "link:src/platform/packages/shared/kbn-unified-tabs",
|
||||||
"@kbn/unified-tabs-examples-plugin": "link:examples/unified_tabs_examples",
|
"@kbn/unified-tabs-examples-plugin": "link:examples/unified_tabs_examples",
|
||||||
|
|
|
@ -168,7 +168,6 @@ pageLoadAssetSize:
|
||||||
uiActions: 35121
|
uiActions: 35121
|
||||||
uiActionsEnhanced: 38494
|
uiActionsEnhanced: 38494
|
||||||
unifiedDocViewer: 25099
|
unifiedDocViewer: 25099
|
||||||
unifiedHistogram: 19928
|
|
||||||
unifiedSearch: 23000
|
unifiedSearch: 23000
|
||||||
upgradeAssistant: 81241
|
upgradeAssistant: 81241
|
||||||
uptime: 60000
|
uptime: 60000
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
import type { ResizeTrigger } from '@elastic/eui/src/components/resizable_container/types';
|
import type { ResizeTrigger } from '@elastic/eui/src/components/resizable_container/types';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import { isEqual, round } from 'lodash';
|
import { isEqual, round } from 'lodash';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { ResizableLayoutDirection } from '../types';
|
import { ResizableLayoutDirection } from '../types';
|
||||||
import { getContainerSize, percentToPixels, pixelsToPercent } from './utils';
|
import { getContainerSize, percentToPixels, pixelsToPercent } from './utils';
|
||||||
|
@ -47,8 +47,8 @@ export const PanelsResizable = ({
|
||||||
fixedPanelSizePct: number;
|
fixedPanelSizePct: number;
|
||||||
flexPanelSizePct: number;
|
flexPanelSizePct: number;
|
||||||
};
|
};
|
||||||
fixedPanel: ReactElement;
|
fixedPanel: ReactNode;
|
||||||
flexPanel: ReactElement;
|
flexPanel: ReactNode;
|
||||||
resizeButtonClassName?: string;
|
resizeButtonClassName?: string;
|
||||||
['data-test-subj']?: string;
|
['data-test-subj']?: string;
|
||||||
onFixedPanelSizeChange?: (fixedPanelSize: number) => void;
|
onFixedPanelSizeChange?: (fixedPanelSize: number) => void;
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ResizableLayoutDirection } from '../types';
|
import { ResizableLayoutDirection } from '../types';
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ export const PanelsStatic = ({
|
||||||
className?: string;
|
className?: string;
|
||||||
direction: ResizableLayoutDirection;
|
direction: ResizableLayoutDirection;
|
||||||
hideFixedPanel?: boolean;
|
hideFixedPanel?: boolean;
|
||||||
fixedPanel: ReactElement;
|
fixedPanel: ReactNode;
|
||||||
flexPanel: ReactElement;
|
flexPanel: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
// By default a flex item has overflow: visible, min-height: auto, and min-width: auto.
|
// 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.
|
// This can cause the item to overflow the flexbox parent when its content is too large.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* 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 React from 'react';
|
||||||
import { round } from 'lodash';
|
import { round } from 'lodash';
|
||||||
import { PanelsResizable } from './panels_resizable';
|
import { PanelsResizable } from './panels_resizable';
|
||||||
|
@ -47,11 +47,11 @@ export interface ResizableLayoutProps {
|
||||||
/**
|
/**
|
||||||
* The fixed panel
|
* The fixed panel
|
||||||
*/
|
*/
|
||||||
fixedPanel: ReactElement;
|
fixedPanel: ReactNode;
|
||||||
/**
|
/**
|
||||||
* The flex panel
|
* The flex panel
|
||||||
*/
|
*/
|
||||||
flexPanel: ReactElement;
|
flexPanel: ReactNode;
|
||||||
/**
|
/**
|
||||||
* Class name for the resize button
|
* Class name for the resize button
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -72,7 +72,6 @@ test.describe(
|
||||||
'kbn-ui-shared-deps-npm',
|
'kbn-ui-shared-deps-npm',
|
||||||
'lens',
|
'lens',
|
||||||
'maps',
|
'maps',
|
||||||
'unifiedHistogram',
|
|
||||||
'unifiedSearch',
|
'unifiedSearch',
|
||||||
]);
|
]);
|
||||||
// Validate individual plugin bundle sizes
|
// Validate individual plugin bundle sizes
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# @kbn/unified-histogram
|
||||||
|
|
||||||
|
Components for the Discover histogram chart
|
|
@ -12,8 +12,8 @@ import React from 'react';
|
||||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||||
import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils';
|
import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils';
|
||||||
import { DataViewField } from '@kbn/data-views-plugin/common';
|
import { DataViewField } from '@kbn/data-views-plugin/common';
|
||||||
import { UnifiedHistogramBreakdownContext } from '../types';
|
import { UnifiedHistogramBreakdownContext } from '../../types';
|
||||||
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||||
import { BreakdownFieldSelector } from './breakdown_field_selector';
|
import { BreakdownFieldSelector } from './breakdown_field_selector';
|
||||||
|
|
||||||
describe('BreakdownFieldSelector', () => {
|
describe('BreakdownFieldSelector', () => {
|
|
@ -21,7 +21,7 @@ import { type DataView, DataViewField } from '@kbn/data-views-plugin/common';
|
||||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||||
import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils';
|
import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { UnifiedHistogramBreakdownContext } from '../types';
|
import { UnifiedHistogramBreakdownContext } from '../../types';
|
||||||
import {
|
import {
|
||||||
ToolbarSelector,
|
ToolbarSelector,
|
||||||
ToolbarSelectorProps,
|
ToolbarSelectorProps,
|
|
@ -13,18 +13,18 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||||
import type { Capabilities } from '@kbn/core/public';
|
import type { Capabilities } from '@kbn/core/public';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import type { Suggestion } from '@kbn/lens-plugin/public';
|
import type { Suggestion } from '@kbn/lens-plugin/public';
|
||||||
import type { UnifiedHistogramFetchStatus } from '../types';
|
import type { UnifiedHistogramFetchStatus } from '../../types';
|
||||||
import { Chart, type ChartProps } from './chart';
|
import { UnifiedHistogramChart, type UnifiedHistogramChartProps } from './chart';
|
||||||
import type { ReactWrapper } from 'enzyme';
|
import type { ReactWrapper } from 'enzyme';
|
||||||
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||||
import { getLensVisMock } from '../__mocks__/lens_vis';
|
import { getLensVisMock } from '../../__mocks__/lens_vis';
|
||||||
import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
|
import { searchSourceInstanceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
|
||||||
import { Subject, of } from 'rxjs';
|
import { Subject, of } from 'rxjs';
|
||||||
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||||
import { dataViewMock } from '../__mocks__/data_view';
|
import { dataViewMock } from '../../__mocks__/data_view';
|
||||||
import { BreakdownFieldSelector } from './breakdown_field_selector';
|
import { BreakdownFieldSelector } from './breakdown_field_selector';
|
||||||
import { checkChartAvailability } from './check_chart_availability';
|
import { checkChartAvailability } from './utils/check_chart_availability';
|
||||||
import { allSuggestionsMock } from '../__mocks__/suggestions';
|
import { allSuggestionsMock } from '../../__mocks__/suggestions';
|
||||||
|
|
||||||
let mockUseEditVisualization: jest.Mock | undefined = jest.fn();
|
let mockUseEditVisualization: jest.Mock | undefined = jest.fn();
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ async function mountComponent({
|
||||||
noHits,
|
noHits,
|
||||||
noBreakdown,
|
noBreakdown,
|
||||||
chartHidden = false,
|
chartHidden = false,
|
||||||
appendHistogram,
|
|
||||||
dataView = dataViewWithTimefieldMock,
|
dataView = dataViewWithTimefieldMock,
|
||||||
allSuggestions,
|
allSuggestions,
|
||||||
isPlainRecord,
|
isPlainRecord,
|
||||||
|
@ -51,7 +50,6 @@ async function mountComponent({
|
||||||
noHits?: boolean;
|
noHits?: boolean;
|
||||||
noBreakdown?: boolean;
|
noBreakdown?: boolean;
|
||||||
chartHidden?: boolean;
|
chartHidden?: boolean;
|
||||||
appendHistogram?: ReactElement;
|
|
||||||
dataView?: DataView;
|
dataView?: DataView;
|
||||||
allSuggestions?: Suggestion[];
|
allSuggestions?: Suggestion[];
|
||||||
isPlainRecord?: boolean;
|
isPlainRecord?: boolean;
|
||||||
|
@ -114,7 +112,7 @@ async function mountComponent({
|
||||||
})
|
})
|
||||||
).lensService;
|
).lensService;
|
||||||
|
|
||||||
const props: ChartProps = {
|
const props: UnifiedHistogramChartProps = {
|
||||||
lensVisService,
|
lensVisService,
|
||||||
dataView,
|
dataView,
|
||||||
requestParams,
|
requestParams,
|
||||||
|
@ -129,7 +127,6 @@ async function mountComponent({
|
||||||
breakdown: noBreakdown ? undefined : { field: undefined },
|
breakdown: noBreakdown ? undefined : { field: undefined },
|
||||||
isChartLoading: Boolean(isChartLoading),
|
isChartLoading: Boolean(isChartLoading),
|
||||||
isPlainRecord,
|
isPlainRecord,
|
||||||
appendHistogram,
|
|
||||||
onChartHiddenChange: jest.fn(),
|
onChartHiddenChange: jest.fn(),
|
||||||
onTimeIntervalChange: jest.fn(),
|
onTimeIntervalChange: jest.fn(),
|
||||||
withDefaultActions: undefined,
|
withDefaultActions: undefined,
|
||||||
|
@ -140,7 +137,7 @@ async function mountComponent({
|
||||||
|
|
||||||
let instance: ReactWrapper = {} as ReactWrapper;
|
let instance: ReactWrapper = {} as ReactWrapper;
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
instance = mountWithIntl(<Chart {...props} />);
|
instance = mountWithIntl(<UnifiedHistogramChart {...props} />);
|
||||||
// wait for initial async loading to complete
|
// wait for initial async loading to complete
|
||||||
await new Promise((r) => setTimeout(r, 0));
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
props.input$?.next({ type: 'fetch' });
|
props.input$?.next({ type: 'fetch' });
|
||||||
|
@ -339,12 +336,6 @@ describe('Chart', () => {
|
||||||
expect(mockUseEditVisualization).toHaveBeenCalled();
|
expect(mockUseEditVisualization).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render the element passed to appendHistogram', async () => {
|
|
||||||
const appendHistogram = <div data-test-subj="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 () => {
|
it('should not render chart if data view is not time based', async () => {
|
||||||
const component = await mountComponent({ dataView: dataViewMock });
|
const component = await mountComponent({ dataView: dataViewMock });
|
||||||
expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeFalsy();
|
expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBeFalsy();
|
|
@ -11,7 +11,7 @@ import React, { memo, ReactElement, useCallback, useEffect, useMemo, useState }
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import { IconButtonGroup, type IconButtonGroupProps } from '@kbn/shared-ux-button-toolbar';
|
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 { i18n } from '@kbn/i18n';
|
||||||
import type {
|
import type {
|
||||||
EmbeddableComponentProps,
|
EmbeddableComponentProps,
|
||||||
|
@ -26,7 +26,7 @@ import type {
|
||||||
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
import type { DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||||
import type { TimeRange } from '@kbn/es-query';
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
import { PublishingSubject } from '@kbn/presentation-publishing';
|
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 { IKibanaSearchResponse } from '@kbn/search-types';
|
||||||
import type { estypes } from '@elastic/elasticsearch';
|
import type { estypes } from '@elastic/elasticsearch';
|
||||||
import { Histogram } from './histogram';
|
import { Histogram } from './histogram';
|
||||||
|
@ -42,8 +42,8 @@ import {
|
||||||
UnifiedHistogramRequestContext,
|
UnifiedHistogramRequestContext,
|
||||||
UnifiedHistogramServices,
|
UnifiedHistogramServices,
|
||||||
UnifiedHistogramBucketInterval,
|
UnifiedHistogramBucketInterval,
|
||||||
} from '../types';
|
} from '../../types';
|
||||||
import { UnifiedHistogramSuggestionType } from '../types';
|
import { UnifiedHistogramSuggestionType } from '../../types';
|
||||||
import { BreakdownFieldSelector } from './breakdown_field_selector';
|
import { BreakdownFieldSelector } from './breakdown_field_selector';
|
||||||
import { TimeIntervalSelector } from './time_interval_selector';
|
import { TimeIntervalSelector } from './time_interval_selector';
|
||||||
import { useTotalHits } from './hooks/use_total_hits';
|
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 { ChartConfigPanel } from './chart_config_panel';
|
||||||
import { useFetch } from './hooks/use_fetch';
|
import { useFetch } from './hooks/use_fetch';
|
||||||
import { useEditVisualization } from './hooks/use_edit_visualization';
|
import { useEditVisualization } from './hooks/use_edit_visualization';
|
||||||
import { LensVisService } from '../services/lens_vis_service';
|
import { LensVisService } from '../../services/lens_vis_service';
|
||||||
import type { UseRequestParamsResult } from '../hooks/use_request_params';
|
import type { UseRequestParamsResult } from '../../hooks/use_request_params';
|
||||||
import { removeTablesFromLensAttributes } from '../utils/lens_vis_from_table';
|
import { removeTablesFromLensAttributes } from '../../utils/lens_vis_from_table';
|
||||||
import { useLensProps } from './hooks/use_lens_props';
|
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';
|
import { buildBucketInterval } from './utils/build_bucket_interval';
|
||||||
|
|
||||||
export interface ChartProps {
|
export interface UnifiedHistogramChartProps {
|
||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
isChartAvailable: boolean;
|
isChartAvailable: boolean;
|
||||||
hiddenPanel?: boolean;
|
hiddenPanel?: boolean;
|
||||||
className?: string;
|
|
||||||
services: UnifiedHistogramServices;
|
services: UnifiedHistogramServices;
|
||||||
dataView: DataView;
|
dataView: DataView;
|
||||||
requestParams: UseRequestParamsResult;
|
requestParams: UseRequestParamsResult;
|
||||||
|
@ -75,7 +74,6 @@ export interface ChartProps {
|
||||||
chart?: UnifiedHistogramChartContext;
|
chart?: UnifiedHistogramChartContext;
|
||||||
breakdown?: UnifiedHistogramBreakdownContext;
|
breakdown?: UnifiedHistogramBreakdownContext;
|
||||||
renderCustomChartToggleActions?: () => ReactElement | undefined;
|
renderCustomChartToggleActions?: () => ReactElement | undefined;
|
||||||
appendHistogram?: ReactElement;
|
|
||||||
disableTriggers?: LensEmbeddableInput['disableTriggers'];
|
disableTriggers?: LensEmbeddableInput['disableTriggers'];
|
||||||
disabledActions?: LensEmbeddableInput['disabledActions'];
|
disabledActions?: LensEmbeddableInput['disabledActions'];
|
||||||
input$?: UnifiedHistogramInput$;
|
input$?: UnifiedHistogramInput$;
|
||||||
|
@ -89,15 +87,15 @@ export interface ChartProps {
|
||||||
onChartLoad?: (event: UnifiedHistogramChartLoadEvent) => void;
|
onChartLoad?: (event: UnifiedHistogramChartLoadEvent) => void;
|
||||||
onFilter?: LensEmbeddableInput['onFilter'];
|
onFilter?: LensEmbeddableInput['onFilter'];
|
||||||
onBrushEnd?: LensEmbeddableInput['onBrushEnd'];
|
onBrushEnd?: LensEmbeddableInput['onBrushEnd'];
|
||||||
withDefaultActions: EmbeddableComponentProps['withDefaultActions'];
|
withDefaultActions?: EmbeddableComponentProps['withDefaultActions'];
|
||||||
columns?: DatatableColumn[];
|
columns?: DatatableColumn[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RequestStatusError: typeof RequestStatus.ERROR = 2;
|
||||||
const HistogramMemoized = memo(Histogram);
|
const HistogramMemoized = memo(Histogram);
|
||||||
|
|
||||||
export function Chart({
|
export function UnifiedHistogramChart({
|
||||||
isChartAvailable,
|
isChartAvailable,
|
||||||
className,
|
|
||||||
services,
|
services,
|
||||||
dataView,
|
dataView,
|
||||||
requestParams,
|
requestParams,
|
||||||
|
@ -109,9 +107,6 @@ export function Chart({
|
||||||
lensVisService,
|
lensVisService,
|
||||||
isPlainRecord,
|
isPlainRecord,
|
||||||
renderCustomChartToggleActions,
|
renderCustomChartToggleActions,
|
||||||
appendHistogram,
|
|
||||||
disableTriggers,
|
|
||||||
disabledActions,
|
|
||||||
input$: originalInput$,
|
input$: originalInput$,
|
||||||
lensAdapters,
|
lensAdapters,
|
||||||
dataLoading$,
|
dataLoading$,
|
||||||
|
@ -121,12 +116,9 @@ export function Chart({
|
||||||
onBreakdownFieldChange,
|
onBreakdownFieldChange,
|
||||||
onTotalHitsChange,
|
onTotalHitsChange,
|
||||||
onChartLoad,
|
onChartLoad,
|
||||||
onFilter,
|
|
||||||
onBrushEnd,
|
|
||||||
withDefaultActions,
|
|
||||||
abortController,
|
|
||||||
columns,
|
columns,
|
||||||
}: ChartProps) {
|
...histogramProps
|
||||||
|
}: UnifiedHistogramChartProps) {
|
||||||
const lensVisServiceCurrentSuggestionContext = useObservable(
|
const lensVisServiceCurrentSuggestionContext = useObservable(
|
||||||
lensVisService.currentSuggestionContext$
|
lensVisService.currentSuggestionContext$
|
||||||
);
|
);
|
||||||
|
@ -177,7 +169,7 @@ export function Chart({
|
||||||
dataLoadingSubject$?: PublishingSubject<boolean | undefined>
|
dataLoadingSubject$?: PublishingSubject<boolean | undefined>
|
||||||
) => {
|
) => {
|
||||||
const lensRequest = adapters?.requests?.getRequests()[0];
|
const lensRequest = adapters?.requests?.getRequests()[0];
|
||||||
const requestFailed = lensRequest?.status === RequestStatus.ERROR;
|
const requestFailed = lensRequest?.status === RequestStatusError;
|
||||||
const json = lensRequest?.response?.json as
|
const json = lensRequest?.response?.json as
|
||||||
| IKibanaSearchResponse<estypes.SearchResponse>
|
| IKibanaSearchResponse<estypes.SearchResponse>
|
||||||
| undefined;
|
| undefined;
|
||||||
|
@ -315,7 +307,7 @@ export function Chart({
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
{...a11yCommonProps}
|
{...a11yCommonProps}
|
||||||
className={className}
|
className="unifiedHistogram__chart"
|
||||||
direction="column"
|
direction="column"
|
||||||
alignItems="stretch"
|
alignItems="stretch"
|
||||||
gutterSize="none"
|
gutterSize="none"
|
||||||
|
@ -413,7 +405,6 @@ export function Chart({
|
||||||
)}
|
)}
|
||||||
{lensPropsContext && (
|
{lensPropsContext && (
|
||||||
<HistogramMemoized
|
<HistogramMemoized
|
||||||
abortController={abortController}
|
|
||||||
services={services}
|
services={services}
|
||||||
dataView={dataView}
|
dataView={dataView}
|
||||||
chart={chart}
|
chart={chart}
|
||||||
|
@ -421,16 +412,12 @@ export function Chart({
|
||||||
getTimeRange={getTimeRange}
|
getTimeRange={getTimeRange}
|
||||||
visContext={visContext}
|
visContext={visContext}
|
||||||
isPlainRecord={isPlainRecord}
|
isPlainRecord={isPlainRecord}
|
||||||
disableTriggers={disableTriggers}
|
{...histogramProps}
|
||||||
disabledActions={disabledActions}
|
|
||||||
onFilter={onFilter}
|
|
||||||
onBrushEnd={onBrushEnd}
|
|
||||||
withDefaultActions={withDefaultActions}
|
|
||||||
{...lensPropsContext}
|
{...lensPropsContext}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
{appendHistogram}
|
<EuiSpacer size="s" />
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
)}
|
)}
|
||||||
{canSaveVisualization && isSaveModalVisible && visContext.attributes && (
|
{canSaveVisualization && isSaveModalVisible && visContext.attributes && (
|
|
@ -12,13 +12,13 @@ import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { setTimeout } from 'timers/promises';
|
import { setTimeout } from 'timers/promises';
|
||||||
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||||
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||||
import { currentSuggestionMock } from '../__mocks__/suggestions';
|
import { currentSuggestionMock } from '../../__mocks__/suggestions';
|
||||||
import { lensAdaptersMock } from '../__mocks__/lens_adapters';
|
import { lensAdaptersMock } from '../../__mocks__/lens_adapters';
|
||||||
import { ChartConfigPanel } from './chart_config_panel';
|
import { ChartConfigPanel } from './chart_config_panel';
|
||||||
import type { UnifiedHistogramVisContext } from '../types';
|
import type { UnifiedHistogramVisContext } from '../../types';
|
||||||
import { UnifiedHistogramSuggestionType } from '../types';
|
import { UnifiedHistogramSuggestionType } from '../../types';
|
||||||
|
|
||||||
describe('ChartConfigPanel', () => {
|
describe('ChartConfigPanel', () => {
|
||||||
it('should return a jsx element to edit the visualization', async () => {
|
it('should return a jsx element to edit the visualization', async () => {
|
|
@ -12,9 +12,9 @@ import type { AggregateQuery, Query } from '@kbn/es-query';
|
||||||
import { isEqual, isObject } from 'lodash';
|
import { isEqual, isObject } from 'lodash';
|
||||||
import type { LensEmbeddableOutput, Suggestion } from '@kbn/lens-plugin/public';
|
import type { LensEmbeddableOutput, Suggestion } from '@kbn/lens-plugin/public';
|
||||||
import type { Datatable } from '@kbn/expressions-plugin/common';
|
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 { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
||||||
import { deriveLensSuggestionFromLensAttributes } from '../utils/external_vis_context';
|
import { deriveLensSuggestionFromLensAttributes } from '../../utils/external_vis_context';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
UnifiedHistogramChartLoadEvent,
|
UnifiedHistogramChartLoadEvent,
|
||||||
|
@ -22,7 +22,7 @@ import {
|
||||||
UnifiedHistogramSuggestionContext,
|
UnifiedHistogramSuggestionContext,
|
||||||
UnifiedHistogramSuggestionType,
|
UnifiedHistogramSuggestionType,
|
||||||
UnifiedHistogramVisContext,
|
UnifiedHistogramVisContext,
|
||||||
} from '../types';
|
} from '../../types';
|
||||||
|
|
||||||
export function ChartConfigPanel({
|
export function ChartConfigPanel({
|
||||||
services,
|
services,
|
|
@ -11,11 +11,11 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||||
import { Histogram, HistogramProps } from './histogram';
|
import { Histogram, HistogramProps } from './histogram';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BehaviorSubject, Subject } from 'rxjs';
|
import { BehaviorSubject, Subject } from 'rxjs';
|
||||||
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||||
import { getLensVisMock } from '../__mocks__/lens_vis';
|
import { getLensVisMock } from '../../__mocks__/lens_vis';
|
||||||
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||||
import { createDefaultInspectorAdapters } from '@kbn/expressions-plugin/common';
|
import { createDefaultInspectorAdapters } from '@kbn/expressions-plugin/common';
|
||||||
import { UnifiedHistogramInput$ } from '../types';
|
import { UnifiedHistogramInput$ } from '../../types';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { RequestStatus } from '@kbn/inspector-plugin/public';
|
import { RequestStatus } from '@kbn/inspector-plugin/public';
|
||||||
import { getLensProps, useLensProps } from './hooks/use_lens_props';
|
import { getLensProps, useLensProps } from './hooks/use_lens_props';
|
|
@ -18,7 +18,7 @@ import type {
|
||||||
UnifiedHistogramChartContext,
|
UnifiedHistogramChartContext,
|
||||||
UnifiedHistogramServices,
|
UnifiedHistogramServices,
|
||||||
UnifiedHistogramVisContext,
|
UnifiedHistogramVisContext,
|
||||||
} from '../types';
|
} from '../../types';
|
||||||
import { useTimeRange } from './hooks/use_time_range';
|
import { useTimeRange } from './hooks/use_time_range';
|
||||||
import type { LensProps } from './hooks/use_lens_props';
|
import type { LensProps } from './hooks/use_lens_props';
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ export interface HistogramProps {
|
||||||
disabledActions?: LensEmbeddableInput['disabledActions'];
|
disabledActions?: LensEmbeddableInput['disabledActions'];
|
||||||
onFilter?: LensEmbeddableInput['onFilter'];
|
onFilter?: LensEmbeddableInput['onFilter'];
|
||||||
onBrushEnd?: LensEmbeddableInput['onBrushEnd'];
|
onBrushEnd?: LensEmbeddableInput['onBrushEnd'];
|
||||||
withDefaultActions: EmbeddableComponentProps['withDefaultActions'];
|
withDefaultActions?: EmbeddableComponentProps['withDefaultActions'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Histogram({
|
export function Histogram({
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { act, renderHook } from '@testing-library/react';
|
import { act, renderHook } from '@testing-library/react';
|
||||||
import { UnifiedHistogramChartContext } from '../../types';
|
import { UnifiedHistogramChartContext } from '../../../types';
|
||||||
import { useChartActions } from './use_chart_actions';
|
import { useChartActions } from './use_chart_actions';
|
||||||
|
|
||||||
describe('useChartActions', () => {
|
describe('useChartActions', () => {
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
import type { UnifiedHistogramChartContext } from '../../types';
|
import type { UnifiedHistogramChartContext } from '../../../types';
|
||||||
|
|
||||||
export const useChartActions = ({
|
export const useChartActions = ({
|
||||||
chart,
|
chart,
|
|
@ -10,9 +10,9 @@
|
||||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||||
import { waitFor, renderHook } from '@testing-library/react';
|
import { waitFor, renderHook } from '@testing-library/react';
|
||||||
import { dataViewMock } from '../../__mocks__/data_view';
|
import { dataViewMock } from '../../../__mocks__/data_view';
|
||||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield';
|
||||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../../../__mocks__/services';
|
||||||
import { useEditVisualization } from './use_edit_visualization';
|
import { useEditVisualization } from './use_edit_visualization';
|
||||||
|
|
||||||
const getTriggerCompatibleActions = unifiedHistogramServicesMock.uiActions
|
const getTriggerCompatibleActions = unifiedHistogramServicesMock.uiActions
|
|
@ -12,7 +12,7 @@ import type { TimeRange } from '@kbn/es-query';
|
||||||
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
|
||||||
import type { VISUALIZE_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
import type { VISUALIZE_FIELD_TRIGGER } from '@kbn/ui-actions-plugin/public';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
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
|
// Avoid taking a dependency on uiActionsPlugin just for this const
|
||||||
const visualizeFieldTrigger: typeof VISUALIZE_FIELD_TRIGGER = 'VISUALIZE_FIELD_TRIGGER';
|
const visualizeFieldTrigger: typeof VISUALIZE_FIELD_TRIGGER = 'VISUALIZE_FIELD_TRIGGER';
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import { useFetch } from './use_fetch';
|
import { useFetch } from './use_fetch';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { UnifiedHistogramInput$ } from '../../types';
|
import { UnifiedHistogramInput$ } from '../../../types';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
describe('useFetch', () => {
|
describe('useFetch', () => {
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { filter, share, tap } from 'rxjs';
|
import { filter, share, tap } from 'rxjs';
|
||||||
import { UnifiedHistogramInput$ } from '../../types';
|
import { UnifiedHistogramInput$ } from '../../../types';
|
||||||
|
|
||||||
export const useFetch = ({
|
export const useFetch = ({
|
||||||
input$,
|
input$,
|
|
@ -9,9 +9,9 @@
|
||||||
|
|
||||||
import { act, renderHook } from '@testing-library/react';
|
import { act, renderHook } from '@testing-library/react';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import type { UnifiedHistogramInputMessage } from '../../types';
|
import type { UnifiedHistogramInputMessage } from '../../../types';
|
||||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield';
|
||||||
import { getLensVisMock } from '../../__mocks__/lens_vis';
|
import { getLensVisMock } from '../../../__mocks__/lens_vis';
|
||||||
import { getLensProps, useLensProps } from './use_lens_props';
|
import { getLensProps, useLensProps } from './use_lens_props';
|
||||||
|
|
||||||
describe('useLensProps', () => {
|
describe('useLensProps', () => {
|
|
@ -16,8 +16,8 @@ import type {
|
||||||
UnifiedHistogramInputMessage,
|
UnifiedHistogramInputMessage,
|
||||||
UnifiedHistogramRequestContext,
|
UnifiedHistogramRequestContext,
|
||||||
UnifiedHistogramVisContext,
|
UnifiedHistogramVisContext,
|
||||||
} from '../../types';
|
} from '../../../types';
|
||||||
import { useStableCallback } from '../../hooks/use_stable_callback';
|
import { useStableCallback } from '../../../hooks/use_stable_callback';
|
||||||
|
|
||||||
export type LensProps = Pick<
|
export type LensProps = Pick<
|
||||||
EmbeddableComponentProps,
|
EmbeddableComponentProps,
|
|
@ -10,7 +10,7 @@
|
||||||
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
|
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
|
||||||
import { TimeRange } from '@kbn/data-plugin/common';
|
import { TimeRange } from '@kbn/data-plugin/common';
|
||||||
import { renderHook } from '@testing-library/react';
|
import { renderHook } from '@testing-library/react';
|
||||||
import { UnifiedHistogramBucketInterval } from '../../types';
|
import { UnifiedHistogramBucketInterval } from '../../../types';
|
||||||
import { useTimeRange } from './use_time_range';
|
import { useTimeRange } from './use_time_range';
|
||||||
|
|
||||||
jest.mock('@kbn/datemath', () => ({
|
jest.mock('@kbn/datemath', () => ({
|
|
@ -14,7 +14,7 @@ import { i18n } from '@kbn/i18n';
|
||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback, useMemo } from 'react';
|
||||||
import dateMath from '@kbn/datemath';
|
import dateMath from '@kbn/datemath';
|
||||||
import type { TimeRange } from '@kbn/data-plugin/common';
|
import type { TimeRange } from '@kbn/data-plugin/common';
|
||||||
import type { UnifiedHistogramBucketInterval } from '../../types';
|
import type { UnifiedHistogramBucketInterval } from '../../../types';
|
||||||
|
|
||||||
export const useTimeRange = ({
|
export const useTimeRange = ({
|
||||||
uiSettings,
|
uiSettings,
|
|
@ -8,8 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Filter } from '@kbn/es-query';
|
import { Filter } from '@kbn/es-query';
|
||||||
import { UnifiedHistogramFetchStatus, UnifiedHistogramInput$ } from '../../types';
|
import { UnifiedHistogramFetchStatus, UnifiedHistogramInput$ } from '../../../types';
|
||||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../../__mocks__/data_view_with_timefield';
|
||||||
import { useTotalHits } from './use_total_hits';
|
import { useTotalHits } from './use_total_hits';
|
||||||
import { useEffect as mockUseEffect } from 'react';
|
import { useEffect as mockUseEffect } from 'react';
|
||||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
|
@ -19,8 +19,8 @@ import {
|
||||||
UnifiedHistogramInputMessage,
|
UnifiedHistogramInputMessage,
|
||||||
UnifiedHistogramRequestContext,
|
UnifiedHistogramRequestContext,
|
||||||
UnifiedHistogramServices,
|
UnifiedHistogramServices,
|
||||||
} from '../../types';
|
} from '../../../types';
|
||||||
import { useStableCallback } from '../../hooks/use_stable_callback';
|
import { useStableCallback } from '../../../hooks/use_stable_callback';
|
||||||
|
|
||||||
export const useTotalHits = ({
|
export const useTotalHits = ({
|
||||||
services,
|
services,
|
|
@ -7,14 +7,5 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Plugin } from '@kbn/core/public';
|
export { UnifiedHistogramChart, type UnifiedHistogramChartProps } from './chart';
|
||||||
|
export { checkChartAvailability } from './utils/check_chart_availability';
|
||||||
export class UnifiedHistogramPublicPlugin implements Plugin<{}, {}, object, {}> {
|
|
||||||
public setup() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
public start() {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,7 +11,7 @@ import React, { useCallback } from 'react';
|
||||||
import { EuiSelectableOption } from '@elastic/eui';
|
import { EuiSelectableOption } from '@elastic/eui';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { search } from '@kbn/data-plugin/public';
|
import { search } from '@kbn/data-plugin/public';
|
||||||
import type { UnifiedHistogramChartContext } from '../types';
|
import type { UnifiedHistogramChartContext } from '../../types';
|
||||||
import { ToolbarSelector, ToolbarSelectorProps, SelectableEntry } from './toolbar_selector';
|
import { ToolbarSelector, ToolbarSelectorProps, SelectableEntry } from './toolbar_selector';
|
||||||
|
|
||||||
export interface TimeIntervalSelectorProps {
|
export interface TimeIntervalSelectorProps {
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
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 { calculateBounds } from '@kbn/data-plugin/public';
|
||||||
import { buildBucketInterval } from './build_bucket_interval';
|
import { buildBucketInterval } from './build_bucket_interval';
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||||
import { DataPublicPluginStart, search, tabifyAggResponse } from '@kbn/data-plugin/public';
|
import { DataPublicPluginStart, search, tabifyAggResponse } from '@kbn/data-plugin/public';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
import type { TimeRange } from '@kbn/es-query';
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
import type { UnifiedHistogramBucketInterval } from '../../types';
|
import type { UnifiedHistogramBucketInterval } from '../../../types';
|
||||||
import { getChartAggConfigs } from './get_chart_agg_configs';
|
import { getChartAggConfigs } from './get_chart_agg_configs';
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type DataView, DataViewType } from '@kbn/data-views-plugin/common';
|
import { type DataView, DataViewType } from '@kbn/data-views-plugin/common';
|
||||||
import { UnifiedHistogramChartContext } from '../types';
|
import { UnifiedHistogramChartContext } from '../../../types';
|
||||||
|
|
||||||
export function checkChartAvailability({
|
export function checkChartAvailability({
|
||||||
chart,
|
chart,
|
|
@ -7,7 +7,7 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* 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 { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||||
import { getChartAggConfigs } from './get_chart_agg_configs';
|
import { getChartAggConfigs } from './get_chart_agg_configs';
|
||||||
|
|
|
@ -7,5 +7,4 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export { Chart } from './chart';
|
export { UnifiedHistogramLayout, type UnifiedHistogramLayoutProps } from './layout';
|
||||||
export { checkChartAvailability } from './check_chart_availability';
|
|
|
@ -12,16 +12,18 @@ import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||||
import type { ReactWrapper } from 'enzyme';
|
import type { ReactWrapper } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { Chart } from '../chart';
|
import { UnifiedHistogramChart } from '../chart';
|
||||||
import {
|
import {
|
||||||
UnifiedHistogramChartContext,
|
UnifiedHistogramChartContext,
|
||||||
UnifiedHistogramFetchStatus,
|
UnifiedHistogramFetchStatus,
|
||||||
UnifiedHistogramHitsContext,
|
UnifiedHistogramHitsContext,
|
||||||
} from '../types';
|
} from '../../types';
|
||||||
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
||||||
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
||||||
import { UnifiedHistogramLayout, UnifiedHistogramLayoutProps } from './layout';
|
import { UnifiedHistogramLayout } from './layout';
|
||||||
import { ResizableLayout, ResizableLayoutMode } from '@kbn/resizable-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';
|
let mockBreakpoint = 'l';
|
||||||
|
|
||||||
|
@ -36,54 +38,65 @@ jest.mock('@elastic/eui', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Layout', () => {
|
describe('Layout', () => {
|
||||||
const createHits = (): UnifiedHistogramHitsContext => ({
|
|
||||||
status: UnifiedHistogramFetchStatus.complete,
|
|
||||||
total: 10,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createChart = (): UnifiedHistogramChartContext => ({
|
|
||||||
hidden: false,
|
|
||||||
timeInterval: 'auto',
|
|
||||||
});
|
|
||||||
|
|
||||||
const mountComponent = async ({
|
const mountComponent = async ({
|
||||||
services = unifiedHistogramServicesMock,
|
services = unifiedHistogramServicesMock,
|
||||||
hits = createHits(),
|
hits,
|
||||||
chart = createChart(),
|
chart,
|
||||||
container = null,
|
topPanelHeight,
|
||||||
...rest
|
...rest
|
||||||
}: Partial<Omit<UnifiedHistogramLayoutProps, 'hits' | 'chart'>> & {
|
}: Partial<UseUnifiedHistogramProps> & {
|
||||||
hits?: UnifiedHistogramHitsContext | null;
|
hits?: UnifiedHistogramHitsContext | null;
|
||||||
chart?: UnifiedHistogramChartContext | null;
|
chart?: UnifiedHistogramChartContext | null;
|
||||||
|
topPanelHeight?: number | null;
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
(searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation(
|
(searchSourceInstanceMock.fetch$ as jest.Mock).mockImplementation(
|
||||||
jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } }))
|
jest.fn().mockReturnValue(of({ rawResponse: { hits: { total: 2 } } }))
|
||||||
);
|
);
|
||||||
|
const Wrapper = () => {
|
||||||
const component = mountWithIntl(
|
const unifiedHistogram = useUnifiedHistogram({
|
||||||
<UnifiedHistogramLayout
|
services,
|
||||||
services={services}
|
initialState: {
|
||||||
hits={hits ?? undefined}
|
totalHitsStatus: hits?.status ?? UnifiedHistogramFetchStatus.complete,
|
||||||
chart={chart ?? undefined}
|
totalHitsResult: hits?.total ?? 10,
|
||||||
container={container}
|
chartHidden: chart?.hidden ?? false,
|
||||||
dataView={dataViewWithTimefieldMock}
|
timeInterval: chart?.timeInterval ?? 'auto',
|
||||||
query={{
|
},
|
||||||
|
dataView: dataViewWithTimefieldMock,
|
||||||
|
query: {
|
||||||
language: 'kuery',
|
language: 'kuery',
|
||||||
query: '',
|
query: '',
|
||||||
}}
|
},
|
||||||
filters={[]}
|
filters: [],
|
||||||
timeRange={{
|
timeRange: {
|
||||||
from: '2020-05-14T11:05:13.590',
|
from: '2020-05-14T11:05:13.590',
|
||||||
to: '2020-05-14T11:20:13.590',
|
to: '2020-05-14T11:20:13.590',
|
||||||
}}
|
},
|
||||||
lensSuggestionsApi={jest.fn()}
|
isChartLoading: false,
|
||||||
onSuggestionContextChange={jest.fn()}
|
...rest,
|
||||||
isChartLoading={false}
|
});
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return component;
|
if (!unifiedHistogram.isInitialized) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UnifiedHistogramLayout
|
||||||
|
container={null}
|
||||||
|
unifiedHistogramChart={<UnifiedHistogramChart {...unifiedHistogram.chartProps} />}
|
||||||
|
{...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(<Wrapper />);
|
||||||
|
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
|
||||||
|
return component.update();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setBreakpoint = (component: ReactWrapper, breakpoint: string) => {
|
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 () => {
|
it('should set the layout mode to ResizableLayoutMode.Static if chart.hidden is true', async () => {
|
||||||
const component = await mountComponent({
|
const component = await mountComponent({ chart: { timeInterval: 'auto', hidden: true } });
|
||||||
chart: {
|
|
||||||
...createChart(),
|
|
||||||
hidden: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(component.find(ResizableLayout).prop('mode')).toBe(ResizableLayoutMode.Static);
|
expect(component.find(ResizableLayout).prop('mode')).toBe(ResizableLayoutMode.Static);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -132,16 +140,20 @@ describe('Layout', () => {
|
||||||
const component = await mountComponent();
|
const component = await mountComponent();
|
||||||
setBreakpoint(component, 's');
|
setBreakpoint(component, 's');
|
||||||
const expectedHeight = component.find(ResizableLayout).prop('fixedPanelSize');
|
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`,
|
height: `${expectedHeight}px`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set a fixed height for Chart when layout mode is ResizableLayoutMode.Static and chart.hidden is true', async () => {
|
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');
|
setBreakpoint(component, 's');
|
||||||
const expectedHeight = component.find(ResizableLayout).prop('fixedPanelSize');
|
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`,
|
height: `${expectedHeight}px`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -150,7 +162,9 @@ describe('Layout', () => {
|
||||||
const component = await mountComponent({ chart: null });
|
const component = await mountComponent({ chart: null });
|
||||||
setBreakpoint(component, 's');
|
setBreakpoint(component, 's');
|
||||||
const expectedHeight = component.find(ResizableLayout).prop('fixedPanelSize');
|
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`,
|
height: `${expectedHeight}px`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -158,7 +172,7 @@ describe('Layout', () => {
|
||||||
|
|
||||||
describe('topPanelHeight', () => {
|
describe('topPanelHeight', () => {
|
||||||
it('should pass a default fixedPanelSize to ResizableLayout when the topPanelHeight prop is undefined', async () => {
|
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);
|
expect(component.find(ResizableLayout).prop('fixedPanelSize')).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<InPortal node={mainPanelNode}>
|
||||||
|
{React.isValidElement<{ isChartAvailable?: boolean }>(children)
|
||||||
|
? React.cloneElement(children, { isChartAvailable })
|
||||||
|
: children}
|
||||||
|
</InPortal>
|
||||||
|
<ResizableLayout
|
||||||
|
mode={panelsMode}
|
||||||
|
direction={ResizableLayoutDirection.Vertical}
|
||||||
|
container={container}
|
||||||
|
fixedPanelSize={currentTopPanelHeight}
|
||||||
|
minFixedPanelSize={defaultTopPanelHeight}
|
||||||
|
minFlexPanelSize={minMainPanelHeight}
|
||||||
|
fixedPanel={unifiedHistogramChart}
|
||||||
|
flexPanel={<OutPortal node={mainPanelNode} />}
|
||||||
|
data-test-subj="unifiedHistogram"
|
||||||
|
css={chartCss}
|
||||||
|
onFixedPanelSizeChange={onTopPanelHeightChange}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -12,11 +12,11 @@ import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||||
import { waitFor, renderHook, act } from '@testing-library/react';
|
import { waitFor, renderHook, act } from '@testing-library/react';
|
||||||
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
import type { DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||||
import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils';
|
import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils';
|
||||||
import { UnifiedHistogramFetchStatus, UnifiedHistogramSuggestionContext } from '../../types';
|
import { UnifiedHistogramFetchStatus, UnifiedHistogramSuggestionContext } from '../types';
|
||||||
import { dataViewMock } from '../../__mocks__/data_view';
|
import { dataViewMock } from '../__mocks__/data_view';
|
||||||
import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield';
|
import { dataViewWithTimefieldMock } from '../__mocks__/data_view_with_timefield';
|
||||||
import { lensAdaptersMock } from '../../__mocks__/lens_adapters';
|
import { lensAdaptersMock } from '../__mocks__/lens_adapters';
|
||||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
||||||
import {
|
import {
|
||||||
createStateService,
|
createStateService,
|
||||||
UnifiedHistogramState,
|
UnifiedHistogramState,
|
||||||
|
@ -64,6 +64,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.current).toMatchInlineSnapshot(`
|
expect(result.current).toMatchInlineSnapshot(`
|
||||||
|
@ -123,6 +124,7 @@ describe('useStateProps', () => {
|
||||||
"onTimeIntervalChange": [Function],
|
"onTimeIntervalChange": [Function],
|
||||||
"onTopPanelHeightChange": [Function],
|
"onTopPanelHeightChange": [Function],
|
||||||
"onTotalHitsChange": [Function],
|
"onTotalHitsChange": [Function],
|
||||||
|
"onVisContextChanged": undefined,
|
||||||
"request": Object {
|
"request": Object {
|
||||||
"adapter": RequestAdapter {
|
"adapter": RequestAdapter {
|
||||||
"_events": Object {},
|
"_events": Object {},
|
||||||
|
@ -135,6 +137,7 @@ describe('useStateProps', () => {
|
||||||
},
|
},
|
||||||
"searchSessionId": "123",
|
"searchSessionId": "123",
|
||||||
},
|
},
|
||||||
|
"topPanelHeight": 100,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@ -153,6 +156,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.current).toMatchInlineSnapshot(`
|
expect(result.current).toMatchInlineSnapshot(`
|
||||||
|
@ -212,6 +216,7 @@ describe('useStateProps', () => {
|
||||||
"onTimeIntervalChange": [Function],
|
"onTimeIntervalChange": [Function],
|
||||||
"onTopPanelHeightChange": [Function],
|
"onTopPanelHeightChange": [Function],
|
||||||
"onTotalHitsChange": [Function],
|
"onTotalHitsChange": [Function],
|
||||||
|
"onVisContextChanged": undefined,
|
||||||
"request": Object {
|
"request": Object {
|
||||||
"adapter": RequestAdapter {
|
"adapter": RequestAdapter {
|
||||||
"_events": Object {},
|
"_events": Object {},
|
||||||
|
@ -224,6 +229,7 @@ describe('useStateProps', () => {
|
||||||
},
|
},
|
||||||
"searchSessionId": "123",
|
"searchSessionId": "123",
|
||||||
},
|
},
|
||||||
|
"topPanelHeight": 100,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
@ -251,6 +257,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.current.chart).toStrictEqual({ hidden: false, timeInterval: 'auto' });
|
expect(result.current.chart).toStrictEqual({ hidden: false, timeInterval: 'auto' });
|
||||||
|
@ -290,6 +297,7 @@ describe('useStateProps', () => {
|
||||||
columns: esqlColumns,
|
columns: esqlColumns,
|
||||||
breakdownField,
|
breakdownField,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -332,6 +340,7 @@ describe('useStateProps', () => {
|
||||||
columns: esqlColumns,
|
columns: esqlColumns,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const { onBreakdownFieldChange } = result.current;
|
const { onBreakdownFieldChange } = result.current;
|
||||||
|
@ -357,6 +366,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.current).toMatchInlineSnapshot(`
|
expect(result.current).toMatchInlineSnapshot(`
|
||||||
|
@ -411,6 +421,7 @@ describe('useStateProps', () => {
|
||||||
"onTimeIntervalChange": [Function],
|
"onTimeIntervalChange": [Function],
|
||||||
"onTopPanelHeightChange": [Function],
|
"onTopPanelHeightChange": [Function],
|
||||||
"onTotalHitsChange": [Function],
|
"onTotalHitsChange": [Function],
|
||||||
|
"onVisContextChanged": undefined,
|
||||||
"request": Object {
|
"request": Object {
|
||||||
"adapter": RequestAdapter {
|
"adapter": RequestAdapter {
|
||||||
"_events": Object {},
|
"_events": Object {},
|
||||||
|
@ -423,6 +434,7 @@ describe('useStateProps', () => {
|
||||||
},
|
},
|
||||||
"searchSessionId": "123",
|
"searchSessionId": "123",
|
||||||
},
|
},
|
||||||
|
"topPanelHeight": 100,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@ -441,6 +453,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(result.current).toMatchInlineSnapshot(`
|
expect(result.current).toMatchInlineSnapshot(`
|
||||||
|
@ -495,6 +508,7 @@ describe('useStateProps', () => {
|
||||||
"onTimeIntervalChange": [Function],
|
"onTimeIntervalChange": [Function],
|
||||||
"onTopPanelHeightChange": [Function],
|
"onTopPanelHeightChange": [Function],
|
||||||
"onTotalHitsChange": [Function],
|
"onTotalHitsChange": [Function],
|
||||||
|
"onVisContextChanged": undefined,
|
||||||
"request": Object {
|
"request": Object {
|
||||||
"adapter": RequestAdapter {
|
"adapter": RequestAdapter {
|
||||||
"_events": Object {},
|
"_events": Object {},
|
||||||
|
@ -507,6 +521,7 @@ describe('useStateProps', () => {
|
||||||
},
|
},
|
||||||
"searchSessionId": "123",
|
"searchSessionId": "123",
|
||||||
},
|
},
|
||||||
|
"topPanelHeight": 100,
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
@ -525,6 +540,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -602,6 +618,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
(stateService.setLensRequestAdapter as jest.Mock).mockClear();
|
(stateService.setLensRequestAdapter as jest.Mock).mockClear();
|
||||||
|
@ -626,6 +643,7 @@ describe('useStateProps', () => {
|
||||||
columns: undefined,
|
columns: undefined,
|
||||||
breakdownField: undefined,
|
breakdownField: undefined,
|
||||||
onBreakdownFieldChange: undefined,
|
onBreakdownFieldChange: undefined,
|
||||||
|
onVisContextChanged: undefined,
|
||||||
};
|
};
|
||||||
const hook = renderHook((props: Parameters<typeof useStateProps>[0]) => useStateProps(props), {
|
const hook = renderHook((props: Parameters<typeof useStateProps>[0]) => useStateProps(props), {
|
||||||
initialProps,
|
initialProps,
|
|
@ -16,10 +16,12 @@ import { convertDatatableColumnToDataViewFieldSpec } from '@kbn/data-view-utils'
|
||||||
import { useCallback, useEffect, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
UnifiedHistogramChartLoadEvent,
|
UnifiedHistogramChartLoadEvent,
|
||||||
|
UnifiedHistogramExternalVisContextStatus,
|
||||||
UnifiedHistogramFetchStatus,
|
UnifiedHistogramFetchStatus,
|
||||||
UnifiedHistogramServices,
|
UnifiedHistogramServices,
|
||||||
UnifiedHistogramSuggestionContext,
|
UnifiedHistogramSuggestionContext,
|
||||||
} from '../../types';
|
UnifiedHistogramVisContext,
|
||||||
|
} from '../types';
|
||||||
import type { UnifiedHistogramStateService } from '../services/state_service';
|
import type { UnifiedHistogramStateService } from '../services/state_service';
|
||||||
import {
|
import {
|
||||||
chartHiddenSelector,
|
chartHiddenSelector,
|
||||||
|
@ -28,9 +30,12 @@ import {
|
||||||
totalHitsStatusSelector,
|
totalHitsStatusSelector,
|
||||||
lensAdaptersSelector,
|
lensAdaptersSelector,
|
||||||
lensDataLoadingSelector$,
|
lensDataLoadingSelector$,
|
||||||
|
topPanelHeightSelector,
|
||||||
} from '../utils/state_selectors';
|
} 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 { setBreakdownField } from '../utils/local_storage_utils';
|
||||||
|
import { exportVisContext } from '../utils/external_vis_context';
|
||||||
|
import { UseUnifiedHistogramProps } from './use_unified_histogram';
|
||||||
|
|
||||||
export const useStateProps = ({
|
export const useStateProps = ({
|
||||||
services,
|
services,
|
||||||
|
@ -43,6 +48,7 @@ export const useStateProps = ({
|
||||||
columns,
|
columns,
|
||||||
breakdownField,
|
breakdownField,
|
||||||
onBreakdownFieldChange: originalOnBreakdownFieldChange,
|
onBreakdownFieldChange: originalOnBreakdownFieldChange,
|
||||||
|
onVisContextChanged: originalOnVisContextChanged,
|
||||||
}: {
|
}: {
|
||||||
services: UnifiedHistogramServices;
|
services: UnifiedHistogramServices;
|
||||||
localStorageKeyPrefix: string | undefined;
|
localStorageKeyPrefix: string | undefined;
|
||||||
|
@ -54,13 +60,21 @@ export const useStateProps = ({
|
||||||
columns: DatatableColumn[] | undefined;
|
columns: DatatableColumn[] | undefined;
|
||||||
breakdownField: string | undefined;
|
breakdownField: string | undefined;
|
||||||
onBreakdownFieldChange: ((breakdownField: string | undefined) => void) | 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 chartHidden = useStateSelector(stateService?.state$, chartHiddenSelector);
|
||||||
const timeInterval = useStateSelector(stateService?.state$, timeIntervalSelector);
|
const timeInterval = useStateSelector(stateService?.state$, timeIntervalSelector);
|
||||||
const totalHitsResult = useStateSelector(stateService?.state$, totalHitsResultSelector);
|
const totalHitsResult = useStateSelector(stateService?.state$, totalHitsResultSelector);
|
||||||
const totalHitsStatus = useStateSelector(stateService?.state$, totalHitsStatusSelector);
|
const totalHitsStatus = useStateSelector(stateService?.state$, totalHitsStatusSelector);
|
||||||
const lensAdapters = useStateSelector(stateService?.state$, lensAdaptersSelector);
|
const lensAdapters = useStateSelector(stateService?.state$, lensAdaptersSelector);
|
||||||
const lensDataLoading$ = useStateSelector(stateService?.state$, lensDataLoadingSelector$);
|
const lensDataLoading$ = useStateSelector(stateService?.state$, lensDataLoadingSelector$);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contexts
|
* Contexts
|
||||||
*/
|
*/
|
||||||
|
@ -132,8 +146,8 @@ export const useStateProps = ({
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const onTopPanelHeightChange = useCallback(
|
const onTopPanelHeightChange = useCallback(
|
||||||
(topPanelHeight: number | undefined) => {
|
(newTopPanelHeight: number | undefined) => {
|
||||||
stateService?.setTopPanelHeight(topPanelHeight);
|
stateService?.setTopPanelHeight(newTopPanelHeight);
|
||||||
},
|
},
|
||||||
[stateService]
|
[stateService]
|
||||||
);
|
);
|
||||||
|
@ -186,6 +200,18 @@ export const useStateProps = ({
|
||||||
[stateService]
|
[stateService]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onVisContextChanged: UseUnifiedHistogramProps['onVisContextChanged'] = useMemo(() => {
|
||||||
|
if (!originalOnVisContextChanged || !isPlainRecord) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (visContext, externalVisContextStatus) => {
|
||||||
|
const minifiedVisContext = exportVisContext(visContext);
|
||||||
|
|
||||||
|
originalOnVisContextChanged(minifiedVisContext, externalVisContextStatus);
|
||||||
|
};
|
||||||
|
}, [isPlainRecord, originalOnVisContextChanged]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Effects
|
* Effects
|
||||||
*/
|
*/
|
||||||
|
@ -205,6 +231,7 @@ export const useStateProps = ({
|
||||||
}, [chart, chartHidden, stateService]);
|
}, [chart, chartHidden, stateService]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
topPanelHeight,
|
||||||
hits,
|
hits,
|
||||||
chart,
|
chart,
|
||||||
breakdown,
|
breakdown,
|
||||||
|
@ -219,5 +246,6 @@ export const useStateProps = ({
|
||||||
onChartLoad,
|
onChartLoad,
|
||||||
onBreakdownFieldChange,
|
onBreakdownFieldChange,
|
||||||
onSuggestionContextChange,
|
onSuggestionContextChange,
|
||||||
|
onVisContextChanged,
|
||||||
};
|
};
|
||||||
};
|
};
|
|
@ -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' });
|
||||||
|
});
|
||||||
|
});
|
|
@ -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<UnifiedHistogramStateOptions, 'services'> & {
|
||||||
|
/**
|
||||||
|
* 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<UnifiedHistogramSuggestionContext> = 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<LensVisService>();
|
||||||
|
const [input$] = useState(() => new Subject<UnifiedHistogramInputMessage>());
|
||||||
|
const [api, setApi] = useState<UnifiedHistogramApi>();
|
||||||
|
|
||||||
|
// 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<Record<string, DatatableColumn>>((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<UnifiedHistogramChartProps | undefined>(() => {
|
||||||
|
return lensVisService
|
||||||
|
? {
|
||||||
|
...props,
|
||||||
|
...stateProps,
|
||||||
|
input$,
|
||||||
|
chart,
|
||||||
|
isChartAvailable,
|
||||||
|
requestParams,
|
||||||
|
lensVisService,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
}, [chart, input$, isChartAvailable, lensVisService, props, requestParams, stateProps]);
|
||||||
|
const layoutProps = useMemo<UnifiedHistogramPartialLayoutProps>(
|
||||||
|
() => ({
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
};
|
|
@ -7,27 +7,6 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* 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 {
|
export type {
|
||||||
UnifiedHistogramServices,
|
UnifiedHistogramServices,
|
||||||
UnifiedHistogramChartLoadEvent,
|
UnifiedHistogramChartLoadEvent,
|
||||||
|
@ -35,6 +14,29 @@ export type {
|
||||||
UnifiedHistogramVisContext,
|
UnifiedHistogramVisContext,
|
||||||
} from './types';
|
} from './types';
|
||||||
export { UnifiedHistogramFetchStatus, UnifiedHistogramExternalVisContextStatus } 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';
|
|
@ -10,11 +10,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
preset: '@kbn/test',
|
preset: '@kbn/test',
|
||||||
rootDir: '../../../../..',
|
rootDir: '../../../../..',
|
||||||
roots: ['<rootDir>/src/platform/plugins/shared/unified_histogram'],
|
roots: ['<rootDir>/src/platform/packages/shared/kbn-unified-histogram'],
|
||||||
coverageDirectory:
|
|
||||||
'<rootDir>/target/kibana-coverage/jest/src/platform/plugins/shared/unified_histogram',
|
|
||||||
coverageReporters: ['text', 'html'],
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'<rootDir>/src/platform/plugins/shared/unified_histogram/{common,public,server}/**/*.{ts,tsx}',
|
|
||||||
],
|
|
||||||
};
|
};
|
|
@ -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"
|
||||||
|
}
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import type { UnifiedHistogramApi } from './container';
|
import { UnifiedHistogramApi } from './hooks/use_unified_histogram';
|
||||||
|
|
||||||
export const createMockUnifiedHistogramApi = () => {
|
export const createMockUnifiedHistogramApi = () => {
|
||||||
const api: UnifiedHistogramApi = {
|
const api: UnifiedHistogramApi = {
|
|
@ -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"
|
||||||
|
}
|
|
@ -34,8 +34,8 @@ import {
|
||||||
mapVisToChartType,
|
mapVisToChartType,
|
||||||
computeInterval,
|
computeInterval,
|
||||||
} from '@kbn/visualization-utils';
|
} from '@kbn/visualization-utils';
|
||||||
import { LegendSize } from '@kbn/visualizations-plugin/public';
|
import type { LegendSize } from '@kbn/visualizations-plugin/public';
|
||||||
import { XYConfiguration } from '@kbn/visualizations-plugin/common';
|
import type { XYConfiguration } from '@kbn/visualizations-plugin/common';
|
||||||
import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common';
|
import type { Datatable, DatatableColumn } from '@kbn/expressions-plugin/common';
|
||||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||||
import { fieldSupportsBreakdown } from '@kbn/field-utils';
|
import { fieldSupportsBreakdown } from '@kbn/field-utils';
|
||||||
|
@ -413,6 +413,7 @@ export class LensVisService {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const legendSize: `${LegendSize.EXTRA_LARGE}` = 'xlarge';
|
||||||
const visualizationState = {
|
const visualizationState = {
|
||||||
layers: [
|
layers: [
|
||||||
{
|
{
|
||||||
|
@ -435,7 +436,7 @@ export class LensVisService {
|
||||||
legend: {
|
legend: {
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
position: 'right',
|
position: 'right',
|
||||||
legendSize: LegendSize.EXTRA_LARGE,
|
legendSize,
|
||||||
shouldTruncate: false,
|
shouldTruncate: false,
|
||||||
},
|
},
|
||||||
preferredSeriesType: 'bar_stacked',
|
preferredSeriesType: 'bar_stacked',
|
|
@ -8,9 +8,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
import { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||||
import { UnifiedHistogramFetchStatus } from '../..';
|
import { UnifiedHistogramFetchStatus } from '..';
|
||||||
import { unifiedHistogramServicesMock } from '../../__mocks__/services';
|
import { unifiedHistogramServicesMock } from '../__mocks__/services';
|
||||||
import { lensAdaptersMock } from '../../__mocks__/lens_adapters';
|
import { lensAdaptersMock } from '../__mocks__/lens_adapters';
|
||||||
import {
|
import {
|
||||||
getChartHidden,
|
getChartHidden,
|
||||||
getTopPanelHeight,
|
getTopPanelHeight,
|
|
@ -10,15 +10,15 @@
|
||||||
import type { RequestAdapter } from '@kbn/inspector-plugin/common';
|
import type { RequestAdapter } from '@kbn/inspector-plugin/common';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { PublishingSubject } from '@kbn/presentation-publishing';
|
import { PublishingSubject } from '@kbn/presentation-publishing';
|
||||||
import { UnifiedHistogramFetchStatus } from '../..';
|
import { UnifiedHistogramFetchStatus } from '..';
|
||||||
import type { UnifiedHistogramServices, UnifiedHistogramChartLoadEvent } from '../../types';
|
import type { UnifiedHistogramServices, UnifiedHistogramChartLoadEvent } from '../types';
|
||||||
import {
|
import {
|
||||||
getChartHidden,
|
getChartHidden,
|
||||||
getTopPanelHeight,
|
getTopPanelHeight,
|
||||||
setChartHidden,
|
setChartHidden,
|
||||||
setTopPanelHeight,
|
setTopPanelHeight,
|
||||||
} from '../utils/local_storage_utils';
|
} from '../utils/local_storage_utils';
|
||||||
import type { UnifiedHistogramSuggestionContext } from '../../types';
|
import type { UnifiedHistogramSuggestionContext } from '../types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current state of the container
|
* The current state of the container
|
|
@ -2,40 +2,39 @@
|
||||||
"extends": "../../../../../tsconfig.base.json",
|
"extends": "../../../../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "target/types",
|
"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_references": [
|
||||||
"@kbn/core",
|
|
||||||
"@kbn/data-plugin",
|
|
||||||
"@kbn/data-views-plugin",
|
"@kbn/data-views-plugin",
|
||||||
"@kbn/lens-plugin",
|
|
||||||
"@kbn/field-formats-plugin",
|
|
||||||
"@kbn/inspector-plugin",
|
|
||||||
"@kbn/expressions-plugin",
|
"@kbn/expressions-plugin",
|
||||||
"@kbn/test-jest-helpers",
|
"@kbn/lens-plugin",
|
||||||
"@kbn/i18n",
|
"@kbn/data-plugin",
|
||||||
"@kbn/es-query",
|
"@kbn/field-formats-plugin",
|
||||||
"@kbn/core-ui-settings-browser",
|
"@kbn/data-view-utils",
|
||||||
"@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/field-utils",
|
"@kbn/field-utils",
|
||||||
"@kbn/esql-utils",
|
"@kbn/esql-utils",
|
||||||
"@kbn/discover-utils",
|
"@kbn/i18n",
|
||||||
"@kbn/visualization-utils",
|
"@kbn/test-jest-helpers",
|
||||||
"@kbn/search-types",
|
"@kbn/core",
|
||||||
|
"@kbn/shared-ux-button-toolbar",
|
||||||
|
"@kbn/es-query",
|
||||||
"@kbn/presentation-publishing",
|
"@kbn/presentation-publishing",
|
||||||
"@kbn/data-view-utils",
|
"@kbn/inspector-plugin",
|
||||||
],
|
"@kbn/search-types",
|
||||||
"exclude": [
|
"@kbn/discover-utils",
|
||||||
"target/**/*",
|
"@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"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* 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 CHART_HIDDEN_KEY = 'chartHidden';
|
||||||
export const HISTOGRAM_HEIGHT_KEY = 'histogramHeight';
|
export const HISTOGRAM_HEIGHT_KEY = 'histogramHeight';
|
|
@ -30,7 +30,6 @@
|
||||||
"expressions",
|
"expressions",
|
||||||
"unifiedDocViewer",
|
"unifiedDocViewer",
|
||||||
"unifiedSearch",
|
"unifiedSearch",
|
||||||
"unifiedHistogram",
|
|
||||||
"contentManagement",
|
"contentManagement",
|
||||||
"discoverShared"
|
"discoverShared"
|
||||||
],
|
],
|
||||||
|
|
|
@ -17,12 +17,14 @@ import type { RuntimeStateManager } from '../application/main/state_management/r
|
||||||
import {
|
import {
|
||||||
createInternalStateStore,
|
createInternalStateStore,
|
||||||
createRuntimeStateManager,
|
createRuntimeStateManager,
|
||||||
|
selectTabRuntimeState,
|
||||||
} from '../application/main/state_management/redux';
|
} from '../application/main/state_management/redux';
|
||||||
import type { DiscoverServices, HistoryLocationState } from '../build_services';
|
import type { DiscoverServices, HistoryLocationState } from '../build_services';
|
||||||
import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
import type { IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
|
import { createKbnUrlStateStorage, withNotifyOnErrors } from '@kbn/kibana-utils-plugin/public';
|
||||||
import type { History } from 'history';
|
import type { History } from 'history';
|
||||||
import type { DiscoverCustomizationContext } from '../customizations';
|
import type { DiscoverCustomizationContext } from '../customizations';
|
||||||
|
import { createCustomizationService } from '../customizations/customization_service';
|
||||||
|
|
||||||
export function getDiscoverStateMock({
|
export function getDiscoverStateMock({
|
||||||
isTimeBased = true,
|
isTimeBased = true,
|
||||||
|
@ -71,6 +73,15 @@ export function getDiscoverStateMock({
|
||||||
internalState,
|
internalState,
|
||||||
runtimeStateManager,
|
runtimeStateManager,
|
||||||
});
|
});
|
||||||
|
const tabRuntimeState = selectTabRuntimeState(
|
||||||
|
runtimeStateManager,
|
||||||
|
internalState.getState().tabs.unsafeCurrentId
|
||||||
|
);
|
||||||
|
tabRuntimeState.customizationService$.next({
|
||||||
|
...createCustomizationService(),
|
||||||
|
cleanup: async () => {},
|
||||||
|
});
|
||||||
|
tabRuntimeState.stateContainer$.next(container);
|
||||||
if (savedSearch !== false) {
|
if (savedSearch !== false) {
|
||||||
container.savedSearchState.set(
|
container.savedSearchState.set(
|
||||||
savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock
|
savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock
|
||||||
|
|
|
@ -63,6 +63,9 @@ export function createDiscoverServicesMock(): DiscoverServices {
|
||||||
dataPlugin.query.timefilter.timefilter.getTime = jest.fn(() => {
|
dataPlugin.query.timefilter.timefilter.getTime = jest.fn(() => {
|
||||||
return { from: 'now-15m', to: 'now' };
|
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(() => {
|
dataPlugin.query.timefilter.timefilter.getRefreshInterval = jest.fn(() => {
|
||||||
return { pause: true, value: 1000 };
|
return { pause: true, value: 1000 };
|
||||||
});
|
});
|
||||||
|
|
|
@ -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<string, ChartPortalNode>;
|
||||||
|
|
||||||
|
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>({});
|
||||||
|
|
||||||
|
chartPortalNodes.current = updatePortals(chartPortalNodes.current, allTabIds);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Object.keys(chartPortalNodes.current).map((tabId) => {
|
||||||
|
return (
|
||||||
|
<InPortal key={tabId} node={chartPortalNodes.current[tabId]}>
|
||||||
|
<UnifiedHistogramGuard tabId={tabId} runtimeStateManager={runtimeStateManager} />
|
||||||
|
</InPortal>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<CurrentTabProvider
|
||||||
|
currentTabId={currentTabId}
|
||||||
|
currentChartPortalNode={chartPortalNodes.current[currentTabId]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</CurrentTabProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePortals = (portals: ChartPortalNodes, tabsIds: string[]) =>
|
||||||
|
tabsIds.reduce<ChartPortalNodes>(
|
||||||
|
(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 (
|
||||||
|
<CurrentTabProvider currentTabId={tabId}>
|
||||||
|
<DiscoverCustomizationProvider value={currentCustomizationService}>
|
||||||
|
<DiscoverMainProvider value={currentStateContainer}>
|
||||||
|
<RuntimeStateProvider currentDataView={currentDataView} adHocDataViews={adHocDataViews}>
|
||||||
|
<UnifiedHistogramChartWrapper
|
||||||
|
stateContainer={currentStateContainer}
|
||||||
|
panelsToggle={panelsToggle}
|
||||||
|
/>
|
||||||
|
</RuntimeStateProvider>
|
||||||
|
</DiscoverMainProvider>
|
||||||
|
</DiscoverCustomizationProvider>
|
||||||
|
</CurrentTabProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type UnifiedHistogramChartProps = Pick<UnifiedHistogramGuardProps, 'panelsToggle'> & {
|
||||||
|
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 (
|
||||||
|
<UnifiedHistogramChart
|
||||||
|
{...unifiedHistogram.chartProps}
|
||||||
|
renderCustomChartToggleActions={renderCustomChartToggleActions}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -7,5 +7,4 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type { UnifiedHistogramLayoutProps } from './layout';
|
export { type ChartPortalNode, ChartPortalsRenderer } from './chart_portals_renderer';
|
||||||
export { UnifiedHistogramLayout } from './layout';
|
|
|
@ -7,7 +7,6 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ReactElement } from 'react';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { AggregateQuery, Query } from '@kbn/es-query';
|
import type { AggregateQuery, Query } from '@kbn/es-query';
|
||||||
import { renderHook, act } from '@testing-library/react';
|
import { renderHook, act } from '@testing-library/react';
|
||||||
|
@ -15,17 +14,15 @@ import { BehaviorSubject, Subject } from 'rxjs';
|
||||||
import { FetchStatus } from '../../../types';
|
import { FetchStatus } from '../../../types';
|
||||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||||
import type { UseDiscoverHistogramProps } from './use_discover_histogram';
|
|
||||||
import { useDiscoverHistogram } from './use_discover_histogram';
|
import { useDiscoverHistogram } from './use_discover_histogram';
|
||||||
import { setTimeout } from 'timers/promises';
|
import { setTimeout } from 'timers/promises';
|
||||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||||
import { DiscoverMainProvider } from '../../state_management/discover_state_provider';
|
import { DiscoverMainProvider } from '../../state_management/discover_state_provider';
|
||||||
import { RequestAdapter } from '@kbn/inspector-plugin/public';
|
import { RequestAdapter } from '@kbn/inspector-plugin/public';
|
||||||
import type { UnifiedHistogramState } from '@kbn/unified-histogram-plugin/public';
|
import type { UnifiedHistogramState } from '@kbn/unified-histogram';
|
||||||
import { UnifiedHistogramFetchStatus } from '@kbn/unified-histogram-plugin/public';
|
import { UnifiedHistogramFetchStatus } from '@kbn/unified-histogram';
|
||||||
import { createMockUnifiedHistogramApi } from '@kbn/unified-histogram-plugin/public/mocks';
|
import { createMockUnifiedHistogramApi } from '@kbn/unified-histogram/mocks';
|
||||||
import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages';
|
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 type { UnifiedHistogramCustomization } from '../../../../customizations/customization_types/histogram_customization';
|
||||||
import { useDiscoverCustomization } from '../../../../customizations';
|
import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||||
|
@ -111,44 +108,26 @@ describe('useDiscoverHistogram', () => {
|
||||||
return stateContainer;
|
return stateContainer;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderUseDiscoverHistogram = async ({
|
const renderUseDiscoverHistogram = async (
|
||||||
stateContainer = getStateContainer(),
|
stateContainer: DiscoverStateContainer = getStateContainer()
|
||||||
inspectorAdapters = { requests: new RequestAdapter() },
|
) => {
|
||||||
hideChart = false,
|
|
||||||
}: {
|
|
||||||
stateContainer?: DiscoverStateContainer;
|
|
||||||
inspectorAdapters?: InspectorAdapters;
|
|
||||||
hideChart?: boolean;
|
|
||||||
} = {}) => {
|
|
||||||
const initialProps = {
|
|
||||||
stateContainer,
|
|
||||||
inspectorAdapters,
|
|
||||||
hideChart,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Wrapper = ({ children }: React.PropsWithChildren<unknown>) => (
|
const Wrapper = ({ children }: React.PropsWithChildren<unknown>) => (
|
||||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<RuntimeStateProvider currentDataView={dataViewMockWithTimeField} adHocDataViews={[]}>
|
<RuntimeStateProvider currentDataView={dataViewMockWithTimeField} adHocDataViews={[]}>
|
||||||
{children as ReactElement}
|
{children}
|
||||||
</RuntimeStateProvider>
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
</CurrentTabProvider>
|
</CurrentTabProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const hook = renderHook(
|
const hook = renderHook(() => useDiscoverHistogram(stateContainer), {
|
||||||
(props: UseDiscoverHistogramProps) => {
|
wrapper: Wrapper,
|
||||||
return useDiscoverHistogram(props);
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
wrapper: Wrapper,
|
|
||||||
initialProps,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await act(() => setTimeout(0));
|
await act(() => setTimeout(0));
|
||||||
|
|
||||||
return { hook, initialProps };
|
return { hook };
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -169,9 +148,9 @@ describe('useDiscoverHistogram', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('initialization', () => {
|
describe('initialization', () => {
|
||||||
it('should return the expected parameters from getCreationOptions', async () => {
|
it('should return the expected parameters', async () => {
|
||||||
const { hook } = await renderUseDiscoverHistogram();
|
const { hook } = await renderUseDiscoverHistogram();
|
||||||
const params = hook.result.current.getCreationOptions();
|
const params = hook.result.current;
|
||||||
expect(params?.localStorageKeyPrefix).toBe('discover');
|
expect(params?.localStorageKeyPrefix).toBe('discover');
|
||||||
expect(Object.keys(params?.initialState ?? {})).toEqual([
|
expect(Object.keys(params?.initialState ?? {})).toEqual([
|
||||||
'chartHidden',
|
'chartHidden',
|
||||||
|
@ -200,7 +179,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
const api = createMockUnifiedHistogramApi();
|
const api = createMockUnifiedHistogramApi();
|
||||||
jest.spyOn(api.state$, 'subscribe');
|
jest.spyOn(api.state$, 'subscribe');
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
expect(api.state$.subscribe).toHaveBeenCalledTimes(2);
|
expect(api.state$.subscribe).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
|
@ -208,7 +187,8 @@ describe('useDiscoverHistogram', () => {
|
||||||
it('should sync Unified Histogram state with the state container', async () => {
|
it('should sync Unified Histogram state with the state container', async () => {
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const inspectorAdapters = { requests: new RequestAdapter(), lensRequests: undefined };
|
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 lensRequestAdapter = new RequestAdapter();
|
||||||
const state = {
|
const state = {
|
||||||
timeInterval: '1m',
|
timeInterval: '1m',
|
||||||
|
@ -219,7 +199,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
const api = createMockUnifiedHistogramApi();
|
const api = createMockUnifiedHistogramApi();
|
||||||
api.state$ = new BehaviorSubject({ ...state, lensRequestAdapter });
|
api.state$ = new BehaviorSubject({ ...state, lensRequestAdapter });
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
expect(inspectorAdapters.lensRequests).toBe(lensRequestAdapter);
|
expect(inspectorAdapters.lensRequests).toBe(lensRequestAdapter);
|
||||||
expect(stateContainer.appState.update).toHaveBeenCalledWith({
|
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 () => {
|
it('should not sync Unified Histogram state with the state container if there are no changes', async () => {
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
const containerState = stateContainer.appState.getState();
|
const containerState = stateContainer.appState.getState();
|
||||||
const state = {
|
const state = {
|
||||||
timeInterval: containerState.interval,
|
timeInterval: containerState.interval,
|
||||||
|
@ -241,14 +221,14 @@ describe('useDiscoverHistogram', () => {
|
||||||
const api = createMockUnifiedHistogramApi();
|
const api = createMockUnifiedHistogramApi();
|
||||||
api.state$ = new BehaviorSubject(state);
|
api.state$ = new BehaviorSubject(state);
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
expect(stateContainer.appState.update).not.toHaveBeenCalled();
|
expect(stateContainer.appState.update).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync the state container state with Unified Histogram', async () => {
|
it('should sync the state container state with Unified Histogram', async () => {
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
const api = createMockUnifiedHistogramApi();
|
const api = createMockUnifiedHistogramApi();
|
||||||
let params: Partial<UnifiedHistogramState> = {};
|
let params: Partial<UnifiedHistogramState> = {};
|
||||||
api.setTotalHits = jest.fn((p) => {
|
api.setTotalHits = jest.fn((p) => {
|
||||||
|
@ -261,7 +241,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
params = { ...params, timeInterval };
|
params = { ...params, timeInterval };
|
||||||
});
|
});
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
stateContainer.appState.update({ hideChart: true, interval: '1m' });
|
stateContainer.appState.update({ hideChart: true, interval: '1m' });
|
||||||
expect(api.setTotalHits).not.toHaveBeenCalled();
|
expect(api.setTotalHits).not.toHaveBeenCalled();
|
||||||
|
@ -272,7 +252,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
|
|
||||||
it('should exclude totalHitsStatus and totalHitsResult from Unified Histogram state updates', async () => {
|
it('should exclude totalHitsStatus and totalHitsResult from Unified Histogram state updates', async () => {
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
const containerState = stateContainer.appState.getState();
|
const containerState = stateContainer.appState.getState();
|
||||||
const state = {
|
const state = {
|
||||||
timeInterval: containerState.interval,
|
timeInterval: containerState.interval,
|
||||||
|
@ -288,7 +268,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
const subject$ = new BehaviorSubject(state);
|
const subject$ = new BehaviorSubject(state);
|
||||||
api.state$ = subject$;
|
api.state$ = subject$;
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
stateContainer.appState.update({ hideChart: true });
|
stateContainer.appState.update({ hideChart: true });
|
||||||
expect(Object.keys(params ?? {})).toEqual(['chartHidden']);
|
expect(Object.keys(params ?? {})).toEqual(['chartHidden']);
|
||||||
|
@ -306,7 +286,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
|
|
||||||
it('should update total hits when the total hits state changes', async () => {
|
it('should update total hits when the total hits state changes', async () => {
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
const containerState = stateContainer.appState.getState();
|
const containerState = stateContainer.appState.getState();
|
||||||
const state = {
|
const state = {
|
||||||
timeInterval: containerState.interval,
|
timeInterval: containerState.interval,
|
||||||
|
@ -325,7 +305,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
result: 100,
|
result: 100,
|
||||||
});
|
});
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
expect(stateContainer.dataState.data$.totalHits$.value).toEqual({
|
expect(stateContainer.dataState.data$.totalHits$.value).toEqual({
|
||||||
fetchStatus: FetchStatus.COMPLETE,
|
fetchStatus: FetchStatus.COMPLETE,
|
||||||
|
@ -349,7 +329,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
|
|
||||||
mockData.query.getState = () => mockQueryState;
|
mockData.query.getState = () => mockQueryState;
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
const containerState = stateContainer.appState.getState();
|
const containerState = stateContainer.appState.getState();
|
||||||
const error = new Error('test');
|
const error = new Error('test');
|
||||||
const state = {
|
const state = {
|
||||||
|
@ -369,7 +349,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
expect(sendErrorTo).toHaveBeenCalledWith(stateContainer.dataState.data$.totalHits$);
|
expect(sendErrorTo).toHaveBeenCalledWith(stateContainer.dataState.data$.totalHits$);
|
||||||
expect(stateContainer.dataState.data$.totalHits$.value).toEqual({
|
expect(stateContainer.dataState.data$.totalHits$.value).toEqual({
|
||||||
|
@ -384,7 +364,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
stateContainer.appState.update({ query: { esql: 'from *' } });
|
stateContainer.appState.update({ query: { esql: 'from *' } });
|
||||||
stateContainer.dataState.fetchChart$ = fetch$;
|
stateContainer.dataState.fetchChart$ = fetch$;
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
act(() => {
|
act(() => {
|
||||||
fetch$.next();
|
fetch$.next();
|
||||||
});
|
});
|
||||||
|
@ -404,7 +384,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
act(() => {
|
act(() => {
|
||||||
fetch$.next();
|
fetch$.next();
|
||||||
});
|
});
|
||||||
|
@ -418,10 +398,10 @@ describe('useDiscoverHistogram', () => {
|
||||||
const savedSearchFetch$ = new Subject<void>();
|
const savedSearchFetch$ = new Subject<void>();
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
stateContainer.dataState.fetchChart$ = savedSearchFetch$;
|
stateContainer.dataState.fetchChart$ = savedSearchFetch$;
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram(stateContainer);
|
||||||
const api = createMockUnifiedHistogramApi();
|
const api = createMockUnifiedHistogramApi();
|
||||||
act(() => {
|
act(() => {
|
||||||
hook.result.current.ref(api);
|
hook.result.current.setUnifiedHistogramApi(api);
|
||||||
});
|
});
|
||||||
expect(api.fetch).not.toHaveBeenCalled();
|
expect(api.fetch).not.toHaveBeenCalled();
|
||||||
act(() => {
|
act(() => {
|
||||||
|
@ -435,7 +415,7 @@ describe('useDiscoverHistogram', () => {
|
||||||
test('should use custom values provided by customization fwk ', async () => {
|
test('should use custom values provided by customization fwk ', async () => {
|
||||||
mockUseCustomizations = true;
|
mockUseCustomizations = true;
|
||||||
const stateContainer = getStateContainer();
|
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.onFilter).toEqual(mockHistogramCustomization.onFilter);
|
||||||
expect(hook.result.current.onBrushEnd).toEqual(mockHistogramCustomization.onBrushEnd);
|
expect(hook.result.current.onBrushEnd).toEqual(mockHistogramCustomization.onBrushEnd);
|
|
@ -10,16 +10,15 @@
|
||||||
import { useQuerySubscriber } from '@kbn/unified-field-list/src/hooks/use_query_subscriber';
|
import { useQuerySubscriber } from '@kbn/unified-field-list/src/hooks/use_query_subscriber';
|
||||||
import type {
|
import type {
|
||||||
UnifiedHistogramApi,
|
UnifiedHistogramApi,
|
||||||
UnifiedHistogramContainerProps,
|
|
||||||
UnifiedHistogramCreationOptions,
|
|
||||||
UnifiedHistogramState,
|
UnifiedHistogramState,
|
||||||
UnifiedHistogramVisContext,
|
UnifiedHistogramVisContext,
|
||||||
} from '@kbn/unified-histogram-plugin/public';
|
UseUnifiedHistogramProps,
|
||||||
|
} from '@kbn/unified-histogram';
|
||||||
import {
|
import {
|
||||||
canImportVisContext,
|
canImportVisContext,
|
||||||
UnifiedHistogramExternalVisContextStatus,
|
UnifiedHistogramExternalVisContextStatus,
|
||||||
UnifiedHistogramFetchStatus,
|
UnifiedHistogramFetchStatus,
|
||||||
} from '@kbn/unified-histogram-plugin/public';
|
} from '@kbn/unified-histogram';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import type { Observable } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
|
@ -43,7 +42,6 @@ import { ESQL_TABLE_TYPE } from '@kbn/data-plugin/common';
|
||||||
import { useDiscoverCustomization } from '../../../../customizations';
|
import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||||
import { FetchStatus } from '../../../types';
|
import { FetchStatus } from '../../../types';
|
||||||
import type { InspectorAdapters } from '../../hooks/use_inspector';
|
|
||||||
import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages';
|
import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages';
|
||||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { addLog } from '../../../../utils/add_log';
|
import { addLog } from '../../../../utils/add_log';
|
||||||
|
@ -65,25 +63,15 @@ import {
|
||||||
const EMPTY_ESQL_COLUMNS: DatatableColumn[] = [];
|
const EMPTY_ESQL_COLUMNS: DatatableColumn[] = [];
|
||||||
const EMPTY_FILTERS: Filter[] = [];
|
const EMPTY_FILTERS: Filter[] = [];
|
||||||
|
|
||||||
export interface UseDiscoverHistogramProps {
|
export const useDiscoverHistogram = (
|
||||||
stateContainer: DiscoverStateContainer;
|
stateContainer: DiscoverStateContainer
|
||||||
inspectorAdapters: InspectorAdapters;
|
): UseUnifiedHistogramProps & { setUnifiedHistogramApi: (api: UnifiedHistogramApi) => void } => {
|
||||||
hideChart: boolean | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useDiscoverHistogram = ({
|
|
||||||
stateContainer,
|
|
||||||
inspectorAdapters,
|
|
||||||
hideChart,
|
|
||||||
}: UseDiscoverHistogramProps): Omit<
|
|
||||||
UnifiedHistogramContainerProps,
|
|
||||||
'container' | 'getCreationOptions'
|
|
||||||
> & {
|
|
||||||
ref: (api: UnifiedHistogramApi | null) => void;
|
|
||||||
getCreationOptions: () => UnifiedHistogramCreationOptions;
|
|
||||||
} => {
|
|
||||||
const services = useDiscoverServices();
|
const services = useDiscoverServices();
|
||||||
const { main$, documents$, totalHits$ } = stateContainer.dataState.data$;
|
const {
|
||||||
|
data$: { main$, documents$, totalHits$ },
|
||||||
|
inspectorAdapters,
|
||||||
|
getAbortController,
|
||||||
|
} = stateContainer.dataState;
|
||||||
const savedSearchState = useSavedSearch();
|
const savedSearchState = useSavedSearch();
|
||||||
const isEsqlMode = useIsEsqlMode();
|
const isEsqlMode = useIsEsqlMode();
|
||||||
|
|
||||||
|
@ -91,53 +79,38 @@ export const useDiscoverHistogram = ({
|
||||||
* API initialization
|
* API initialization
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const [unifiedHistogram, ref] = useState<UnifiedHistogramApi | null>();
|
const [unifiedHistogramApi, setUnifiedHistogramApi] = useState<UnifiedHistogramApi>();
|
||||||
const [isSuggestionLoading, setIsSuggestionLoading] = useState(false);
|
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
|
* Sync Unified Histogram state with Discover state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = createUnifiedHistogramStateObservable(unifiedHistogram?.state$)?.subscribe(
|
const subscription = createUnifiedHistogramStateObservable(
|
||||||
(changes) => {
|
unifiedHistogramApi?.state$
|
||||||
const { lensRequestAdapter, ...stateChanges } = changes;
|
)?.subscribe((changes) => {
|
||||||
const appState = stateContainer.appState.getState();
|
const { lensRequestAdapter, ...stateChanges } = changes;
|
||||||
const oldState = {
|
const appState = stateContainer.appState.getState();
|
||||||
hideChart: appState.hideChart,
|
const oldState = {
|
||||||
interval: appState.interval,
|
hideChart: appState.hideChart,
|
||||||
};
|
interval: appState.interval,
|
||||||
const newState = { ...oldState, ...stateChanges };
|
};
|
||||||
|
const newState = { ...oldState, ...stateChanges };
|
||||||
|
|
||||||
if ('lensRequestAdapter' in changes) {
|
if ('lensRequestAdapter' in changes) {
|
||||||
inspectorAdapters.lensRequests = lensRequestAdapter;
|
inspectorAdapters.lensRequests = lensRequestAdapter;
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEqual(oldState, newState)) {
|
|
||||||
stateContainer.appState.update(newState);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
if (!isEqual(oldState, newState)) {
|
||||||
|
stateContainer.appState.update(newState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
subscription?.unsubscribe();
|
subscription?.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [inspectorAdapters, stateContainer.appState, unifiedHistogram?.state$]);
|
}, [inspectorAdapters, stateContainer.appState, unifiedHistogramApi?.state$]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync URL query params with Unified Histogram
|
* Sync URL query params with Unified Histogram
|
||||||
|
@ -147,11 +120,11 @@ export const useDiscoverHistogram = ({
|
||||||
const subscription = createAppStateObservable(stateContainer.appState.state$).subscribe(
|
const subscription = createAppStateObservable(stateContainer.appState.state$).subscribe(
|
||||||
(changes) => {
|
(changes) => {
|
||||||
if ('timeInterval' in changes && changes.timeInterval) {
|
if ('timeInterval' in changes && changes.timeInterval) {
|
||||||
unifiedHistogram?.setTimeInterval(changes.timeInterval);
|
unifiedHistogramApi?.setTimeInterval(changes.timeInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('chartHidden' in changes && typeof changes.chartHidden === 'boolean') {
|
if ('chartHidden' in changes && typeof changes.chartHidden === 'boolean') {
|
||||||
unifiedHistogram?.setChartHidden(changes.chartHidden);
|
unifiedHistogramApi?.setChartHidden(changes.chartHidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -159,7 +132,7 @@ export const useDiscoverHistogram = ({
|
||||||
return () => {
|
return () => {
|
||||||
subscription?.unsubscribe();
|
subscription?.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [stateContainer.appState.state$, unifiedHistogram]);
|
}, [stateContainer.appState.state$, unifiedHistogramApi]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total hits
|
* Total hits
|
||||||
|
@ -168,7 +141,7 @@ export const useDiscoverHistogram = ({
|
||||||
const setTotalHitsError = useMemo(() => sendErrorTo(totalHits$), [totalHits$]);
|
const setTotalHitsError = useMemo(() => sendErrorTo(totalHits$), [totalHits$]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = createTotalHitsObservable(unifiedHistogram?.state$)?.subscribe(
|
const subscription = createTotalHitsObservable(unifiedHistogramApi?.state$)?.subscribe(
|
||||||
({ status, result }) => {
|
({ status, result }) => {
|
||||||
if (isEsqlMode) {
|
if (isEsqlMode) {
|
||||||
// ignore histogram's total hits updates for ES|QL as Discover manages them during docs fetching
|
// ignore histogram's total hits updates for ES|QL as Discover manages them during docs fetching
|
||||||
|
@ -221,7 +194,7 @@ export const useDiscoverHistogram = ({
|
||||||
totalHits$,
|
totalHits$,
|
||||||
setTotalHitsError,
|
setTotalHitsError,
|
||||||
stateContainer.appState,
|
stateContainer.appState,
|
||||||
unifiedHistogram?.state$,
|
unifiedHistogramApi?.state$,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,7 +255,7 @@ export const useDiscoverHistogram = ({
|
||||||
|
|
||||||
// Handle unified histogram refetching
|
// Handle unified histogram refetching
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!unifiedHistogram) {
|
if (!unifiedHistogramApi) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +270,7 @@ export const useDiscoverHistogram = ({
|
||||||
// a refetch anyway and result in multiple unnecessary fetches.
|
// a refetch anyway and result in multiple unnecessary fetches.
|
||||||
if (isEsqlMode) {
|
if (isEsqlMode) {
|
||||||
fetchChart$ = merge(
|
fetchChart$ = merge(
|
||||||
createCurrentSuggestionObservable(unifiedHistogram.state$).pipe(map(() => 'lens')),
|
createCurrentSuggestionObservable(unifiedHistogramApi.state$).pipe(map(() => 'lens')),
|
||||||
esqlFetchComplete$.pipe(map(() => 'discover'))
|
esqlFetchComplete$.pipe(map(() => 'discover'))
|
||||||
).pipe(debounceTime(50));
|
).pipe(debounceTime(50));
|
||||||
} else {
|
} else {
|
||||||
|
@ -307,13 +280,13 @@ export const useDiscoverHistogram = ({
|
||||||
const subscription = fetchChart$.subscribe((source) => {
|
const subscription = fetchChart$.subscribe((source) => {
|
||||||
if (source === 'discover') addLog('Unified Histogram - Discover refetch');
|
if (source === 'discover') addLog('Unified Histogram - Discover refetch');
|
||||||
if (source === 'lens') addLog('Unified Histogram - Lens suggestion refetch');
|
if (source === 'lens') addLog('Unified Histogram - Lens suggestion refetch');
|
||||||
unifiedHistogram.fetch();
|
unifiedHistogramApi.fetch();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogram]);
|
}, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogramApi]);
|
||||||
|
|
||||||
const dataView = useCurrentDataView();
|
const dataView = useCurrentDataView();
|
||||||
|
|
||||||
|
@ -380,10 +353,12 @@ export const useDiscoverHistogram = ({
|
||||||
[dispatch, setOverriddenVisContextAfterInvalidation, stateContainer.savedSearchState]
|
[dispatch, setOverriddenVisContextAfterInvalidation, stateContainer.savedSearchState]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const chartHidden = useAppStateSelector((state) => state.hideChart);
|
||||||
|
const timeInterval = useAppStateSelector((state) => state.interval);
|
||||||
const breakdownField = useAppStateSelector((state) => state.breakdownField);
|
const breakdownField = useAppStateSelector((state) => state.breakdownField);
|
||||||
|
|
||||||
const onBreakdownFieldChange = useCallback<
|
const onBreakdownFieldChange = useCallback<
|
||||||
NonNullable<UnifiedHistogramContainerProps['onBreakdownFieldChange']>
|
NonNullable<UseUnifiedHistogramProps['onBreakdownFieldChange']>
|
||||||
>(
|
>(
|
||||||
(nextBreakdownField) => {
|
(nextBreakdownField) => {
|
||||||
if (nextBreakdownField !== breakdownField) {
|
if (nextBreakdownField !== breakdownField) {
|
||||||
|
@ -394,9 +369,17 @@ export const useDiscoverHistogram = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ref,
|
setUnifiedHistogramApi,
|
||||||
getCreationOptions,
|
|
||||||
services,
|
services,
|
||||||
|
localStorageKeyPrefix: 'discover',
|
||||||
|
requestAdapter: inspectorAdapters.requests,
|
||||||
|
abortController: getAbortController(),
|
||||||
|
initialState: {
|
||||||
|
chartHidden,
|
||||||
|
timeInterval,
|
||||||
|
totalHitsStatus: UnifiedHistogramFetchStatus.loading,
|
||||||
|
totalHitsResult: undefined,
|
||||||
|
},
|
||||||
dataView: isEsqlMode ? esqlDataView : dataView,
|
dataView: isEsqlMode ? esqlDataView : dataView,
|
||||||
query: isEsqlMode ? esqlQuery : query,
|
query: isEsqlMode ? esqlQuery : query,
|
||||||
filters: filtersMemoized,
|
filters: filtersMemoized,
|
|
@ -36,10 +36,17 @@ import { act } from 'react-dom/test-utils';
|
||||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||||
import {
|
import {
|
||||||
CurrentTabProvider,
|
InternalStateProvider,
|
||||||
RuntimeStateProvider,
|
RuntimeStateProvider,
|
||||||
internalStateActions,
|
internalStateActions,
|
||||||
} from '../../state_management/redux';
|
} 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({
|
function getStateContainer({
|
||||||
savedSearch,
|
savedSearch,
|
||||||
|
@ -155,13 +162,15 @@ const mountComponent = async ({
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<KibanaRenderContextProvider {...services.core}>
|
<KibanaRenderContextProvider {...services.core}>
|
||||||
<KibanaContextProvider services={services}>
|
<KibanaContextProvider services={services}>
|
||||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
<InternalStateProvider store={stateContainer.internalState}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<ChartPortalsRenderer runtimeStateManager={stateContainer.runtimeStateManager}>
|
||||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<DiscoverHistogramLayout {...props} />
|
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||||
</RuntimeStateProvider>
|
<DiscoverHistogramLayout {...props} />
|
||||||
</DiscoverMainProvider>
|
</RuntimeStateProvider>
|
||||||
</CurrentTabProvider>
|
</DiscoverMainProvider>
|
||||||
|
</ChartPortalsRenderer>
|
||||||
|
</InternalStateProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</KibanaRenderContextProvider>
|
</KibanaRenderContextProvider>
|
||||||
);
|
);
|
||||||
|
@ -177,19 +186,19 @@ const mountComponent = async ({
|
||||||
|
|
||||||
describe('Discover histogram layout component', () => {
|
describe('Discover histogram layout component', () => {
|
||||||
describe('render', () => {
|
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 });
|
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();
|
const { component } = await mountComponent();
|
||||||
expect(component.isEmptyRender()).toBe(false);
|
expect(component.exists(UnifiedHistogramChart)).toBe(true);
|
||||||
}, 10000);
|
}, 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 });
|
const { component } = await mountComponent({ isEsqlMode: true });
|
||||||
expect(component.isEmptyRender()).toBe(false);
|
expect(component.exists(UnifiedHistogramChart)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render PanelsToggle', async () => {
|
it('should render PanelsToggle', async () => {
|
||||||
|
|
|
@ -7,67 +7,40 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback } from 'react';
|
import React from 'react';
|
||||||
import { UnifiedHistogramContainer } from '@kbn/unified-histogram-plugin/public';
|
import { UnifiedHistogramLayout } from '@kbn/unified-histogram';
|
||||||
import { css } from '@emotion/react';
|
import { OutPortal } from 'react-reverse-portal';
|
||||||
import { useDiscoverHistogram } from './use_discover_histogram';
|
|
||||||
import { type DiscoverMainContentProps, DiscoverMainContent } from './discover_main_content';
|
import { type DiscoverMainContentProps, DiscoverMainContent } from './discover_main_content';
|
||||||
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
import { useCurrentChartPortalNode, useCurrentTabRuntimeState } from '../../state_management/redux';
|
||||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
|
||||||
|
|
||||||
export interface DiscoverHistogramLayoutProps extends DiscoverMainContentProps {
|
export interface DiscoverHistogramLayoutProps extends DiscoverMainContentProps {
|
||||||
container: HTMLElement | null;
|
container: HTMLElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const histogramLayoutCss = css`
|
|
||||||
height: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const DiscoverHistogramLayout = ({
|
export const DiscoverHistogramLayout = ({
|
||||||
dataView,
|
|
||||||
stateContainer,
|
|
||||||
container,
|
container,
|
||||||
panelsToggle,
|
panelsToggle,
|
||||||
...mainContentProps
|
...mainContentProps
|
||||||
}: DiscoverHistogramLayoutProps) => {
|
}: DiscoverHistogramLayoutProps) => {
|
||||||
const { dataState } = stateContainer;
|
const chartPortalNode = useCurrentChartPortalNode();
|
||||||
const hideChart = useAppStateSelector((state) => state.hideChart);
|
const layoutProps = useCurrentTabRuntimeState(
|
||||||
const isEsqlMode = useIsEsqlMode();
|
mainContentProps.stateContainer.runtimeStateManager,
|
||||||
const unifiedHistogramProps = useDiscoverHistogram({
|
(tab) => tab.unifiedHistogramLayoutProps$
|
||||||
stateContainer,
|
|
||||||
inspectorAdapters: dataState.inspectorAdapters,
|
|
||||||
hideChart,
|
|
||||||
});
|
|
||||||
|
|
||||||
const renderCustomChartToggleActions = useCallback(
|
|
||||||
() =>
|
|
||||||
React.isValidElement(panelsToggle)
|
|
||||||
? React.cloneElement(panelsToggle, { renderedFor: 'histogram' })
|
|
||||||
: panelsToggle,
|
|
||||||
[panelsToggle]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Initialized when the first search has been requested or
|
if (!layoutProps) {
|
||||||
// when in ES|QL mode since search sessions are not supported
|
|
||||||
if (!unifiedHistogramProps.searchSessionId && !isEsqlMode) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<UnifiedHistogramContainer
|
<UnifiedHistogramLayout
|
||||||
{...unifiedHistogramProps}
|
|
||||||
requestAdapter={dataState.inspectorAdapters.requests}
|
|
||||||
container={container}
|
container={container}
|
||||||
css={histogramLayoutCss}
|
unifiedHistogramChart={
|
||||||
renderCustomChartToggleActions={renderCustomChartToggleActions}
|
chartPortalNode ? <OutPortal node={chartPortalNode} panelsToggle={panelsToggle} /> : null
|
||||||
abortController={stateContainer.dataState.getAbortController()}
|
}
|
||||||
|
{...layoutProps}
|
||||||
>
|
>
|
||||||
<DiscoverMainContent
|
<DiscoverMainContent {...mainContentProps} panelsToggle={panelsToggle} />
|
||||||
{...mainContentProps}
|
</UnifiedHistogramLayout>
|
||||||
stateContainer={stateContainer}
|
|
||||||
dataView={dataView}
|
|
||||||
panelsToggle={panelsToggle}
|
|
||||||
/>
|
|
||||||
</UnifiedHistogramContainer>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -40,10 +40,11 @@ import { ErrorCallout } from '../../../../components/common/error_callout';
|
||||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||||
import {
|
import {
|
||||||
CurrentTabProvider,
|
InternalStateProvider,
|
||||||
RuntimeStateProvider,
|
RuntimeStateProvider,
|
||||||
internalStateActions,
|
internalStateActions,
|
||||||
} from '../../state_management/redux';
|
} from '../../state_management/redux';
|
||||||
|
import { ChartPortalsRenderer } from '../chart';
|
||||||
|
|
||||||
jest.mock('@elastic/eui', () => ({
|
jest.mock('@elastic/eui', () => ({
|
||||||
...jest.requireActual('@elastic/eui'),
|
...jest.requireActual('@elastic/eui'),
|
||||||
|
@ -137,15 +138,17 @@ async function mountComponent(
|
||||||
|
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<KibanaContextProvider services={services}>
|
<KibanaContextProvider services={services}>
|
||||||
<CurrentTabProvider currentTabId={stateContainer.getCurrentTab().id}>
|
<InternalStateProvider store={stateContainer.internalState}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<ChartPortalsRenderer runtimeStateManager={stateContainer.runtimeStateManager}>
|
||||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<EuiProvider highContrastMode={false}>
|
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||||
<DiscoverLayout {...props} />
|
<EuiProvider highContrastMode={false}>
|
||||||
</EuiProvider>
|
<DiscoverLayout {...props} />
|
||||||
</RuntimeStateProvider>
|
</EuiProvider>
|
||||||
</DiscoverMainProvider>
|
</RuntimeStateProvider>
|
||||||
</CurrentTabProvider>
|
</DiscoverMainProvider>
|
||||||
|
</ChartPortalsRenderer>
|
||||||
|
</InternalStateProvider>
|
||||||
</KibanaContextProvider>,
|
</KibanaContextProvider>,
|
||||||
mountOptions
|
mountOptions
|
||||||
);
|
);
|
||||||
|
|
|
@ -49,7 +49,6 @@ import type { SidebarToggleState } from '../../../types';
|
||||||
import { FetchStatus } from '../../../types';
|
import { FetchStatus } from '../../../types';
|
||||||
import { useDataState } from '../../hooks/use_data_state';
|
import { useDataState } from '../../hooks/use_data_state';
|
||||||
import { SavedSearchURLConflictCallout } from '../../../../components/saved_search_url_conflict_callout/saved_search_url_conflict_callout';
|
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 { ErrorCallout } from '../../../../components/common/error_callout';
|
||||||
import { addLog } from '../../../../utils/add_log';
|
import { addLog } from '../../../../utils/add_log';
|
||||||
import { DiscoverResizableLayout } from './discover_resizable_layout';
|
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 { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||||
import { useCurrentDataView, useCurrentTabSelector } from '../../state_management/redux';
|
import { useCurrentDataView, useCurrentTabSelector } from '../../state_management/redux';
|
||||||
import { TABS_ENABLED } from '../../../../constants';
|
import { TABS_ENABLED } from '../../../../constants';
|
||||||
|
import { DiscoverHistogramLayout } from './discover_histogram_layout';
|
||||||
|
|
||||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||||
const TopNavMemoized = React.memo(DiscoverTopNav);
|
const TopNavMemoized = React.memo(DiscoverTopNav);
|
||||||
|
|
|
@ -12,7 +12,6 @@ import React, { useState } from 'react';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { DiscoverSessionView, type DiscoverSessionViewProps } from '../session_view';
|
import { DiscoverSessionView, type DiscoverSessionViewProps } from '../session_view';
|
||||||
import {
|
import {
|
||||||
CurrentTabProvider,
|
|
||||||
createTabItem,
|
createTabItem,
|
||||||
internalStateActions,
|
internalStateActions,
|
||||||
selectAllTabs,
|
selectAllTabs,
|
||||||
|
@ -34,17 +33,10 @@ export const TabsView = (props: DiscoverSessionViewProps) => {
|
||||||
<UnifiedTabs
|
<UnifiedTabs
|
||||||
services={services}
|
services={services}
|
||||||
initialItems={initialItems}
|
initialItems={initialItems}
|
||||||
onChanged={(updateState) => {
|
onChanged={(updateState) => dispatch(internalStateActions.updateTabs(updateState))}
|
||||||
const updateTabsAction = internalStateActions.updateTabs(updateState);
|
|
||||||
return dispatch(updateTabsAction);
|
|
||||||
}}
|
|
||||||
createItem={() => createTabItem(allTabs)}
|
createItem={() => createTabItem(allTabs)}
|
||||||
getPreviewData={getPreviewData}
|
getPreviewData={getPreviewData}
|
||||||
renderContent={() => (
|
renderContent={() => <DiscoverSessionView key={currentTabId} {...props} />}
|
||||||
<CurrentTabProvider currentTabId={currentTabId}>
|
|
||||||
<DiscoverSessionView key={currentTabId} {...props} />
|
|
||||||
</CurrentTabProvider>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
createInternalStateStore,
|
createInternalStateStore,
|
||||||
createRuntimeStateManager,
|
createRuntimeStateManager,
|
||||||
internalStateActions,
|
internalStateActions,
|
||||||
CurrentTabProvider,
|
|
||||||
} from './state_management/redux';
|
} from './state_management/redux';
|
||||||
import type { RootProfileState } from '../../context_awareness';
|
import type { RootProfileState } from '../../context_awareness';
|
||||||
import { useRootProfile, useDefaultAdHocDataViews } from '../../context_awareness';
|
import { useRootProfile, useDefaultAdHocDataViews } from '../../context_awareness';
|
||||||
|
@ -35,6 +34,7 @@ import {
|
||||||
import { useAsyncFunction } from './hooks/use_async_function';
|
import { useAsyncFunction } from './hooks/use_async_function';
|
||||||
import { TabsView } from './components/tabs_view';
|
import { TabsView } from './components/tabs_view';
|
||||||
import { TABS_ENABLED } from '../../constants';
|
import { TABS_ENABLED } from '../../constants';
|
||||||
|
import { ChartPortalsRenderer } from './components/chart';
|
||||||
|
|
||||||
export interface MainRouteProps {
|
export interface MainRouteProps {
|
||||||
customizationContext: DiscoverCustomizationContext;
|
customizationContext: DiscoverCustomizationContext;
|
||||||
|
@ -142,13 +142,13 @@ export const DiscoverMainRoute = ({
|
||||||
return (
|
return (
|
||||||
<InternalStateProvider store={internalState}>
|
<InternalStateProvider store={internalState}>
|
||||||
<rootProfileState.AppWrapper>
|
<rootProfileState.AppWrapper>
|
||||||
{TABS_ENABLED ? (
|
<ChartPortalsRenderer runtimeStateManager={sessionViewProps.runtimeStateManager}>
|
||||||
<TabsView {...sessionViewProps} />
|
{TABS_ENABLED ? (
|
||||||
) : (
|
<TabsView {...sessionViewProps} />
|
||||||
<CurrentTabProvider currentTabId={internalState.getState().tabs.unsafeCurrentId}>
|
) : (
|
||||||
<DiscoverSessionView {...sessionViewProps} />
|
<DiscoverSessionView {...sessionViewProps} />
|
||||||
</CurrentTabProvider>
|
)}
|
||||||
)}
|
</ChartPortalsRenderer>
|
||||||
</rootProfileState.AppWrapper>
|
</rootProfileState.AppWrapper>
|
||||||
</InternalStateProvider>
|
</InternalStateProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,8 +15,8 @@ import type { FilterCompareOptions } from '@kbn/es-query';
|
||||||
import { COMPARE_ALL_OPTIONS, isOfAggregateQueryType, updateFilterReferences } from '@kbn/es-query';
|
import { COMPARE_ALL_OPTIONS, isOfAggregateQueryType, updateFilterReferences } from '@kbn/es-query';
|
||||||
import type { SearchSourceFields } from '@kbn/data-plugin/common';
|
import type { SearchSourceFields } from '@kbn/data-plugin/common';
|
||||||
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
import type { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||||
import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram-plugin/public';
|
import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram';
|
||||||
import { canImportVisContext } from '@kbn/unified-histogram-plugin/public';
|
import { canImportVisContext } from '@kbn/unified-histogram';
|
||||||
import type { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public';
|
import type { SavedObjectSaveOpts } from '@kbn/saved-objects-plugin/public';
|
||||||
import { isEqual, isFunction } from 'lodash';
|
import { isEqual, isFunction } from 'lodash';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
|
|
@ -68,12 +68,31 @@ export const initializeSession: InternalStateThunkActionCreator<
|
||||||
dispatch(disconnectTab({ tabId }));
|
dispatch(disconnectTab({ tabId }));
|
||||||
dispatch(internalStateSlice.actions.resetOnSavedSearchChange({ 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
|
* "No data" checks
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const discoverSessionLoadTracker =
|
|
||||||
services.ebtManager.trackPerformanceEvent('discoverLoadSavedSearch');
|
|
||||||
const urlState = cleanupUrlState(
|
const urlState = cleanupUrlState(
|
||||||
defaultUrlState ?? urlStateStorage.get<AppStateUrl>(APP_STATE_URL_KEY),
|
defaultUrlState ?? urlStateStorage.get<AppStateUrl>(APP_STATE_URL_KEY),
|
||||||
services.uiSettings
|
services.uiSettings
|
||||||
|
@ -124,10 +143,6 @@ export const initializeSession: InternalStateThunkActionCreator<
|
||||||
setBreadcrumbs({ services, titleBreadcrumbText: persistedDiscoverSession.title });
|
setBreadcrumbs({ services, titleBreadcrumbText: persistedDiscoverSession.title });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currentDataView$, stateContainer$, customizationService$ } = selectTabRuntimeState(
|
|
||||||
runtimeStateManager,
|
|
||||||
tabId
|
|
||||||
);
|
|
||||||
let dataView: DataView;
|
let dataView: DataView;
|
||||||
|
|
||||||
if (isOfAggregateQueryType(initialQuery)) {
|
if (isOfAggregateQueryType(initialQuery)) {
|
||||||
|
|
|
@ -15,7 +15,8 @@ import {
|
||||||
createDispatchHook,
|
createDispatchHook,
|
||||||
createSelectorHook,
|
createSelectorHook,
|
||||||
} from 'react-redux';
|
} 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 { useAdHocDataViews } from './runtime_state';
|
||||||
import type { DiscoverInternalState, TabState } from './types';
|
import type { DiscoverInternalState, TabState } from './types';
|
||||||
import {
|
import {
|
||||||
|
@ -25,6 +26,7 @@ import {
|
||||||
} from './internal_state';
|
} from './internal_state';
|
||||||
import { selectTab } from './selectors';
|
import { selectTab } from './selectors';
|
||||||
import { type TabActionInjector, createTabActionInjector } from './utils';
|
import { type TabActionInjector, createTabActionInjector } from './utils';
|
||||||
|
import type { ChartPortalNode } from '../../components/chart';
|
||||||
|
|
||||||
const internalStateContext = createContext<ReactReduxContextValue>(
|
const internalStateContext = createContext<ReactReduxContextValue>(
|
||||||
// Recommended approach for versions of Redux prior to v9:
|
// Recommended approach for versions of Redux prior to v9:
|
||||||
|
@ -49,6 +51,7 @@ export const useInternalStateSelector: TypedUseSelectorHook<DiscoverInternalStat
|
||||||
|
|
||||||
interface CurrentTabContextValue {
|
interface CurrentTabContextValue {
|
||||||
currentTabId: string;
|
currentTabId: string;
|
||||||
|
currentChartPortalNode?: ChartPortalNode;
|
||||||
injectCurrentTab: TabActionInjector;
|
injectCurrentTab: TabActionInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +59,16 @@ const currentTabContext = createContext<CurrentTabContextValue | undefined>(unde
|
||||||
|
|
||||||
export const CurrentTabProvider = ({
|
export const CurrentTabProvider = ({
|
||||||
currentTabId,
|
currentTabId,
|
||||||
|
currentChartPortalNode,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<{ currentTabId: string }>) => {
|
}: PropsWithChildren<{ currentTabId: string; currentChartPortalNode?: ChartPortalNode }>) => {
|
||||||
const contextValue = useMemo<CurrentTabContextValue>(
|
const contextValue = useMemo<CurrentTabContextValue>(
|
||||||
() => ({ currentTabId, injectCurrentTab: createTabActionInjector(currentTabId) }),
|
() => ({
|
||||||
[currentTabId]
|
currentTabId,
|
||||||
|
currentChartPortalNode,
|
||||||
|
injectCurrentTab: createTabActionInjector(currentTabId),
|
||||||
|
}),
|
||||||
|
[currentChartPortalNode, currentTabId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <currentTabContext.Provider value={contextValue}>{children}</currentTabContext.Provider>;
|
return <currentTabContext.Provider value={contextValue}>{children}</currentTabContext.Provider>;
|
||||||
|
@ -88,6 +96,8 @@ export const useCurrentTabAction = <TPayload extends TabActionPayload, TReturn>(
|
||||||
return useMemo(() => injectCurrentTab(actionCreator), [actionCreator, injectCurrentTab]);
|
return useMemo(() => injectCurrentTab(actionCreator), [actionCreator, injectCurrentTab]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCurrentChartPortalNode = () => useCurrentTabContext().currentChartPortalNode;
|
||||||
|
|
||||||
export const useDataViewsForPicker = () => {
|
export const useDataViewsForPicker = () => {
|
||||||
const originalAdHocDataViews = useAdHocDataViews();
|
const originalAdHocDataViews = useAdHocDataViews();
|
||||||
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
|
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
|
||||||
|
|
|
@ -52,6 +52,7 @@ export {
|
||||||
CurrentTabProvider,
|
CurrentTabProvider,
|
||||||
useCurrentTabSelector,
|
useCurrentTabSelector,
|
||||||
useCurrentTabAction,
|
useCurrentTabAction,
|
||||||
|
useCurrentChartPortalNode,
|
||||||
useDataViewsForPicker,
|
useDataViewsForPicker,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
|
|
|
@ -193,11 +193,16 @@ export interface InternalStateThunkDependencies {
|
||||||
urlStateStorage: IKbnUrlStateStorage;
|
urlStateStorage: IKbnUrlStateStorage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IS_JEST_ENVIRONMENT = typeof jest !== 'undefined';
|
||||||
|
|
||||||
export const createInternalStateStore = (options: InternalStateThunkDependencies) => {
|
export const createInternalStateStore = (options: InternalStateThunkDependencies) => {
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: internalStateSlice.reducer,
|
reducer: internalStateSlice.reducer,
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({ thunk: { extraArgument: options } }),
|
getDefaultMiddleware({
|
||||||
|
thunk: { extraArgument: options },
|
||||||
|
serializableCheck: !IS_JEST_ENVIRONMENT,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
// TEMPORARY: Create initial default tab
|
// TEMPORARY: Create initial default tab
|
||||||
|
|
|
@ -11,6 +11,7 @@ import type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
import React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react';
|
import React, { type PropsWithChildren, createContext, useContext, useMemo } from 'react';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import type { UnifiedHistogramPartialLayoutProps } from '@kbn/unified-histogram';
|
||||||
import { useCurrentTabContext } from './hooks';
|
import { useCurrentTabContext } from './hooks';
|
||||||
import type { DiscoverStateContainer } from '../discover_state';
|
import type { DiscoverStateContainer } from '../discover_state';
|
||||||
import type { ConnectedCustomizationService } from '../../../../customizations';
|
import type { ConnectedCustomizationService } from '../../../../customizations';
|
||||||
|
@ -22,6 +23,7 @@ interface DiscoverRuntimeState {
|
||||||
interface TabRuntimeState {
|
interface TabRuntimeState {
|
||||||
stateContainer?: DiscoverStateContainer;
|
stateContainer?: DiscoverStateContainer;
|
||||||
customizationService?: ConnectedCustomizationService;
|
customizationService?: ConnectedCustomizationService;
|
||||||
|
unifiedHistogramLayoutProps?: UnifiedHistogramPartialLayoutProps;
|
||||||
currentDataView: DataView;
|
currentDataView: DataView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +47,9 @@ export const createRuntimeStateManager = (): RuntimeStateManager => ({
|
||||||
export const createTabRuntimeState = (): ReactiveTabRuntimeState => ({
|
export const createTabRuntimeState = (): ReactiveTabRuntimeState => ({
|
||||||
stateContainer$: new BehaviorSubject<DiscoverStateContainer | undefined>(undefined),
|
stateContainer$: new BehaviorSubject<DiscoverStateContainer | undefined>(undefined),
|
||||||
customizationService$: new BehaviorSubject<ConnectedCustomizationService | undefined>(undefined),
|
customizationService$: new BehaviorSubject<ConnectedCustomizationService | undefined>(undefined),
|
||||||
|
unifiedHistogramLayoutProps$: new BehaviorSubject<UnifiedHistogramPartialLayoutProps | undefined>(
|
||||||
|
undefined
|
||||||
|
),
|
||||||
currentDataView$: new BehaviorSubject<DataView | undefined>(undefined),
|
currentDataView$: new BehaviorSubject<DataView | undefined>(undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,15 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import type { DiscoverInternalState } from './types';
|
import type { DiscoverInternalState } from './types';
|
||||||
|
|
||||||
export const selectTab = (state: DiscoverInternalState, tabId: string) => state.tabs.byId[tabId];
|
export const selectTab = (state: DiscoverInternalState, tabId: string) => state.tabs.byId[tabId];
|
||||||
|
|
||||||
export const selectAllTabs = (state: DiscoverInternalState) =>
|
export const selectAllTabs = createSelector(
|
||||||
state.tabs.allIds.map((id) => selectTab(state, id));
|
[
|
||||||
|
(state: DiscoverInternalState) => state.tabs.allIds,
|
||||||
|
(state: DiscoverInternalState) => state.tabs.byId,
|
||||||
|
],
|
||||||
|
(allIds, byId) => allIds.map((id) => byId[id])
|
||||||
|
);
|
||||||
|
|
|
@ -11,7 +11,7 @@ import type { RefreshInterval } from '@kbn/data-plugin/common';
|
||||||
import type { DataViewListItem } from '@kbn/data-views-plugin/public';
|
import type { DataViewListItem } from '@kbn/data-views-plugin/public';
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils';
|
import type { DataTableRecord } from '@kbn/discover-utils';
|
||||||
import type { Filter, TimeRange } from '@kbn/es-query';
|
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';
|
import type { TabItem } from '@kbn/unified-tabs';
|
||||||
|
|
||||||
export enum LoadingStatus {
|
export enum LoadingStatus {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue