mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Discover] Fix tab preview reactivity (#218368)
## Summary This PR fixes the issue with Discover tabs where `getPreviewData` is not reactive. It's not as straightforward as it probably should be because of our nested state containers, but it should be reliable for now. I experimented with putting the `previewDataMap$` observable directly in `RuntimeStateManager`, but it seemed trickier with more manual syncing involved, so I just put it in a hook for now. Fixes #217120. ### Checklist - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
bcba741abc
commit
de49dec324
2 changed files with 111 additions and 34 deletions
|
@ -7,22 +7,20 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { type TabItem, UnifiedTabs, TabStatus } from '@kbn/unified-tabs';
|
||||
import { type TabItem, UnifiedTabs } from '@kbn/unified-tabs';
|
||||
import React, { useState } from 'react';
|
||||
import { pick } from 'lodash';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import { DiscoverSessionView, type DiscoverSessionViewProps } from '../session_view';
|
||||
import {
|
||||
CurrentTabProvider,
|
||||
createTabItem,
|
||||
internalStateActions,
|
||||
selectAllTabs,
|
||||
selectTabRuntimeState,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
import { FetchStatus } from '../../../types';
|
||||
import { usePreviewData } from './use_preview_data';
|
||||
|
||||
export const TabsView = (props: DiscoverSessionViewProps) => {
|
||||
const services = useDiscoverServices();
|
||||
|
@ -30,6 +28,7 @@ export const TabsView = (props: DiscoverSessionViewProps) => {
|
|||
const allTabs = useInternalStateSelector(selectAllTabs);
|
||||
const currentTabId = useInternalStateSelector((state) => state.tabs.unsafeCurrentId);
|
||||
const [initialItems] = useState<TabItem[]>(() => allTabs.map((tab) => pick(tab, 'id', 'label')));
|
||||
const { getPreviewData } = usePreviewData(props.runtimeStateManager);
|
||||
|
||||
return (
|
||||
<UnifiedTabs
|
||||
|
@ -40,36 +39,7 @@ export const TabsView = (props: DiscoverSessionViewProps) => {
|
|||
return dispatch(updateTabsAction);
|
||||
}}
|
||||
createItem={() => createTabItem(allTabs)}
|
||||
getPreviewData={(item) => {
|
||||
const defaultQuery = { language: 'kuery', query: '(Empty query)' };
|
||||
const stateContainer = selectTabRuntimeState(
|
||||
props.runtimeStateManager,
|
||||
item.id
|
||||
).stateContainer$.getValue();
|
||||
|
||||
if (!stateContainer) {
|
||||
return {
|
||||
query: defaultQuery,
|
||||
status: TabStatus.RUNNING,
|
||||
};
|
||||
}
|
||||
|
||||
const fetchStatus = stateContainer.dataState.data$.main$.getValue().fetchStatus;
|
||||
const query = stateContainer.appState.getState().query;
|
||||
|
||||
return {
|
||||
query: isOfAggregateQueryType(query)
|
||||
? { esql: query.esql.trim() || defaultQuery.query }
|
||||
: query
|
||||
? { ...query, query: query.query.trim() || defaultQuery.query }
|
||||
: defaultQuery,
|
||||
status: [FetchStatus.UNINITIALIZED, FetchStatus.COMPLETE].includes(fetchStatus)
|
||||
? TabStatus.SUCCESS
|
||||
: fetchStatus === FetchStatus.ERROR
|
||||
? TabStatus.ERROR
|
||||
: TabStatus.RUNNING,
|
||||
};
|
||||
}}
|
||||
getPreviewData={getPreviewData}
|
||||
renderContent={() => (
|
||||
<CurrentTabProvider currentTabId={currentTabId}>
|
||||
<DiscoverSessionView key={currentTabId} {...props} />
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", 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 { useCallback, useMemo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import type { Observable } from 'rxjs';
|
||||
import { combineLatest, distinctUntilChanged, map, of, startWith, switchMap } from 'rxjs';
|
||||
import type { TabItem, TabPreviewData } from '@kbn/unified-tabs';
|
||||
import { TabStatus } from '@kbn/unified-tabs';
|
||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isEqual } from 'lodash';
|
||||
import type { RuntimeStateManager } from '../../state_management/redux';
|
||||
import { selectTabRuntimeState, useInternalStateSelector } from '../../state_management/redux';
|
||||
import { FetchStatus } from '../../../types';
|
||||
|
||||
export const usePreviewData = (runtimeStateManager: RuntimeStateManager) => {
|
||||
const allTabIds = useInternalStateSelector((state) => state.tabs.allIds);
|
||||
const previewDataMap$ = useMemo(
|
||||
() =>
|
||||
combineLatest(
|
||||
allTabIds.reduce<Record<string, Observable<TabPreviewData>>>(
|
||||
(acc, tabId) => ({
|
||||
...acc,
|
||||
[tabId]: getPreviewDataObservable(runtimeStateManager, tabId),
|
||||
}),
|
||||
{}
|
||||
)
|
||||
),
|
||||
[allTabIds, runtimeStateManager]
|
||||
);
|
||||
const previewDataMap = useObservable(previewDataMap$);
|
||||
const getPreviewData = useCallback(
|
||||
(item: TabItem) =>
|
||||
previewDataMap?.[item.id] ?? {
|
||||
status: TabStatus.SUCCESS,
|
||||
query: DEFAULT_PREVIEW_QUERY,
|
||||
},
|
||||
[previewDataMap]
|
||||
);
|
||||
|
||||
return { getPreviewData };
|
||||
};
|
||||
|
||||
const getPreviewStatus = (fetchStatus: FetchStatus): TabPreviewData['status'] => {
|
||||
switch (fetchStatus) {
|
||||
case FetchStatus.UNINITIALIZED:
|
||||
case FetchStatus.COMPLETE:
|
||||
return TabStatus.SUCCESS;
|
||||
case FetchStatus.ERROR:
|
||||
return TabStatus.ERROR;
|
||||
default:
|
||||
return TabStatus.RUNNING;
|
||||
}
|
||||
};
|
||||
|
||||
const DEFAULT_PREVIEW_QUERY = {
|
||||
language: 'kuery',
|
||||
query: i18n.translate('discover.tabsView.defaultQuery', { defaultMessage: '(Empty query)' }),
|
||||
};
|
||||
|
||||
const getPreviewQuery = (query: TabPreviewData['query'] | undefined): TabPreviewData['query'] => {
|
||||
if (!query) {
|
||||
return DEFAULT_PREVIEW_QUERY;
|
||||
}
|
||||
|
||||
if (isOfAggregateQueryType(query)) {
|
||||
return {
|
||||
...query,
|
||||
esql: query.esql.trim() || DEFAULT_PREVIEW_QUERY.query,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...query,
|
||||
query: query.query.trim() || DEFAULT_PREVIEW_QUERY.query,
|
||||
};
|
||||
};
|
||||
|
||||
const getPreviewDataObservable = (runtimeStateManager: RuntimeStateManager, tabId: string) =>
|
||||
selectTabRuntimeState(runtimeStateManager, tabId).stateContainer$.pipe(
|
||||
switchMap((tabStateContainer) => {
|
||||
if (!tabStateContainer) {
|
||||
return of({ status: TabStatus.RUNNING, query: DEFAULT_PREVIEW_QUERY });
|
||||
}
|
||||
|
||||
const { appState } = tabStateContainer;
|
||||
|
||||
return combineLatest([
|
||||
tabStateContainer.dataState.data$.main$,
|
||||
appState.state$.pipe(startWith(appState.get())),
|
||||
]).pipe(
|
||||
map(([{ fetchStatus }, { query }]) => ({ fetchStatus, query })),
|
||||
distinctUntilChanged(isEqual),
|
||||
map(({ fetchStatus, query }) => ({
|
||||
status: getPreviewStatus(fetchStatus),
|
||||
query: getPreviewQuery(query),
|
||||
}))
|
||||
);
|
||||
})
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue