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