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