mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
[Discover] Replace DiscoverInternalStateContainer
with Redux based InternalStateStore
(#208784)
## Summary This PR replaces Discover's current `DiscoverInternalStateContainer` (based on Kibana's custom `ReduxLikeStateContainer`) with an actual Redux store using Redux Toolkit. It's the first step toward migrating all of Discover's state management to Redux as part of the Discover tabs project. Part of #210160. Resolves #213304. ### 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 - [x] [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 - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
989cf1ec34
commit
ccae358d37
68 changed files with 1209 additions and 876 deletions
|
@ -241,9 +241,9 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
||||||
>();
|
>();
|
||||||
const stateStorage = stateContainer.stateStorage;
|
const stateStorage = stateContainer.stateStorage;
|
||||||
const dataView = useObservable(
|
const dataView = useObservable(
|
||||||
stateContainer.internalState.state$,
|
stateContainer.runtimeStateManager.currentDataView$,
|
||||||
stateContainer.internalState.getState()
|
stateContainer.runtimeStateManager.currentDataView$.getValue()
|
||||||
).dataView;
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!controlGroupAPI) {
|
if (!controlGroupAPI) {
|
||||||
|
@ -262,7 +262,6 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
||||||
});
|
});
|
||||||
|
|
||||||
const filterSubscription = controlGroupAPI.filters$.subscribe((newFilters = []) => {
|
const filterSubscription = controlGroupAPI.filters$.subscribe((newFilters = []) => {
|
||||||
stateContainer.internalState.transitions.setCustomFilters(newFilters);
|
|
||||||
stateContainer.actions.fetchData();
|
stateContainer.actions.fetchData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -79,12 +79,16 @@ export const deepMockedFields = shallowMockedFields.map(
|
||||||
) as DataView['fields'];
|
) as DataView['fields'];
|
||||||
|
|
||||||
export const buildDataViewMock = ({
|
export const buildDataViewMock = ({
|
||||||
name,
|
id,
|
||||||
fields: definedFields,
|
title,
|
||||||
|
name = 'data-view-mock',
|
||||||
|
fields: definedFields = [] as unknown as DataView['fields'],
|
||||||
timeFieldName,
|
timeFieldName,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
id?: string;
|
||||||
fields: DataView['fields'];
|
title?: string;
|
||||||
|
name?: string;
|
||||||
|
fields?: DataView['fields'];
|
||||||
timeFieldName?: string;
|
timeFieldName?: string;
|
||||||
}): DataView => {
|
}): DataView => {
|
||||||
const dataViewFields = [...definedFields] as DataView['fields'];
|
const dataViewFields = [...definedFields] as DataView['fields'];
|
||||||
|
@ -105,9 +109,12 @@ export const buildDataViewMock = ({
|
||||||
return new DataViewField(spec);
|
return new DataViewField(spec);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
id = id ?? `${name}-id`;
|
||||||
|
title = title ?? `${name}-title`;
|
||||||
|
|
||||||
const dataView = {
|
const dataView = {
|
||||||
id: `${name}-id`,
|
id,
|
||||||
title: `${name}-title`,
|
title,
|
||||||
name,
|
name,
|
||||||
metaFields: ['_index', '_score'],
|
metaFields: ['_index', '_score'],
|
||||||
fields: dataViewFields,
|
fields: dataViewFields,
|
||||||
|
@ -122,7 +129,7 @@ export const buildDataViewMock = ({
|
||||||
getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })),
|
getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })),
|
||||||
isTimeNanosBased: () => false,
|
isTimeNanosBased: () => false,
|
||||||
isPersisted: () => true,
|
isPersisted: () => true,
|
||||||
toSpec: () => ({}),
|
toSpec: () => ({ id, title, name }),
|
||||||
toMinimalSpec: () => ({}),
|
toMinimalSpec: () => ({}),
|
||||||
getTimeField: () => {
|
getTimeField: () => {
|
||||||
return dataViewFields.find((field) => field.name === timeFieldName);
|
return dataViewFields.find((field) => field.name === timeFieldName);
|
||||||
|
|
|
@ -13,13 +13,19 @@ import { savedSearchMockWithTimeField, savedSearchMock } from './saved_search';
|
||||||
import { discoverServiceMock } from './services';
|
import { discoverServiceMock } from './services';
|
||||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||||
import { mockCustomizationContext } from '../customizations/__mocks__/customization_context';
|
import { mockCustomizationContext } from '../customizations/__mocks__/customization_context';
|
||||||
|
import {
|
||||||
|
RuntimeStateManager,
|
||||||
|
createRuntimeStateManager,
|
||||||
|
} from '../application/main/state_management/redux';
|
||||||
|
|
||||||
export function getDiscoverStateMock({
|
export function getDiscoverStateMock({
|
||||||
isTimeBased = true,
|
isTimeBased = true,
|
||||||
savedSearch,
|
savedSearch,
|
||||||
|
runtimeStateManager,
|
||||||
}: {
|
}: {
|
||||||
isTimeBased?: boolean;
|
isTimeBased?: boolean;
|
||||||
savedSearch?: SavedSearch;
|
savedSearch?: SavedSearch;
|
||||||
|
runtimeStateManager?: RuntimeStateManager;
|
||||||
}) {
|
}) {
|
||||||
const history = createBrowserHistory();
|
const history = createBrowserHistory();
|
||||||
history.push('/');
|
history.push('/');
|
||||||
|
@ -27,6 +33,7 @@ export function getDiscoverStateMock({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
history,
|
history,
|
||||||
customizationContext: mockCustomizationContext,
|
customizationContext: mockCustomizationContext,
|
||||||
|
runtimeStateManager: runtimeStateManager ?? createRuntimeStateManager(),
|
||||||
});
|
});
|
||||||
container.savedSearchState.set(
|
container.savedSearchState.set(
|
||||||
savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock
|
savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { DiscoverCustomization, DiscoverCustomizationProvider } from '../../../.
|
||||||
import { createCustomizationService } from '../../../../customizations/customization_service';
|
import { createCustomizationService } from '../../../../customizations/customization_service';
|
||||||
import { DiscoverGrid } from '../../../../components/discover_grid';
|
import { DiscoverGrid } from '../../../../components/discover_grid';
|
||||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||||
|
import { internalStateActions } from '../../state_management/redux';
|
||||||
|
|
||||||
const customisationService = createCustomizationService();
|
const customisationService = createCustomizationService();
|
||||||
|
|
||||||
|
@ -46,16 +47,18 @@ async function mountComponent(fetchStatus: FetchStatus, hits: EsHitRecord[]) {
|
||||||
stateContainer.appState.update({
|
stateContainer.appState.update({
|
||||||
dataSource: createDataViewDataSource({ dataViewId: dataViewMock.id! }),
|
dataSource: createDataViewDataSource({ dataViewId: dataViewMock.id! }),
|
||||||
});
|
});
|
||||||
stateContainer.internalState.transitions.setDataRequestParams({
|
stateContainer.internalState.dispatch(
|
||||||
timeRangeRelative: {
|
internalStateActions.setDataRequestParams({
|
||||||
from: '2020-05-14T11:05:13.590',
|
timeRangeRelative: {
|
||||||
to: '2020-05-14T11:20:13.590',
|
from: '2020-05-14T11:05:13.590',
|
||||||
},
|
to: '2020-05-14T11:20:13.590',
|
||||||
timeRangeAbsolute: {
|
},
|
||||||
from: '2020-05-14T11:05:13.590',
|
timeRangeAbsolute: {
|
||||||
to: '2020-05-14T11:20:13.590',
|
from: '2020-05-14T11:05:13.590',
|
||||||
},
|
to: '2020-05-14T11:20:13.590',
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
stateContainer.dataState.data$.documents$ = documents$;
|
stateContainer.dataState.data$.documents$ = documents$;
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,6 @@ import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||||
import { useQuerySubscriber } from '@kbn/unified-field-list';
|
import { useQuerySubscriber } from '@kbn/unified-field-list';
|
||||||
import { DiscoverGrid } from '../../../../components/discover_grid';
|
import { DiscoverGrid } from '../../../../components/discover_grid';
|
||||||
import { getDefaultRowsPerPage } from '../../../../../common/constants';
|
import { getDefaultRowsPerPage } from '../../../../../common/constants';
|
||||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
|
||||||
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
||||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||||
import { FetchStatus } from '../../../types';
|
import { FetchStatus } from '../../../types';
|
||||||
|
@ -73,6 +72,11 @@ import {
|
||||||
useAdditionalCellActions,
|
useAdditionalCellActions,
|
||||||
useProfileAccessor,
|
useProfileAccessor,
|
||||||
} from '../../../../context_awareness';
|
} from '../../../../context_awareness';
|
||||||
|
import {
|
||||||
|
internalStateActions,
|
||||||
|
useInternalStateDispatch,
|
||||||
|
useInternalStateSelector,
|
||||||
|
} from '../../state_management/redux';
|
||||||
|
|
||||||
const containerStyles = css`
|
const containerStyles = css`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -108,6 +112,7 @@ function DiscoverDocumentsComponent({
|
||||||
onFieldEdited?: () => void;
|
onFieldEdited?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const services = useDiscoverServices();
|
const services = useDiscoverServices();
|
||||||
|
const dispatch = useInternalStateDispatch();
|
||||||
const documents$ = stateContainer.dataState.data$.documents$;
|
const documents$ = stateContainer.dataState.data$.documents$;
|
||||||
const savedSearch = useSavedSearchInitial();
|
const savedSearch = useSavedSearchInitial();
|
||||||
const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services;
|
const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services;
|
||||||
|
@ -204,9 +209,9 @@ function DiscoverDocumentsComponent({
|
||||||
|
|
||||||
const setExpandedDoc = useCallback(
|
const setExpandedDoc = useCallback(
|
||||||
(doc: DataTableRecord | undefined) => {
|
(doc: DataTableRecord | undefined) => {
|
||||||
stateContainer.internalState.transitions.setExpandedDoc(doc);
|
dispatch(internalStateActions.setExpandedDoc(doc));
|
||||||
},
|
},
|
||||||
[stateContainer]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onResizeDataGrid = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
|
const onResizeDataGrid = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { DiscoverMainProvider } from '../../state_management/discover_state_prov
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||||
|
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||||
|
|
||||||
function getStateContainer(savedSearch?: SavedSearch) {
|
function getStateContainer(savedSearch?: SavedSearch) {
|
||||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true, savedSearch });
|
const stateContainer = getDiscoverStateMock({ isTimeBased: true, savedSearch });
|
||||||
|
@ -46,17 +47,19 @@ function getStateContainer(savedSearch?: SavedSearch) {
|
||||||
|
|
||||||
stateContainer.appState.update(appState);
|
stateContainer.appState.update(appState);
|
||||||
|
|
||||||
stateContainer.internalState.transitions.setDataView(dataView);
|
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||||
stateContainer.internalState.transitions.setDataRequestParams({
|
stateContainer.internalState.dispatch(
|
||||||
timeRangeAbsolute: {
|
internalStateActions.setDataRequestParams({
|
||||||
from: '2020-05-14T11:05:13.590',
|
timeRangeAbsolute: {
|
||||||
to: '2020-05-14T11:20:13.590',
|
from: '2020-05-14T11:05:13.590',
|
||||||
},
|
to: '2020-05-14T11:20:13.590',
|
||||||
timeRangeRelative: {
|
},
|
||||||
from: '2020-05-14T11:05:13.590',
|
timeRangeRelative: {
|
||||||
to: '2020-05-14T11:20:13.590',
|
from: '2020-05-14T11:05:13.590',
|
||||||
},
|
to: '2020-05-14T11:20:13.590',
|
||||||
});
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return stateContainer;
|
return stateContainer;
|
||||||
}
|
}
|
||||||
|
@ -142,7 +145,9 @@ const mountComponent = async ({
|
||||||
<KibanaRenderContextProvider {...services.core}>
|
<KibanaRenderContextProvider {...services.core}>
|
||||||
<KibanaContextProvider services={services}>
|
<KibanaContextProvider services={services}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<DiscoverHistogramLayout {...props} />
|
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||||
|
<DiscoverHistogramLayout {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</KibanaRenderContextProvider>
|
</KibanaRenderContextProvider>
|
||||||
|
|
|
@ -40,6 +40,7 @@ import { act } from 'react-dom/test-utils';
|
||||||
import { ErrorCallout } from '../../../../components/common/error_callout';
|
import { ErrorCallout } from '../../../../components/common/error_callout';
|
||||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||||
|
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||||
|
|
||||||
jest.mock('@elastic/eui', () => ({
|
jest.mock('@elastic/eui', () => ({
|
||||||
...jest.requireActual('@elastic/eui'),
|
...jest.requireActual('@elastic/eui'),
|
||||||
|
@ -104,11 +105,10 @@ async function mountComponent(
|
||||||
interval: 'auto',
|
interval: 'auto',
|
||||||
query,
|
query,
|
||||||
});
|
});
|
||||||
stateContainer.internalState.transitions.setDataView(dataView);
|
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||||
stateContainer.internalState.transitions.setDataRequestParams({
|
stateContainer.internalState.dispatch(
|
||||||
timeRangeAbsolute: time,
|
internalStateActions.setDataRequestParams({ timeRangeAbsolute: time, timeRangeRelative: time })
|
||||||
timeRangeRelative: time,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
dataView,
|
dataView,
|
||||||
|
@ -128,9 +128,11 @@ async function mountComponent(
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<KibanaContextProvider services={services}>
|
<KibanaContextProvider services={services}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<EuiProvider>
|
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||||
<DiscoverLayout {...props} />
|
<EuiProvider>
|
||||||
</EuiProvider>
|
<DiscoverLayout {...props} />
|
||||||
|
</EuiProvider>
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
</KibanaContextProvider>,
|
</KibanaContextProvider>,
|
||||||
mountOptions
|
mountOptions
|
||||||
|
|
|
@ -34,7 +34,6 @@ import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||||
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { VIEW_MODE } from '../../../../../common/constants';
|
import { VIEW_MODE } from '../../../../../common/constants';
|
||||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
|
||||||
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
||||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||||
import { DiscoverNoResults } from '../no_results';
|
import { DiscoverNoResults } from '../no_results';
|
||||||
|
@ -54,6 +53,7 @@ import { DiscoverResizableLayout } from './discover_resizable_layout';
|
||||||
import { PanelsToggle, PanelsToggleProps } from '../../../../components/panels_toggle';
|
import { PanelsToggle, PanelsToggleProps } from '../../../../components/panels_toggle';
|
||||||
import { sendErrorMsg } from '../../hooks/use_saved_search_messages';
|
import { sendErrorMsg } from '../../hooks/use_saved_search_messages';
|
||||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||||
|
import { useCurrentDataView, useInternalStateSelector } from '../../state_management/redux';
|
||||||
|
|
||||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||||
const TopNavMemoized = React.memo(DiscoverTopNav);
|
const TopNavMemoized = React.memo(DiscoverTopNav);
|
||||||
|
@ -89,7 +89,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
||||||
state.grid,
|
state.grid,
|
||||||
]);
|
]);
|
||||||
const isEsqlMode = useIsEsqlMode();
|
const isEsqlMode = useIsEsqlMode();
|
||||||
|
|
||||||
const viewMode: VIEW_MODE = useAppStateSelector((state) => {
|
const viewMode: VIEW_MODE = useAppStateSelector((state) => {
|
||||||
const fieldStatsNotAvailable =
|
const fieldStatsNotAvailable =
|
||||||
!uiSettings.get(SHOW_FIELD_STATISTICS) && !!dataVisualizerService;
|
!uiSettings.get(SHOW_FIELD_STATISTICS) && !!dataVisualizerService;
|
||||||
|
@ -98,15 +97,10 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
||||||
}
|
}
|
||||||
return state.viewMode ?? VIEW_MODE.DOCUMENT_LEVEL;
|
return state.viewMode ?? VIEW_MODE.DOCUMENT_LEVEL;
|
||||||
});
|
});
|
||||||
const [dataView, dataViewLoading] = useInternalStateSelector((state) => [
|
const dataView = useCurrentDataView();
|
||||||
state.dataView!,
|
const dataViewLoading = useInternalStateSelector((state) => state.isDataViewLoading);
|
||||||
state.isDataViewLoading,
|
|
||||||
]);
|
|
||||||
const customFilters = useInternalStateSelector((state) => state.customFilters);
|
|
||||||
|
|
||||||
const dataState: DataMainMsg = useDataState(main$);
|
const dataState: DataMainMsg = useDataState(main$);
|
||||||
const savedSearch = useSavedSearchInitial();
|
const savedSearch = useSavedSearchInitial();
|
||||||
|
|
||||||
const fetchCounter = useRef<number>(0);
|
const fetchCounter = useRef<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -197,21 +191,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
||||||
[filterManager, dataView, dataViews, trackUiMetric, capabilities, ebtManager, fieldsMetadata]
|
[filterManager, dataView, dataViews, trackUiMetric, capabilities, ebtManager, fieldsMetadata]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getOperator = (fieldName: string, values: unknown, operation: '+' | '-') => {
|
|
||||||
if (fieldName === '_exists_') {
|
|
||||||
return 'is_not_null';
|
|
||||||
}
|
|
||||||
if (values == null && operation === '-') {
|
|
||||||
return 'is_not_null';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values == null && operation === '+') {
|
|
||||||
return 'is_null';
|
|
||||||
}
|
|
||||||
|
|
||||||
return operation;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPopulateWhereClause = useCallback<DocViewFilterFn>(
|
const onPopulateWhereClause = useCallback<DocViewFilterFn>(
|
||||||
(field, values, operation) => {
|
(field, values, operation) => {
|
||||||
if (!field || !isOfAggregateQueryType(query)) {
|
if (!field || !isOfAggregateQueryType(query)) {
|
||||||
|
@ -430,7 +409,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
||||||
sidebarToggleState$={sidebarToggleState$}
|
sidebarToggleState$={sidebarToggleState$}
|
||||||
sidebarPanel={
|
sidebarPanel={
|
||||||
<SidebarMemoized
|
<SidebarMemoized
|
||||||
additionalFilters={customFilters}
|
|
||||||
columns={currentColumns}
|
columns={currentColumns}
|
||||||
documents$={stateContainer.dataState.data$.documents$}
|
documents$={stateContainer.dataState.data$.documents$}
|
||||||
onAddBreakdownField={canSetBreakdownField ? onAddBreakdownField : undefined}
|
onAddBreakdownField={canSetBreakdownField ? onAddBreakdownField : undefined}
|
||||||
|
@ -503,3 +481,18 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
||||||
</EuiPage>
|
</EuiPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOperator = (fieldName: string, values: unknown, operation: '+' | '-') => {
|
||||||
|
if (fieldName === '_exists_') {
|
||||||
|
return 'is_not_null';
|
||||||
|
}
|
||||||
|
if (values == null && operation === '-') {
|
||||||
|
return 'is_not_null';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values == null && operation === '+') {
|
||||||
|
return 'is_null';
|
||||||
|
}
|
||||||
|
|
||||||
|
return operation;
|
||||||
|
};
|
||||||
|
|
|
@ -29,6 +29,8 @@ import type { InspectorAdapters } from '../../hooks/use_inspector';
|
||||||
import { UnifiedHistogramCustomization } from '../../../../customizations/customization_types/histogram_customization';
|
import { UnifiedHistogramCustomization } from '../../../../customizations/customization_types/histogram_customization';
|
||||||
import { useDiscoverCustomization } from '../../../../customizations';
|
import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
import { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||||
|
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||||
|
import { dataViewMockWithTimeField } from '@kbn/discover-utils/src/__mocks__';
|
||||||
|
|
||||||
const mockData = dataPluginMock.createStartContract();
|
const mockData = dataPluginMock.createStartContract();
|
||||||
let mockQueryState = {
|
let mockQueryState = {
|
||||||
|
@ -121,7 +123,11 @@ describe('useDiscoverHistogram', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wrapper = ({ children }: React.PropsWithChildren<unknown>) => (
|
const Wrapper = ({ children }: React.PropsWithChildren<unknown>) => (
|
||||||
<DiscoverMainProvider value={stateContainer}>{children as ReactElement}</DiscoverMainProvider>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
|
<RuntimeStateProvider currentDataView={dataViewMockWithTimeField} adHocDataViews={[]}>
|
||||||
|
{children as ReactElement}
|
||||||
|
</RuntimeStateProvider>
|
||||||
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
const hook = renderHook(
|
const hook = renderHook(
|
||||||
|
@ -379,15 +385,17 @@ describe('useDiscoverHistogram', () => {
|
||||||
expect(hook.result.current.isChartLoading).toBe(true);
|
expect(hook.result.current.isChartLoading).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use timerange + timeRangeRelative + query given by the internalState container', async () => {
|
it('should use timerange + timeRangeRelative + query given by the internalState', async () => {
|
||||||
const fetch$ = new Subject<void>();
|
const fetch$ = new Subject<void>();
|
||||||
const stateContainer = getStateContainer();
|
const stateContainer = getStateContainer();
|
||||||
const timeRangeAbs = { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' };
|
const timeRangeAbs = { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' };
|
||||||
const timeRangeRel = { from: 'now-15m', to: 'now' };
|
const timeRangeRel = { from: 'now-15m', to: 'now' };
|
||||||
stateContainer.internalState.transitions.setDataRequestParams({
|
stateContainer.internalState.dispatch(
|
||||||
timeRangeAbsolute: timeRangeAbs,
|
internalStateActions.setDataRequestParams({
|
||||||
timeRangeRelative: timeRangeRel,
|
timeRangeAbsolute: timeRangeAbs,
|
||||||
});
|
timeRangeRelative: timeRangeRel,
|
||||||
|
})
|
||||||
|
);
|
||||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
||||||
act(() => {
|
act(() => {
|
||||||
fetch$.next();
|
fetch$.next();
|
||||||
|
|
|
@ -44,7 +44,6 @@ import type { InspectorAdapters } from '../../hooks/use_inspector';
|
||||||
import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages';
|
import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages';
|
||||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { addLog } from '../../../../utils/add_log';
|
import { addLog } from '../../../../utils/add_log';
|
||||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
|
||||||
import {
|
import {
|
||||||
useAppStateSelector,
|
useAppStateSelector,
|
||||||
type DiscoverAppState,
|
type DiscoverAppState,
|
||||||
|
@ -52,6 +51,12 @@ import {
|
||||||
import { DataDocumentsMsg } from '../../state_management/discover_data_state_container';
|
import { DataDocumentsMsg } from '../../state_management/discover_data_state_container';
|
||||||
import { useSavedSearch } from '../../state_management/discover_state_provider';
|
import { useSavedSearch } from '../../state_management/discover_state_provider';
|
||||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||||
|
import {
|
||||||
|
internalStateActions,
|
||||||
|
useCurrentDataView,
|
||||||
|
useInternalStateDispatch,
|
||||||
|
useInternalStateSelector,
|
||||||
|
} from '../../state_management/redux';
|
||||||
|
|
||||||
const EMPTY_ESQL_COLUMNS: DatatableColumn[] = [];
|
const EMPTY_ESQL_COLUMNS: DatatableColumn[] = [];
|
||||||
const EMPTY_FILTERS: Filter[] = [];
|
const EMPTY_FILTERS: Filter[] = [];
|
||||||
|
@ -220,7 +225,6 @@ export const useDiscoverHistogram = ({
|
||||||
*/
|
*/
|
||||||
const { query, filters } = useQuerySubscriber({ data: services.data });
|
const { query, filters } = useQuerySubscriber({ data: services.data });
|
||||||
const requestParams = useInternalStateSelector((state) => state.dataRequestParams);
|
const requestParams = useInternalStateSelector((state) => state.dataRequestParams);
|
||||||
const customFilters = useInternalStateSelector((state) => state.customFilters);
|
|
||||||
const { timeRangeRelative: relativeTimeRange, timeRangeAbsolute: timeRange } = requestParams;
|
const { timeRangeRelative: relativeTimeRange, timeRangeAbsolute: timeRange } = requestParams;
|
||||||
// When in ES|QL mode, update the data view, query, and
|
// When in ES|QL mode, update the data view, query, and
|
||||||
// columns only when documents are done fetching so the Lens suggestions
|
// columns only when documents are done fetching so the Lens suggestions
|
||||||
|
@ -308,17 +312,18 @@ export const useDiscoverHistogram = ({
|
||||||
};
|
};
|
||||||
}, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogram]);
|
}, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogram]);
|
||||||
|
|
||||||
const dataView = useInternalStateSelector((state) => state.dataView!);
|
const dataView = useCurrentDataView();
|
||||||
|
|
||||||
const histogramCustomization = useDiscoverCustomization('unified_histogram');
|
const histogramCustomization = useDiscoverCustomization('unified_histogram');
|
||||||
|
|
||||||
const filtersMemoized = useMemo(() => {
|
const filtersMemoized = useMemo(() => {
|
||||||
const allFilters = [...(filters ?? []), ...customFilters];
|
const allFilters = [...(filters ?? [])];
|
||||||
return allFilters.length ? allFilters : EMPTY_FILTERS;
|
return allFilters.length ? allFilters : EMPTY_FILTERS;
|
||||||
}, [filters, customFilters]);
|
}, [filters]);
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const timeRangeMemoized = useMemo(() => timeRange, [timeRange?.from, timeRange?.to]);
|
const timeRangeMemoized = useMemo(() => timeRange, [timeRange?.from, timeRange?.to]);
|
||||||
|
const dispatch = useInternalStateDispatch();
|
||||||
|
|
||||||
const onVisContextChanged = useCallback(
|
const onVisContextChanged = useCallback(
|
||||||
(
|
(
|
||||||
|
@ -332,31 +337,25 @@ export const useDiscoverHistogram = ({
|
||||||
stateContainer.savedSearchState.updateVisContext({
|
stateContainer.savedSearchState.updateVisContext({
|
||||||
nextVisContext,
|
nextVisContext,
|
||||||
});
|
});
|
||||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation(
|
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(undefined));
|
||||||
undefined
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case UnifiedHistogramExternalVisContextStatus.automaticallyOverridden:
|
case UnifiedHistogramExternalVisContextStatus.automaticallyOverridden:
|
||||||
// if the visualization was invalidated as incompatible and rebuilt
|
// if the visualization was invalidated as incompatible and rebuilt
|
||||||
// (it will be used later for saving the visualization via Save button)
|
// (it will be used later for saving the visualization via Save button)
|
||||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation(
|
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(nextVisContext));
|
||||||
nextVisContext
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case UnifiedHistogramExternalVisContextStatus.automaticallyCreated:
|
case UnifiedHistogramExternalVisContextStatus.automaticallyCreated:
|
||||||
case UnifiedHistogramExternalVisContextStatus.applied:
|
case UnifiedHistogramExternalVisContextStatus.applied:
|
||||||
// clearing the value in the internal state so we don't use it during saved search saving
|
// clearing the value in the internal state so we don't use it during saved search saving
|
||||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation(
|
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(undefined));
|
||||||
undefined
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case UnifiedHistogramExternalVisContextStatus.unknown:
|
case UnifiedHistogramExternalVisContextStatus.unknown:
|
||||||
// using `{}` to overwrite the value inside the saved search SO during saving
|
// using `{}` to overwrite the value inside the saved search SO during saving
|
||||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation({});
|
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation({}));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[stateContainer]
|
[dispatch, stateContainer.savedSearchState]
|
||||||
);
|
);
|
||||||
|
|
||||||
const breakdownField = useAppStateSelector((state) => state.breakdownField);
|
const breakdownField = useAppStateSelector((state) => state.breakdownField);
|
||||||
|
|
|
@ -25,7 +25,6 @@ import { DataDocuments$ } from '../../state_management/discover_data_state_conta
|
||||||
import { stubLogstashDataView } from '@kbn/data-plugin/common/stubs';
|
import { stubLogstashDataView } from '@kbn/data-plugin/common/stubs';
|
||||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||||
import { DiscoverAppStateProvider } from '../../state_management/discover_app_state_container';
|
|
||||||
import * as ExistingFieldsServiceApi from '@kbn/unified-field-list/src/services/field_existing/load_field_existing';
|
import * as ExistingFieldsServiceApi from '@kbn/unified-field-list/src/services/field_existing/load_field_existing';
|
||||||
import { resetExistingFieldsCache } from '@kbn/unified-field-list/src/hooks/use_existing_fields';
|
import { resetExistingFieldsCache } from '@kbn/unified-field-list/src/hooks/use_existing_fields';
|
||||||
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
||||||
|
@ -34,7 +33,8 @@ import { buildDataTableRecord } from '@kbn/discover-utils';
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||||
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||||
import { FieldListCustomization, SearchBarCustomization } from '../../../../customizations';
|
import { FieldListCustomization, SearchBarCustomization } from '../../../../customizations';
|
||||||
import { InternalStateProvider } from '../../state_management/discover_internal_state_container';
|
import { RuntimeStateProvider } from '../../state_management/redux';
|
||||||
|
import { DiscoverMainProvider } from '../../state_management/discover_state_provider';
|
||||||
|
|
||||||
const mockSearchBarCustomization: SearchBarCustomization = {
|
const mockSearchBarCustomization: SearchBarCustomization = {
|
||||||
id: 'search_bar',
|
id: 'search_bar',
|
||||||
|
@ -193,7 +193,7 @@ async function mountComponent(
|
||||||
services?: DiscoverServices
|
services?: DiscoverServices
|
||||||
): Promise<ReactWrapper<DiscoverSidebarResponsiveProps>> {
|
): Promise<ReactWrapper<DiscoverSidebarResponsiveProps>> {
|
||||||
let comp: ReactWrapper<DiscoverSidebarResponsiveProps>;
|
let comp: ReactWrapper<DiscoverSidebarResponsiveProps>;
|
||||||
const { appState, internalState } = getStateContainer(appStateParams);
|
const stateContainer = getStateContainer(appStateParams);
|
||||||
const mockedServices = services ?? createMockServices();
|
const mockedServices = services ?? createMockServices();
|
||||||
mockedServices.data.dataViews.getIdsWithTitle = jest.fn(async () =>
|
mockedServices.data.dataViews.getIdsWithTitle = jest.fn(async () =>
|
||||||
props.selectedDataView
|
props.selectedDataView
|
||||||
|
@ -203,16 +203,18 @@ async function mountComponent(
|
||||||
mockedServices.data.dataViews.get = jest.fn().mockImplementation(async (id) => {
|
mockedServices.data.dataViews.get = jest.fn().mockImplementation(async (id) => {
|
||||||
return [props.selectedDataView].find((d) => d!.id === id);
|
return [props.selectedDataView].find((d) => d!.id === id);
|
||||||
});
|
});
|
||||||
mockedServices.data.query.getState = jest.fn().mockImplementation(() => appState.getState());
|
mockedServices.data.query.getState = jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => stateContainer.appState.getState());
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
comp = mountWithIntl(
|
comp = mountWithIntl(
|
||||||
<KibanaContextProvider services={mockedServices}>
|
<KibanaContextProvider services={mockedServices}>
|
||||||
<DiscoverAppStateProvider value={appState}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<InternalStateProvider value={internalState}>
|
<RuntimeStateProvider currentDataView={props.selectedDataView!} adHocDataViews={[]}>
|
||||||
<DiscoverSidebarResponsive {...props} />
|
<DiscoverSidebarResponsive {...props} />{' '}
|
||||||
</InternalStateProvider>
|
</RuntimeStateProvider>
|
||||||
</DiscoverAppStateProvider>
|
</DiscoverMainProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
);
|
);
|
||||||
// wait for lazy modules
|
// wait for lazy modules
|
||||||
|
|
|
@ -37,10 +37,7 @@ import {
|
||||||
import { useDiscoverCustomization } from '../../../../customizations';
|
import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import { useAdditionalFieldGroups } from '../../hooks/sidebar/use_additional_field_groups';
|
import { useAdditionalFieldGroups } from '../../hooks/sidebar/use_additional_field_groups';
|
||||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||||
import {
|
import { useDataViewsForPicker } from '../../state_management/redux';
|
||||||
selectDataViewsForPicker,
|
|
||||||
useInternalStateSelector,
|
|
||||||
} from '../../state_management/discover_internal_state_container';
|
|
||||||
|
|
||||||
const EMPTY_FIELD_COUNTS = {};
|
const EMPTY_FIELD_COUNTS = {};
|
||||||
|
|
||||||
|
@ -176,8 +173,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
|
||||||
);
|
);
|
||||||
const selectedDataViewRef = useRef<DataView | null | undefined>(selectedDataView);
|
const selectedDataViewRef = useRef<DataView | null | undefined>(selectedDataView);
|
||||||
const showFieldList = sidebarState.status !== DiscoverSidebarReducerStatus.INITIAL;
|
const showFieldList = sidebarState.status !== DiscoverSidebarReducerStatus.INITIAL;
|
||||||
const { savedDataViews, managedDataViews, adHocDataViews } =
|
const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker();
|
||||||
useInternalStateSelector(selectDataViewsForPicker);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = props.documents$.subscribe((documentState) => {
|
const subscription = props.documents$.subscribe((documentState) => {
|
||||||
|
|
|
@ -19,6 +19,7 @@ import type { SearchBarCustomization, TopNavCustomization } from '../../../../cu
|
||||||
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||||
import { useDiscoverCustomization } from '../../../../customizations';
|
import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||||
|
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||||
|
|
||||||
jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
||||||
...jest.requireActual('@kbn/kibana-react-plugin/public'),
|
...jest.requireActual('@kbn/kibana-react-plugin/public'),
|
||||||
|
@ -69,7 +70,7 @@ function getProps(
|
||||||
mockDiscoverService.capabilities = capabilities as typeof mockDiscoverService.capabilities;
|
mockDiscoverService.capabilities = capabilities as typeof mockDiscoverService.capabilities;
|
||||||
}
|
}
|
||||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||||
stateContainer.internalState.transitions.setDataView(dataViewMock);
|
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataViewMock));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
stateContainer,
|
stateContainer,
|
||||||
|
@ -110,7 +111,9 @@ describe('Discover topnav component', () => {
|
||||||
const props = getProps({ capabilities: { discover_v2: { save: true } } });
|
const props = getProps({ capabilities: { discover_v2: { save: true } } });
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<DiscoverMainProvider value={props.stateContainer}>
|
<DiscoverMainProvider value={props.stateContainer}>
|
||||||
<DiscoverTopNav {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverTopNav {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
const topNavMenu = component.find(TopNavMenu);
|
const topNavMenu = component.find(TopNavMenu);
|
||||||
|
@ -122,7 +125,9 @@ describe('Discover topnav component', () => {
|
||||||
const props = getProps({ capabilities: { discover_v2: { save: false } } });
|
const props = getProps({ capabilities: { discover_v2: { save: false } } });
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<DiscoverMainProvider value={props.stateContainer}>
|
<DiscoverMainProvider value={props.stateContainer}>
|
||||||
<DiscoverTopNav {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverTopNav {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
const topNavMenu = component.find(TopNavMenu).props();
|
const topNavMenu = component.find(TopNavMenu).props();
|
||||||
|
@ -144,7 +149,9 @@ describe('Discover topnav component', () => {
|
||||||
const props = getProps();
|
const props = getProps();
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<DiscoverMainProvider value={props.stateContainer}>
|
<DiscoverMainProvider value={props.stateContainer}>
|
||||||
<DiscoverTopNav {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverTopNav {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
const topNavMenu = component.find(TopNavMenu);
|
const topNavMenu = component.find(TopNavMenu);
|
||||||
|
@ -164,7 +171,9 @@ describe('Discover topnav component', () => {
|
||||||
const props = getProps();
|
const props = getProps();
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<DiscoverMainProvider value={props.stateContainer}>
|
<DiscoverMainProvider value={props.stateContainer}>
|
||||||
<DiscoverTopNav {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverTopNav {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -176,7 +185,9 @@ describe('Discover topnav component', () => {
|
||||||
const props = getProps();
|
const props = getProps();
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<DiscoverMainProvider value={props.stateContainer}>
|
<DiscoverMainProvider value={props.stateContainer}>
|
||||||
<DiscoverTopNav {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverTopNav {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
const topNav = component.find(mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu);
|
const topNav = component.find(mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu);
|
||||||
|
@ -197,7 +208,9 @@ describe('Discover topnav component', () => {
|
||||||
const props = getProps();
|
const props = getProps();
|
||||||
const component = mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<DiscoverMainProvider value={props.stateContainer}>
|
<DiscoverMainProvider value={props.stateContainer}>
|
||||||
<DiscoverTopNav {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverTopNav {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,6 @@ import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
||||||
import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
||||||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||||
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
|
import { ESQL_TRANSITION_MODAL_KEY } from '../../../../../common/constants';
|
||||||
import {
|
|
||||||
selectDataViewsForPicker,
|
|
||||||
useInternalStateSelector,
|
|
||||||
} from '../../state_management/discover_internal_state_container';
|
|
||||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { onSaveSearch } from './on_save_search';
|
import { onSaveSearch } from './on_save_search';
|
||||||
|
@ -26,6 +22,13 @@ import { useDiscoverTopNav } from './use_discover_topnav';
|
||||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||||
import { ESQLToDataViewTransitionModal } from './esql_dataview_transition';
|
import { ESQLToDataViewTransitionModal } from './esql_dataview_transition';
|
||||||
import './top_nav.scss';
|
import './top_nav.scss';
|
||||||
|
import {
|
||||||
|
internalStateActions,
|
||||||
|
useCurrentDataView,
|
||||||
|
useDataViewsForPicker,
|
||||||
|
useInternalStateDispatch,
|
||||||
|
useInternalStateSelector,
|
||||||
|
} from '../../state_management/redux';
|
||||||
|
|
||||||
export interface DiscoverTopNavProps {
|
export interface DiscoverTopNavProps {
|
||||||
savedQuery?: string;
|
savedQuery?: string;
|
||||||
|
@ -46,12 +49,12 @@ export const DiscoverTopNav = ({
|
||||||
isLoading,
|
isLoading,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
}: DiscoverTopNavProps) => {
|
}: DiscoverTopNavProps) => {
|
||||||
|
const dispatch = useInternalStateDispatch();
|
||||||
const services = useDiscoverServices();
|
const services = useDiscoverServices();
|
||||||
const { dataViewEditor, navigation, dataViewFieldEditor, data, setHeaderActionMenu } = services;
|
const { dataViewEditor, navigation, dataViewFieldEditor, data, setHeaderActionMenu } = services;
|
||||||
const query = useAppStateSelector((state) => state.query);
|
const query = useAppStateSelector((state) => state.query);
|
||||||
const { savedDataViews, managedDataViews, adHocDataViews } =
|
const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker();
|
||||||
useInternalStateSelector(selectDataViewsForPicker);
|
const dataView = useCurrentDataView();
|
||||||
const dataView = useInternalStateSelector((state) => state.dataView!);
|
|
||||||
const isESQLToDataViewTransitionModalVisible = useInternalStateSelector(
|
const isESQLToDataViewTransitionModalVisible = useInternalStateSelector(
|
||||||
(state) => state.isESQLToDataViewTransitionModalVisible
|
(state) => state.isESQLToDataViewTransitionModalVisible
|
||||||
);
|
);
|
||||||
|
@ -134,7 +137,7 @@ export const DiscoverTopNav = ({
|
||||||
if (shouldDismissModal) {
|
if (shouldDismissModal) {
|
||||||
services.storage.set(ESQL_TRANSITION_MODAL_KEY, true);
|
services.storage.set(ESQL_TRANSITION_MODAL_KEY, true);
|
||||||
}
|
}
|
||||||
stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(false);
|
dispatch(internalStateActions.setIsESQLToDataViewTransitionModalVisible(false));
|
||||||
// the user dismissed the modal, we don't need to save the search or switch to the data view mode
|
// the user dismissed the modal, we don't need to save the search or switch to the data view mode
|
||||||
if (needsSave == null) {
|
if (needsSave == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -145,9 +148,7 @@ export const DiscoverTopNav = ({
|
||||||
services,
|
services,
|
||||||
state: stateContainer,
|
state: stateContainer,
|
||||||
onClose: () =>
|
onClose: () =>
|
||||||
stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(
|
dispatch(internalStateActions.setIsESQLToDataViewTransitionModalVisible(false)),
|
||||||
false
|
|
||||||
),
|
|
||||||
onSaveCb: () => {
|
onSaveCb: () => {
|
||||||
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||||
},
|
},
|
||||||
|
@ -156,7 +157,7 @@ export const DiscoverTopNav = ({
|
||||||
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dataView.id, services, stateContainer]
|
[dataView.id, dispatch, services, stateContainer]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer });
|
const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer });
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { discoverServiceMock } from '../../../../__mocks__/services';
|
||||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||||
import { createBrowserHistory } from 'history';
|
import { createBrowserHistory } from 'history';
|
||||||
import { mockCustomizationContext } from '../../../../customizations/__mocks__/customization_context';
|
import { mockCustomizationContext } from '../../../../customizations/__mocks__/customization_context';
|
||||||
|
import { createRuntimeStateManager } from '../../state_management/redux';
|
||||||
|
|
||||||
function getStateContainer({ dataView }: { dataView?: DataView } = {}) {
|
function getStateContainer({ dataView }: { dataView?: DataView } = {}) {
|
||||||
const savedSearch = savedSearchMock;
|
const savedSearch = savedSearchMock;
|
||||||
|
@ -28,13 +29,14 @@ function getStateContainer({ dataView }: { dataView?: DataView } = {}) {
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
history,
|
history,
|
||||||
customizationContext: mockCustomizationContext,
|
customizationContext: mockCustomizationContext,
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
});
|
});
|
||||||
stateContainer.savedSearchState.set(savedSearch);
|
stateContainer.savedSearchState.set(savedSearch);
|
||||||
stateContainer.appState.getState = jest.fn(() => ({
|
stateContainer.appState.getState = jest.fn(() => ({
|
||||||
rowsPerPage: 250,
|
rowsPerPage: 250,
|
||||||
}));
|
}));
|
||||||
if (dataView) {
|
if (dataView) {
|
||||||
stateContainer.internalState.transitions.setDataView(dataView);
|
stateContainer.actions.setDataView(dataView);
|
||||||
}
|
}
|
||||||
return stateContainer;
|
return stateContainer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { SavedSearch, SaveSavedSearchOptions } from '@kbn/saved-search-plugin/pu
|
||||||
import { DiscoverServices } from '../../../../build_services';
|
import { DiscoverServices } from '../../../../build_services';
|
||||||
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { getAllowedSampleSize } from '../../../../utils/get_allowed_sample_size';
|
import { getAllowedSampleSize } from '../../../../utils/get_allowed_sample_size';
|
||||||
|
import { internalStateActions } from '../../state_management/redux';
|
||||||
|
|
||||||
async function saveDataSource({
|
async function saveDataSource({
|
||||||
savedSearch,
|
savedSearch,
|
||||||
|
@ -92,7 +93,7 @@ export async function onSaveSearch({
|
||||||
onSaveCb?: () => void;
|
onSaveCb?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { uiSettings, savedObjectsTagging } = services;
|
const { uiSettings, savedObjectsTagging } = services;
|
||||||
const dataView = state.internalState.getState().dataView;
|
const dataView = savedSearch.searchSource.getField('index');
|
||||||
const overriddenVisContextAfterInvalidation =
|
const overriddenVisContextAfterInvalidation =
|
||||||
state.internalState.getState().overriddenVisContextAfterInvalidation;
|
state.internalState.getState().overriddenVisContextAfterInvalidation;
|
||||||
|
|
||||||
|
@ -174,7 +175,7 @@ export async function onSaveSearch({
|
||||||
savedSearch.tags = currentTags;
|
savedSearch.tags = currentTags;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.internalState.transitions.resetOnSavedSearchChange();
|
state.internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||||
state.appState.resetInitialState();
|
state.appState.resetInitialState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { useDiscoverCustomization } from '../../../../customizations';
|
||||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||||
import { useInspector } from '../../hooks/use_inspector';
|
import { useInspector } from '../../hooks/use_inspector';
|
||||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
|
||||||
import {
|
import {
|
||||||
useSavedSearch,
|
useSavedSearch,
|
||||||
useSavedSearchHasChanged,
|
useSavedSearchHasChanged,
|
||||||
|
@ -21,6 +20,7 @@ import {
|
||||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||||
import { getTopNavBadges } from './get_top_nav_badges';
|
import { getTopNavBadges } from './get_top_nav_badges';
|
||||||
import { useTopNavLinks } from './use_top_nav_links';
|
import { useTopNavLinks } from './use_top_nav_links';
|
||||||
|
import { useAdHocDataViews, useCurrentDataView } from '../../state_management/redux';
|
||||||
|
|
||||||
export const useDiscoverTopNav = ({
|
export const useDiscoverTopNav = ({
|
||||||
stateContainer,
|
stateContainer,
|
||||||
|
@ -47,8 +47,8 @@ export const useDiscoverTopNav = ({
|
||||||
const savedSearchId = useSavedSearch().id;
|
const savedSearchId = useSavedSearch().id;
|
||||||
const savedSearchHasChanged = useSavedSearchHasChanged();
|
const savedSearchHasChanged = useSavedSearchHasChanged();
|
||||||
const shouldShowESQLToDataViewTransitionModal = !savedSearchId || savedSearchHasChanged;
|
const shouldShowESQLToDataViewTransitionModal = !savedSearchId || savedSearchHasChanged;
|
||||||
const dataView = useInternalStateSelector((state) => state.dataView);
|
const dataView = useCurrentDataView();
|
||||||
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
|
const adHocDataViews = useAdHocDataViews();
|
||||||
const isEsqlMode = useIsEsqlMode();
|
const isEsqlMode = useIsEsqlMode();
|
||||||
const onOpenInspector = useInspector({
|
const onOpenInspector = useInspector({
|
||||||
inspector: services.inspector,
|
inspector: services.inspector,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { useTopNavLinks } from './use_top_nav_links';
|
||||||
import { DiscoverServices } from '../../../../build_services';
|
import { DiscoverServices } from '../../../../build_services';
|
||||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||||
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
||||||
|
import { DiscoverMainProvider } from '../../state_management/discover_state_provider';
|
||||||
|
|
||||||
describe('useTopNavLinks', () => {
|
describe('useTopNavLinks', () => {
|
||||||
const services = {
|
const services = {
|
||||||
|
@ -33,7 +34,11 @@ describe('useTopNavLinks', () => {
|
||||||
state.actions.setDataView(dataViewMock);
|
state.actions.setDataView(dataViewMock);
|
||||||
|
|
||||||
const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
const Wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
return <KibanaContextProvider services={services}>{children}</KibanaContextProvider>;
|
return (
|
||||||
|
<KibanaContextProvider services={services}>
|
||||||
|
<DiscoverMainProvider value={state}>{children}</DiscoverMainProvider>
|
||||||
|
</KibanaContextProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
test('useTopNavLinks result', () => {
|
test('useTopNavLinks result', () => {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
} from './app_menu_actions';
|
} from './app_menu_actions';
|
||||||
import type { TopNavCustomization } from '../../../../customizations';
|
import type { TopNavCustomization } from '../../../../customizations';
|
||||||
import { useProfileAccessor } from '../../../../context_awareness';
|
import { useProfileAccessor } from '../../../../context_awareness';
|
||||||
|
import { internalStateActions, useInternalStateDispatch } from '../../state_management/redux';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to build the top nav links
|
* Helper function to build the top nav links
|
||||||
|
@ -52,6 +53,7 @@ export const useTopNavLinks = ({
|
||||||
topNavCustomization: TopNavCustomization | undefined;
|
topNavCustomization: TopNavCustomization | undefined;
|
||||||
shouldShowESQLToDataViewTransitionModal: boolean;
|
shouldShowESQLToDataViewTransitionModal: boolean;
|
||||||
}): TopNavMenuData[] => {
|
}): TopNavMenuData[] => {
|
||||||
|
const dispatch = useInternalStateDispatch();
|
||||||
const [newSearchUrl, setNewSearchUrl] = useState<string | undefined>(undefined);
|
const [newSearchUrl, setNewSearchUrl] = useState<string | undefined>(undefined);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
@ -68,10 +70,10 @@ export const useTopNavLinks = ({
|
||||||
adHocDataViews,
|
adHocDataViews,
|
||||||
onUpdateAdHocDataViews: async (adHocDataViewList) => {
|
onUpdateAdHocDataViews: async (adHocDataViewList) => {
|
||||||
await state.actions.loadDataViewList();
|
await state.actions.loadDataViewList();
|
||||||
state.internalState.transitions.setAdHocDataViews(adHocDataViewList);
|
dispatch(internalStateActions.setAdHocDataViews(adHocDataViewList));
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[isEsqlMode, dataView, adHocDataViews, state]
|
[isEsqlMode, dataView, adHocDataViews, state.actions, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultMenu = topNavCustomization?.defaultMenu;
|
const defaultMenu = topNavCustomization?.defaultMenu;
|
||||||
|
@ -180,7 +182,7 @@ export const useTopNavLinks = ({
|
||||||
shouldShowESQLToDataViewTransitionModal &&
|
shouldShowESQLToDataViewTransitionModal &&
|
||||||
!services.storage.get(ESQL_TRANSITION_MODAL_KEY)
|
!services.storage.get(ESQL_TRANSITION_MODAL_KEY)
|
||||||
) {
|
) {
|
||||||
state.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(true);
|
dispatch(internalStateActions.setIsESQLToDataViewTransitionModalVisible(true));
|
||||||
} else {
|
} else {
|
||||||
state.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
state.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||||
}
|
}
|
||||||
|
@ -223,12 +225,13 @@ export const useTopNavLinks = ({
|
||||||
|
|
||||||
return entries;
|
return entries;
|
||||||
}, [
|
}, [
|
||||||
services,
|
|
||||||
appMenuRegistry,
|
appMenuRegistry,
|
||||||
state,
|
services,
|
||||||
dataView,
|
defaultMenu?.saveItem?.disabled,
|
||||||
isEsqlMode,
|
isEsqlMode,
|
||||||
|
dataView,
|
||||||
shouldShowESQLToDataViewTransitionModal,
|
shouldShowESQLToDataViewTransitionModal,
|
||||||
defaultMenu,
|
dispatch,
|
||||||
|
state,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { fetchEsql } from './fetch_esql';
|
||||||
import { buildDataTableRecord } from '@kbn/discover-utils';
|
import { buildDataTableRecord } from '@kbn/discover-utils';
|
||||||
import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__';
|
import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__';
|
||||||
import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings';
|
import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings';
|
||||||
|
import { createInternalStateStore, createRuntimeStateManager } from '../state_management/redux';
|
||||||
|
|
||||||
jest.mock('./fetch_documents', () => ({
|
jest.mock('./fetch_documents', () => ({
|
||||||
fetchDocuments: jest.fn().mockResolvedValue([]),
|
fetchDocuments: jest.fn().mockResolvedValue([]),
|
||||||
|
@ -67,22 +68,9 @@ describe('test fetchAll', () => {
|
||||||
abortController: new AbortController(),
|
abortController: new AbortController(),
|
||||||
inspectorAdapters: { requests: new RequestAdapter() },
|
inspectorAdapters: { requests: new RequestAdapter() },
|
||||||
getAppState: () => ({}),
|
getAppState: () => ({}),
|
||||||
getInternalState: () => ({
|
internalState: createInternalStateStore({
|
||||||
dataView: undefined,
|
services: discoverServiceMock,
|
||||||
isDataViewLoading: false,
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
savedDataViews: [],
|
|
||||||
adHocDataViews: [],
|
|
||||||
defaultProfileAdHocDataViewIds: [],
|
|
||||||
expandedDoc: undefined,
|
|
||||||
customFilters: [],
|
|
||||||
overriddenVisContextAfterInvalidation: undefined,
|
|
||||||
resetDefaultProfileState: {
|
|
||||||
resetId: 'test',
|
|
||||||
columns: false,
|
|
||||||
rowHeight: false,
|
|
||||||
breakdownField: false,
|
|
||||||
},
|
|
||||||
dataRequestParams: {},
|
|
||||||
}),
|
}),
|
||||||
searchSessionId: '123',
|
searchSessionId: '123',
|
||||||
initialFetchStatus: FetchStatus.UNINITIALIZED,
|
initialFetchStatus: FetchStatus.UNINITIALIZED,
|
||||||
|
@ -261,22 +249,9 @@ describe('test fetchAll', () => {
|
||||||
savedSearch: savedSearchMock,
|
savedSearch: savedSearchMock,
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
getAppState: () => ({ query }),
|
getAppState: () => ({ query }),
|
||||||
getInternalState: () => ({
|
internalState: createInternalStateStore({
|
||||||
dataView: undefined,
|
services: discoverServiceMock,
|
||||||
isDataViewLoading: false,
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
savedDataViews: [],
|
|
||||||
adHocDataViews: [],
|
|
||||||
defaultProfileAdHocDataViewIds: [],
|
|
||||||
expandedDoc: undefined,
|
|
||||||
customFilters: [],
|
|
||||||
overriddenVisContextAfterInvalidation: undefined,
|
|
||||||
resetDefaultProfileState: {
|
|
||||||
resetId: 'test',
|
|
||||||
columns: false,
|
|
||||||
rowHeight: false,
|
|
||||||
breakdownField: false,
|
|
||||||
},
|
|
||||||
dataRequestParams: {},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
fetchAll(subjects, false, deps);
|
fetchAll(subjects, false, deps);
|
||||||
|
@ -386,22 +361,9 @@ describe('test fetchAll', () => {
|
||||||
savedSearch: savedSearchMock,
|
savedSearch: savedSearchMock,
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
getAppState: () => ({ query }),
|
getAppState: () => ({ query }),
|
||||||
getInternalState: () => ({
|
internalState: createInternalStateStore({
|
||||||
dataView: undefined,
|
services: discoverServiceMock,
|
||||||
isDataViewLoading: false,
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
savedDataViews: [],
|
|
||||||
adHocDataViews: [],
|
|
||||||
defaultProfileAdHocDataViewIds: [],
|
|
||||||
expandedDoc: undefined,
|
|
||||||
customFilters: [],
|
|
||||||
overriddenVisContextAfterInvalidation: undefined,
|
|
||||||
resetDefaultProfileState: {
|
|
||||||
resetId: 'test',
|
|
||||||
columns: false,
|
|
||||||
rowHeight: false,
|
|
||||||
breakdownField: false,
|
|
||||||
},
|
|
||||||
dataRequestParams: {},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
fetchAll(subjects, false, deps);
|
fetchAll(subjects, false, deps);
|
||||||
|
|
|
@ -42,12 +42,12 @@ import {
|
||||||
} from '../state_management/discover_data_state_container';
|
} from '../state_management/discover_data_state_container';
|
||||||
import { DiscoverServices } from '../../../build_services';
|
import { DiscoverServices } from '../../../build_services';
|
||||||
import { fetchEsql } from './fetch_esql';
|
import { fetchEsql } from './fetch_esql';
|
||||||
import { InternalState } from '../state_management/discover_internal_state_container';
|
import { InternalStateStore } from '../state_management/redux';
|
||||||
|
|
||||||
export interface FetchDeps {
|
export interface FetchDeps {
|
||||||
abortController: AbortController;
|
abortController: AbortController;
|
||||||
getAppState: () => DiscoverAppState;
|
getAppState: () => DiscoverAppState;
|
||||||
getInternalState: () => InternalState;
|
internalState: InternalStateStore;
|
||||||
initialFetchStatus: FetchStatus;
|
initialFetchStatus: FetchStatus;
|
||||||
inspectorAdapters: Adapters;
|
inspectorAdapters: Adapters;
|
||||||
savedSearch: SavedSearch;
|
savedSearch: SavedSearch;
|
||||||
|
@ -71,7 +71,7 @@ export function fetchAll(
|
||||||
const {
|
const {
|
||||||
initialFetchStatus,
|
initialFetchStatus,
|
||||||
getAppState,
|
getAppState,
|
||||||
getInternalState,
|
internalState,
|
||||||
services,
|
services,
|
||||||
inspectorAdapters,
|
inspectorAdapters,
|
||||||
savedSearch,
|
savedSearch,
|
||||||
|
@ -96,8 +96,7 @@ export function fetchAll(
|
||||||
dataView,
|
dataView,
|
||||||
services,
|
services,
|
||||||
sort: getAppState().sort as SortOrder[],
|
sort: getAppState().sort as SortOrder[],
|
||||||
customFilters: getInternalState().customFilters,
|
inputTimeRange: internalState.getState().dataRequestParams.timeRangeAbsolute,
|
||||||
inputTimeRange: getInternalState().dataRequestParams.timeRangeAbsolute,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +117,7 @@ export function fetchAll(
|
||||||
data,
|
data,
|
||||||
expressions,
|
expressions,
|
||||||
profilesManager,
|
profilesManager,
|
||||||
timeRange: getInternalState().dataRequestParams.timeRangeAbsolute,
|
timeRange: internalState.getState().dataRequestParams.timeRangeAbsolute,
|
||||||
})
|
})
|
||||||
: fetchDocuments(searchSource, fetchDeps);
|
: fetchDocuments(searchSource, fetchDeps);
|
||||||
const fetchType = isEsqlQuery ? 'fetchTextBased' : 'fetchDocuments';
|
const fetchType = isEsqlQuery ? 'fetchTextBased' : 'fetchDocuments';
|
||||||
|
@ -221,7 +220,7 @@ export async function fetchMoreDocuments(
|
||||||
fetchDeps: FetchDeps
|
fetchDeps: FetchDeps
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { getAppState, getInternalState, services, savedSearch } = fetchDeps;
|
const { getAppState, services, savedSearch } = fetchDeps;
|
||||||
const searchSource = savedSearch.searchSource.createChild();
|
const searchSource = savedSearch.searchSource.createChild();
|
||||||
const dataView = searchSource.getField('index')!;
|
const dataView = searchSource.getField('index')!;
|
||||||
const query = getAppState().query;
|
const query = getAppState().query;
|
||||||
|
@ -249,7 +248,6 @@ export async function fetchMoreDocuments(
|
||||||
dataView,
|
dataView,
|
||||||
services,
|
services,
|
||||||
sort: getAppState().sort as SortOrder[],
|
sort: getAppState().sort as SortOrder[],
|
||||||
customFilters: getInternalState().customFilters,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch more documents
|
// Fetch more documents
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_so
|
||||||
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||||
import type { SortOrder } from '@kbn/saved-search-plugin/public';
|
import type { SortOrder } from '@kbn/saved-search-plugin/public';
|
||||||
import { discoverServiceMock } from '../../../__mocks__/services';
|
import { discoverServiceMock } from '../../../__mocks__/services';
|
||||||
import { Filter } from '@kbn/es-query';
|
|
||||||
|
|
||||||
describe('updateVolatileSearchSource', () => {
|
describe('updateVolatileSearchSource', () => {
|
||||||
test('updates a given search source', async () => {
|
test('updates a given search source', async () => {
|
||||||
|
@ -22,24 +21,9 @@ describe('updateVolatileSearchSource', () => {
|
||||||
dataView: dataViewMock,
|
dataView: dataViewMock,
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
sort: [] as SortOrder[],
|
sort: [] as SortOrder[],
|
||||||
customFilters: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(searchSource.getField('fields')).toEqual([{ field: '*', include_unmapped: true }]);
|
expect(searchSource.getField('fields')).toEqual([{ field: '*', include_unmapped: true }]);
|
||||||
expect(searchSource.getField('fieldsFromSource')).toBe(undefined);
|
expect(searchSource.getField('fieldsFromSource')).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should properly update the search source with the given custom filters', async () => {
|
|
||||||
const searchSource = createSearchSourceMock({});
|
|
||||||
const filter = { meta: { index: 'foo', key: 'bar' } } as Filter;
|
|
||||||
|
|
||||||
updateVolatileSearchSource(searchSource, {
|
|
||||||
dataView: dataViewMock,
|
|
||||||
services: discoverServiceMock,
|
|
||||||
sort: [] as SortOrder[],
|
|
||||||
customFilters: [filter],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(searchSource.getField('filter')).toEqual([filter]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import type { ISearchSource } from '@kbn/data-plugin/public';
|
import type { ISearchSource } from '@kbn/data-plugin/public';
|
||||||
import { DataViewType, type DataView } from '@kbn/data-views-plugin/public';
|
import { DataViewType, type DataView } from '@kbn/data-views-plugin/public';
|
||||||
import type { Filter, TimeRange } from '@kbn/es-query';
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
import type { SortOrder } from '@kbn/saved-search-plugin/public';
|
import type { SortOrder } from '@kbn/saved-search-plugin/public';
|
||||||
import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils';
|
import { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils';
|
||||||
import { DiscoverServices } from '../../../build_services';
|
import { DiscoverServices } from '../../../build_services';
|
||||||
|
@ -24,13 +24,11 @@ export function updateVolatileSearchSource(
|
||||||
dataView,
|
dataView,
|
||||||
services,
|
services,
|
||||||
sort,
|
sort,
|
||||||
customFilters,
|
|
||||||
inputTimeRange,
|
inputTimeRange,
|
||||||
}: {
|
}: {
|
||||||
dataView: DataView;
|
dataView: DataView;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
sort?: SortOrder[];
|
sort?: SortOrder[];
|
||||||
customFilters: Filter[];
|
|
||||||
inputTimeRange?: TimeRange;
|
inputTimeRange?: TimeRange;
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -46,16 +44,14 @@ export function updateVolatileSearchSource(
|
||||||
|
|
||||||
searchSource.setField('trackTotalHits', true);
|
searchSource.setField('trackTotalHits', true);
|
||||||
|
|
||||||
let filters = [...customFilters];
|
|
||||||
|
|
||||||
if (dataView.type !== DataViewType.ROLLUP) {
|
if (dataView.type !== DataViewType.ROLLUP) {
|
||||||
// Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range
|
// Set the date range filter fields from timeFilter using the absolute format. Search sessions requires that it be converted from a relative range
|
||||||
const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, inputTimeRange);
|
searchSource.setField(
|
||||||
filters = timeFilter ? [...filters, timeFilter] : filters;
|
'filter',
|
||||||
|
data.query.timefilter.timefilter.createFilter(dataView, inputTimeRange)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchSource.setField('filter', filters);
|
|
||||||
|
|
||||||
searchSource.removeField('fieldsFromSource');
|
searchSource.removeField('fieldsFromSource');
|
||||||
searchSource.setField('fields', [{ field: '*', include_unmapped: true }]);
|
searchSource.setField('fields', [{ field: '*', include_unmapped: true }]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { Router } from '@kbn/shared-ux-router';
|
||||||
import { createMemoryHistory } from 'history';
|
import { createMemoryHistory } from 'history';
|
||||||
import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock';
|
||||||
import { DiscoverMainProvider } from './state_management/discover_state_provider';
|
import { DiscoverMainProvider } from './state_management/discover_state_provider';
|
||||||
|
import { RuntimeStateProvider, internalStateActions } from './state_management/redux';
|
||||||
|
|
||||||
discoverServiceMock.data.query.timefilter.timefilter.getTime = () => {
|
discoverServiceMock.data.query.timefilter.timefilter.getTime = () => {
|
||||||
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
|
return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' };
|
||||||
|
@ -32,7 +33,7 @@ describe('DiscoverMainApp', () => {
|
||||||
}) as unknown as DataViewListItem[];
|
}) as unknown as DataViewListItem[];
|
||||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||||
stateContainer.actions.setDataView(dataViewMock);
|
stateContainer.actions.setDataView(dataViewMock);
|
||||||
stateContainer.internalState.transitions.setSavedDataViews(dataViewList);
|
stateContainer.internalState.dispatch(internalStateActions.setSavedDataViews(dataViewList));
|
||||||
const props = {
|
const props = {
|
||||||
stateContainer,
|
stateContainer,
|
||||||
};
|
};
|
||||||
|
@ -41,11 +42,13 @@ describe('DiscoverMainApp', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
const component = await mountWithIntl(
|
const component = mountWithIntl(
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<KibanaContextProvider services={discoverServiceMock}>
|
<KibanaContextProvider services={discoverServiceMock}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
<DiscoverMainApp {...props} />
|
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||||
|
<DiscoverMainApp {...props} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
</DiscoverMainProvider>
|
</DiscoverMainProvider>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
</Router>
|
</Router>
|
||||||
|
@ -53,7 +56,7 @@ describe('DiscoverMainApp', () => {
|
||||||
|
|
||||||
// wait for lazy modules
|
// wait for lazy modules
|
||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
await component.update();
|
component.update();
|
||||||
|
|
||||||
expect(component.find(DiscoverTopNav).exists()).toBe(true);
|
expect(component.find(DiscoverTopNav).exists()).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -31,10 +31,7 @@ jest.mock('../../customizations', () => {
|
||||||
const originalModule = jest.requireActual('../../customizations');
|
const originalModule = jest.requireActual('../../customizations');
|
||||||
return {
|
return {
|
||||||
...originalModule,
|
...originalModule,
|
||||||
useDiscoverCustomizationService: () => ({
|
useDiscoverCustomizationService: () => mockCustomizationService,
|
||||||
customizationService: mockCustomizationService,
|
|
||||||
isInitialized: Boolean(mockCustomizationService),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useEffect, useState, memo, useCallback, useMemo } from 'react';
|
import React, { useEffect, useState, memo, useCallback, useMemo, lazy, ReactNode } from 'react';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams, useHistory } from 'react-router-dom';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||||
import {
|
import {
|
||||||
|
@ -22,6 +22,10 @@ import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
||||||
import { withSuspense } from '@kbn/shared-ux-utility';
|
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||||
import { getInitialESQLQuery } from '@kbn/esql-utils';
|
import { getInitialESQLQuery } from '@kbn/esql-utils';
|
||||||
import { ESQL_TYPE } from '@kbn/data-view-utils';
|
import { ESQL_TYPE } from '@kbn/data-view-utils';
|
||||||
|
import type {
|
||||||
|
AnalyticsNoDataPageKibanaDependencies,
|
||||||
|
AnalyticsNoDataPageProps,
|
||||||
|
} from '@kbn/shared-ux-page-analytics-no-data-types';
|
||||||
import { useUrl } from './hooks/use_url';
|
import { useUrl } from './hooks/use_url';
|
||||||
import { useDiscoverStateContainer } from './hooks/use_discover_state_container';
|
import { useDiscoverStateContainer } from './hooks/use_discover_state_container';
|
||||||
import { MainHistoryLocationState } from '../../../common';
|
import { MainHistoryLocationState } from '../../../common';
|
||||||
|
@ -41,6 +45,12 @@ import {
|
||||||
import { DiscoverStateContainer, LoadParams } from './state_management/discover_state';
|
import { DiscoverStateContainer, LoadParams } from './state_management/discover_state';
|
||||||
import { DataSourceType, isDataSourceType } from '../../../common/data_sources';
|
import { DataSourceType, isDataSourceType } from '../../../common/data_sources';
|
||||||
import { useDefaultAdHocDataViews, useRootProfile } from '../../context_awareness';
|
import { useDefaultAdHocDataViews, useRootProfile } from '../../context_awareness';
|
||||||
|
import {
|
||||||
|
RuntimeStateManager,
|
||||||
|
RuntimeStateProvider,
|
||||||
|
createRuntimeStateManager,
|
||||||
|
useRuntimeState,
|
||||||
|
} from './state_management/redux';
|
||||||
|
|
||||||
const DiscoverMainAppMemoized = memo(DiscoverMainApp);
|
const DiscoverMainAppMemoized = memo(DiscoverMainApp);
|
||||||
|
|
||||||
|
@ -72,17 +82,18 @@ export function DiscoverMainRoute({
|
||||||
getScopedHistory,
|
getScopedHistory,
|
||||||
} = services;
|
} = services;
|
||||||
const { id: savedSearchId } = useParams<DiscoverLandingParams>();
|
const { id: savedSearchId } = useParams<DiscoverLandingParams>();
|
||||||
|
const [runtimeStateManager] = useState(() => createRuntimeStateManager());
|
||||||
const [stateContainer, { reset: resetStateContainer }] = useDiscoverStateContainer({
|
const [stateContainer, { reset: resetStateContainer }] = useDiscoverStateContainer({
|
||||||
history,
|
history,
|
||||||
services,
|
services,
|
||||||
customizationContext,
|
customizationContext,
|
||||||
stateStorageContainer,
|
stateStorageContainer,
|
||||||
|
runtimeStateManager,
|
||||||
|
});
|
||||||
|
const customizationService = useDiscoverCustomizationService({
|
||||||
|
customizationCallbacks,
|
||||||
|
stateContainer,
|
||||||
});
|
});
|
||||||
const { customizationService, isInitialized: isCustomizationServiceInitialized } =
|
|
||||||
useDiscoverCustomizationService({
|
|
||||||
customizationCallbacks,
|
|
||||||
stateContainer,
|
|
||||||
});
|
|
||||||
const [error, setError] = useState<Error>();
|
const [error, setError] = useState<Error>();
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [noDataState, setNoDataState] = useState({
|
const [noDataState, setNoDataState] = useState({
|
||||||
|
@ -110,6 +121,7 @@ export function DiscoverMainRoute({
|
||||||
page: 'app',
|
page: 'app',
|
||||||
id: savedSearchId || 'new',
|
id: savedSearchId || 'new',
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to determine when to skip the no data page
|
* Helper function to determine when to skip the no data page
|
||||||
*/
|
*/
|
||||||
|
@ -134,8 +146,7 @@ export function DiscoverMainRoute({
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const persistedDataViewsExist = hasUserDataViewValue && defaultDataViewExists;
|
const persistedDataViewsExist = hasUserDataViewValue && defaultDataViewExists;
|
||||||
const adHocDataViewsExist =
|
const adHocDataViewsExist = runtimeStateManager.adHocDataViews$.getValue().length > 0;
|
||||||
stateContainer.internalState.getState().adHocDataViews.length > 0;
|
|
||||||
const locationStateHasDataViewSpec = Boolean(historyLocationState?.dataViewSpec);
|
const locationStateHasDataViewSpec = Boolean(historyLocationState?.dataViewSpec);
|
||||||
const canAccessWithAdHocDataViews =
|
const canAccessWithAdHocDataViews =
|
||||||
hasESDataValue && (adHocDataViewsExist || locationStateHasDataViewSpec);
|
hasESDataValue && (adHocDataViewsExist || locationStateHasDataViewSpec);
|
||||||
|
@ -156,7 +167,13 @@ export function DiscoverMainRoute({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[data.dataViews, historyLocationState?.dataViewSpec, savedSearchId, stateContainer]
|
[
|
||||||
|
data.dataViews,
|
||||||
|
historyLocationState?.dataViewSpec,
|
||||||
|
runtimeStateManager,
|
||||||
|
savedSearchId,
|
||||||
|
stateContainer,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadSavedSearch = useCallback(
|
const loadSavedSearch = useCallback(
|
||||||
|
@ -243,7 +260,7 @@ export function DiscoverMainRoute({
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isCustomizationServiceInitialized || rootProfileState.rootProfileLoading) {
|
if (!customizationService || rootProfileState.rootProfileLoading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,16 +279,17 @@ export function DiscoverMainRoute({
|
||||||
await loadSavedSearch();
|
await loadSavedSearch();
|
||||||
} else {
|
} else {
|
||||||
// restore the previously selected data view for a new state (when a saved search was open)
|
// restore the previously selected data view for a new state (when a saved search was open)
|
||||||
await loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
|
await loadSavedSearch(getLoadParamsForNewSearch({ stateContainer, runtimeStateManager }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
load();
|
load();
|
||||||
}, [
|
}, [
|
||||||
|
customizationService,
|
||||||
initializeProfileDataViews,
|
initializeProfileDataViews,
|
||||||
isCustomizationServiceInitialized,
|
|
||||||
loadSavedSearch,
|
loadSavedSearch,
|
||||||
rootProfileState.rootProfileLoading,
|
rootProfileState.rootProfileLoading,
|
||||||
|
runtimeStateManager,
|
||||||
savedSearchId,
|
savedSearchId,
|
||||||
stateContainer,
|
stateContainer,
|
||||||
]);
|
]);
|
||||||
|
@ -280,10 +298,10 @@ export function DiscoverMainRoute({
|
||||||
useUrl({
|
useUrl({
|
||||||
history,
|
history,
|
||||||
savedSearchId,
|
savedSearchId,
|
||||||
onNewUrl: () => {
|
onNewUrl: useCallback(() => {
|
||||||
// restore the previously selected data view for a new state
|
// restore the previously selected data view for a new state
|
||||||
loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
|
loadSavedSearch(getLoadParamsForNewSearch({ stateContainer, runtimeStateManager }));
|
||||||
},
|
}, [loadSavedSearch, runtimeStateManager, stateContainer]),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDataViewCreated = useCallback(
|
const onDataViewCreated = useCallback(
|
||||||
|
@ -302,7 +320,7 @@ export function DiscoverMainRoute({
|
||||||
resetStateContainer();
|
resetStateContainer();
|
||||||
}, [resetStateContainer]);
|
}, [resetStateContainer]);
|
||||||
|
|
||||||
const noDataDependencies = useMemo(
|
const noDataDependencies = useMemo<AnalyticsNoDataPageKibanaDependencies>(
|
||||||
() => ({
|
() => ({
|
||||||
coreStart: core,
|
coreStart: core,
|
||||||
dataViews: {
|
dataViews: {
|
||||||
|
@ -323,62 +341,39 @@ export function DiscoverMainRoute({
|
||||||
[core, data.dataViews, dataViewEditor, noDataState, services.noDataPage, share]
|
[core, data.dataViews, dataViewEditor, noDataState, services.noDataPage, share]
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadingIndicator = useMemo(
|
const currentDataView = useRuntimeState(runtimeStateManager.currentDataView$);
|
||||||
() => <LoadingIndicator type={hasCustomBranding ? 'spinner' : 'elastic'} />,
|
const adHocDataViews = useRuntimeState(runtimeStateManager.adHocDataViews$);
|
||||||
[hasCustomBranding]
|
|
||||||
);
|
|
||||||
|
|
||||||
const mainContent = useMemo(() => {
|
|
||||||
if (noDataState.showNoDataPage) {
|
|
||||||
const importPromise = import('@kbn/shared-ux-page-analytics-no-data');
|
|
||||||
const AnalyticsNoDataPageKibanaProvider = withSuspense(
|
|
||||||
React.lazy(() =>
|
|
||||||
importPromise.then(({ AnalyticsNoDataPageKibanaProvider: NoDataProvider }) => {
|
|
||||||
return { default: NoDataProvider };
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const AnalyticsNoDataPage = withSuspense(
|
|
||||||
React.lazy(() =>
|
|
||||||
importPromise.then(({ AnalyticsNoDataPage: NoDataPage }) => {
|
|
||||||
return { default: NoDataPage };
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AnalyticsNoDataPageKibanaProvider {...noDataDependencies}>
|
|
||||||
<AnalyticsNoDataPage
|
|
||||||
onDataViewCreated={onDataViewCreated}
|
|
||||||
onESQLNavigationComplete={onESQLNavigationComplete}
|
|
||||||
/>
|
|
||||||
</AnalyticsNoDataPageKibanaProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return loadingIndicator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <DiscoverMainAppMemoized stateContainer={stateContainer} />;
|
|
||||||
}, [
|
|
||||||
loading,
|
|
||||||
loadingIndicator,
|
|
||||||
noDataDependencies,
|
|
||||||
onDataViewCreated,
|
|
||||||
onESQLNavigationComplete,
|
|
||||||
noDataState.showNoDataPage,
|
|
||||||
stateContainer,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <DiscoverError error={error} />;
|
return <DiscoverError error={error} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadingIndicator = <LoadingIndicator type={hasCustomBranding ? 'spinner' : 'elastic'} />;
|
||||||
|
|
||||||
if (!customizationService || rootProfileState.rootProfileLoading) {
|
if (!customizationService || rootProfileState.rootProfileLoading) {
|
||||||
return loadingIndicator;
|
return loadingIndicator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mainContent: ReactNode;
|
||||||
|
|
||||||
|
if (!loading && noDataState.showNoDataPage) {
|
||||||
|
mainContent = (
|
||||||
|
<NoDataPage
|
||||||
|
onDataViewCreated={onDataViewCreated}
|
||||||
|
onESQLNavigationComplete={onESQLNavigationComplete}
|
||||||
|
{...noDataDependencies}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (!loading && currentDataView) {
|
||||||
|
mainContent = (
|
||||||
|
<RuntimeStateProvider currentDataView={currentDataView} adHocDataViews={adHocDataViews}>
|
||||||
|
<DiscoverMainAppMemoized stateContainer={stateContainer} />
|
||||||
|
</RuntimeStateProvider>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
mainContent = loadingIndicator;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DiscoverCustomizationProvider value={customizationService}>
|
<DiscoverCustomizationProvider value={customizationService}>
|
||||||
<DiscoverMainProvider value={stateContainer}>
|
<DiscoverMainProvider value={stateContainer}>
|
||||||
|
@ -387,15 +382,45 @@ export function DiscoverMainRoute({
|
||||||
</DiscoverCustomizationProvider>
|
</DiscoverCustomizationProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-default-export
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default DiscoverMainRoute;
|
export default DiscoverMainRoute;
|
||||||
|
|
||||||
function getLoadParamsForNewSearch(stateContainer: DiscoverStateContainer): {
|
const NoDataPage = ({
|
||||||
|
onDataViewCreated,
|
||||||
|
onESQLNavigationComplete,
|
||||||
|
...noDataDependencies
|
||||||
|
}: AnalyticsNoDataPageKibanaDependencies & AnalyticsNoDataPageProps) => {
|
||||||
|
const importPromise = import('@kbn/shared-ux-page-analytics-no-data');
|
||||||
|
const AnalyticsNoDataPageKibanaProvider = withSuspense(
|
||||||
|
lazy(async () => ({ default: (await importPromise).AnalyticsNoDataPageKibanaProvider }))
|
||||||
|
);
|
||||||
|
const AnalyticsNoDataPage = withSuspense(
|
||||||
|
lazy(async () => ({ default: (await importPromise).AnalyticsNoDataPage }))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnalyticsNoDataPageKibanaProvider {...noDataDependencies}>
|
||||||
|
<AnalyticsNoDataPage
|
||||||
|
onDataViewCreated={onDataViewCreated}
|
||||||
|
onESQLNavigationComplete={onESQLNavigationComplete}
|
||||||
|
/>
|
||||||
|
</AnalyticsNoDataPageKibanaProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getLoadParamsForNewSearch({
|
||||||
|
stateContainer,
|
||||||
|
runtimeStateManager,
|
||||||
|
}: {
|
||||||
|
stateContainer: DiscoverStateContainer;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
|
}): {
|
||||||
nextDataView: LoadParams['dataView'];
|
nextDataView: LoadParams['dataView'];
|
||||||
initialAppState: LoadParams['initialAppState'];
|
initialAppState: LoadParams['initialAppState'];
|
||||||
} {
|
} {
|
||||||
const prevAppState = stateContainer.appState.getState();
|
const prevAppState = stateContainer.appState.getState();
|
||||||
const prevDataView = stateContainer.internalState.getState().dataView;
|
const prevDataView = runtimeStateManager.currentDataView$.getValue();
|
||||||
const initialAppState =
|
const initialAppState =
|
||||||
isDataSourceType(prevAppState.dataSource, DataSourceType.Esql) &&
|
isDataSourceType(prevAppState.dataSource, DataSourceType.Esql) &&
|
||||||
prevDataView &&
|
prevDataView &&
|
||||||
|
|
|
@ -11,11 +11,11 @@ import { useEffect } from 'react';
|
||||||
import { METRIC_TYPE } from '@kbn/analytics';
|
import { METRIC_TYPE } from '@kbn/analytics';
|
||||||
import { DiscoverServices } from '../../../build_services';
|
import { DiscoverServices } from '../../../build_services';
|
||||||
import { useSavedSearch } from '../state_management/discover_state_provider';
|
import { useSavedSearch } from '../state_management/discover_state_provider';
|
||||||
import { useInternalStateSelector } from '../state_management/discover_internal_state_container';
|
|
||||||
import { ADHOC_DATA_VIEW_RENDER_EVENT } from '../../../constants';
|
import { ADHOC_DATA_VIEW_RENDER_EVENT } from '../../../constants';
|
||||||
import { DiscoverStateContainer } from '../state_management/discover_state';
|
import { DiscoverStateContainer } from '../state_management/discover_state';
|
||||||
import { useFiltersValidation } from './use_filters_validation';
|
import { useFiltersValidation } from './use_filters_validation';
|
||||||
import { useIsEsqlMode } from './use_is_esql_mode';
|
import { useIsEsqlMode } from './use_is_esql_mode';
|
||||||
|
import { useCurrentDataView } from '../state_management/redux';
|
||||||
|
|
||||||
export const useAdHocDataViews = ({
|
export const useAdHocDataViews = ({
|
||||||
services,
|
services,
|
||||||
|
@ -23,7 +23,7 @@ export const useAdHocDataViews = ({
|
||||||
stateContainer: DiscoverStateContainer;
|
stateContainer: DiscoverStateContainer;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
}) => {
|
}) => {
|
||||||
const dataView = useInternalStateSelector((state) => state.dataView);
|
const dataView = useCurrentDataView();
|
||||||
const savedSearch = useSavedSearch();
|
const savedSearch = useSavedSearch();
|
||||||
const isEsqlMode = useIsEsqlMode();
|
const isEsqlMode = useIsEsqlMode();
|
||||||
const { filterManager, toastNotifications } = services;
|
const { filterManager, toastNotifications } = services;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { VIEW_MODE } from '@kbn/saved-search-plugin/public';
|
||||||
import { dataViewAdHoc } from '../../../__mocks__/data_view_complex';
|
import { dataViewAdHoc } from '../../../__mocks__/data_view_complex';
|
||||||
import { buildDataTableRecord, EsHitRecord } from '@kbn/discover-utils';
|
import { buildDataTableRecord, EsHitRecord } from '@kbn/discover-utils';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import { internalStateActions } from '../state_management/redux';
|
||||||
|
|
||||||
function getHookProps(
|
function getHookProps(
|
||||||
query: AggregateQuery | Query | undefined,
|
query: AggregateQuery | Query | undefined,
|
||||||
|
@ -37,7 +38,9 @@ function getHookProps(
|
||||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||||
stateContainer.appState.replaceUrlState = replaceUrlState;
|
stateContainer.appState.replaceUrlState = replaceUrlState;
|
||||||
stateContainer.appState.update({ columns: [], ...appState });
|
stateContainer.appState.update({ columns: [], ...appState });
|
||||||
stateContainer.internalState.transitions.setSavedDataViews([dataViewMock as DataViewListItem]);
|
stateContainer.internalState.dispatch(
|
||||||
|
internalStateActions.setSavedDataViews([dataViewMock as DataViewListItem])
|
||||||
|
);
|
||||||
|
|
||||||
const msgLoading = {
|
const msgLoading = {
|
||||||
fetchStatus: defaultFetchStatus,
|
fetchStatus: defaultFetchStatus,
|
||||||
|
@ -502,7 +505,9 @@ describe('useEsqlMode', () => {
|
||||||
FetchStatus.LOADING
|
FetchStatus.LOADING
|
||||||
);
|
);
|
||||||
const documents$ = stateContainer.dataState.data$.documents$;
|
const documents$ = stateContainer.dataState.data$.documents$;
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
@ -517,7 +522,9 @@ describe('useEsqlMode', () => {
|
||||||
query: { esql: 'from pattern1' },
|
query: { esql: 'from pattern1' },
|
||||||
});
|
});
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: true,
|
columns: true,
|
||||||
rowHeight: true,
|
rowHeight: true,
|
||||||
breakdownField: true,
|
breakdownField: true,
|
||||||
|
@ -527,18 +534,22 @@ describe('useEsqlMode', () => {
|
||||||
fetchStatus: FetchStatus.PARTIAL,
|
fetchStatus: FetchStatus.PARTIAL,
|
||||||
query: { esql: 'from pattern1' },
|
query: { esql: 'from pattern1' },
|
||||||
});
|
});
|
||||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
stateContainer.internalState.dispatch(
|
||||||
columns: false,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: false,
|
columns: false,
|
||||||
breakdownField: false,
|
rowHeight: false,
|
||||||
});
|
breakdownField: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
stateContainer.appState.update({ query: { esql: 'from pattern1' } });
|
stateContainer.appState.update({ query: { esql: 'from pattern1' } });
|
||||||
documents$.next({
|
documents$.next({
|
||||||
fetchStatus: FetchStatus.LOADING,
|
fetchStatus: FetchStatus.LOADING,
|
||||||
query: { esql: 'from pattern1' },
|
query: { esql: 'from pattern1' },
|
||||||
});
|
});
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
@ -554,7 +565,9 @@ describe('useEsqlMode', () => {
|
||||||
query: { esql: 'from pattern2' },
|
query: { esql: 'from pattern2' },
|
||||||
});
|
});
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: true,
|
columns: true,
|
||||||
rowHeight: true,
|
rowHeight: true,
|
||||||
breakdownField: true,
|
breakdownField: true,
|
||||||
|
@ -571,7 +584,9 @@ describe('useEsqlMode', () => {
|
||||||
const documents$ = stateContainer.dataState.data$.documents$;
|
const documents$ = stateContainer.dataState.data$.documents$;
|
||||||
const result1 = [buildDataTableRecord({ message: 'foo' } as EsHitRecord)];
|
const result1 = [buildDataTableRecord({ message: 'foo' } as EsHitRecord)];
|
||||||
const result2 = [buildDataTableRecord({ message: 'foo', extension: 'bar' } as EsHitRecord)];
|
const result2 = [buildDataTableRecord({ message: 'foo', extension: 'bar' } as EsHitRecord)];
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
@ -582,7 +597,9 @@ describe('useEsqlMode', () => {
|
||||||
result: result1,
|
result: result1,
|
||||||
});
|
});
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
@ -594,7 +611,9 @@ describe('useEsqlMode', () => {
|
||||||
result: result2,
|
result: result2,
|
||||||
});
|
});
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: true,
|
columns: true,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { useSavedSearchInitial } from '../state_management/discover_state_provid
|
||||||
import type { DiscoverStateContainer } from '../state_management/discover_state';
|
import type { DiscoverStateContainer } from '../state_management/discover_state';
|
||||||
import { getValidViewMode } from '../utils/get_valid_view_mode';
|
import { getValidViewMode } from '../utils/get_valid_view_mode';
|
||||||
import { FetchStatus } from '../../types';
|
import { FetchStatus } from '../../types';
|
||||||
|
import { internalStateActions, useInternalStateDispatch } from '../state_management/redux';
|
||||||
|
|
||||||
const MAX_NUM_OF_COLUMNS = 50;
|
const MAX_NUM_OF_COLUMNS = 50;
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ export function useEsqlMode({
|
||||||
stateContainer: DiscoverStateContainer;
|
stateContainer: DiscoverStateContainer;
|
||||||
dataViews: DataViewsContract;
|
dataViews: DataViewsContract;
|
||||||
}) {
|
}) {
|
||||||
|
const dispatch = useInternalStateDispatch();
|
||||||
const savedSearch = useSavedSearchInitial();
|
const savedSearch = useSavedSearchInitial();
|
||||||
const prev = useRef<{
|
const prev = useRef<{
|
||||||
initialFetch: boolean;
|
initialFetch: boolean;
|
||||||
|
@ -93,11 +95,13 @@ export function useEsqlMode({
|
||||||
|
|
||||||
// Reset all default profile state when index pattern changes
|
// Reset all default profile state when index pattern changes
|
||||||
if (indexPatternChanged) {
|
if (indexPatternChanged) {
|
||||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
dispatch(
|
||||||
columns: true,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: true,
|
columns: true,
|
||||||
breakdownField: true,
|
rowHeight: true,
|
||||||
});
|
breakdownField: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,11 +153,13 @@ export function useEsqlMode({
|
||||||
// If the index pattern hasn't changed, but the available columns have changed
|
// If the index pattern hasn't changed, but the available columns have changed
|
||||||
// due to transformational commands, reset the associated default profile state
|
// due to transformational commands, reset the associated default profile state
|
||||||
if (!indexPatternChanged && allColumnsChanged) {
|
if (!indexPatternChanged && allColumnsChanged) {
|
||||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
dispatch(
|
||||||
columns: true,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: false,
|
columns: true,
|
||||||
breakdownField: false,
|
rowHeight: false,
|
||||||
});
|
breakdownField: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prev.current.allColumns = nextAllColumns;
|
prev.current.allColumns = nextAllColumns;
|
||||||
|
@ -186,5 +192,5 @@ export function useEsqlMode({
|
||||||
cleanup();
|
cleanup();
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [dataViews, stateContainer, savedSearch, cleanup]);
|
}, [dataViews, stateContainer, savedSearch, cleanup, dispatch]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,9 @@ import { OverlayRef } from '@kbn/core/public';
|
||||||
import { AggregateRequestAdapter } from '../utils/aggregate_request_adapter';
|
import { AggregateRequestAdapter } from '../utils/aggregate_request_adapter';
|
||||||
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
||||||
|
import { internalStateActions } from '../state_management/redux';
|
||||||
|
import React from 'react';
|
||||||
|
import { DiscoverMainProvider } from '../state_management/discover_state_provider';
|
||||||
|
|
||||||
describe('test useInspector', () => {
|
describe('test useInspector', () => {
|
||||||
test('inspector open function is executed, expanded doc is closed', async () => {
|
test('inspector open function is executed, expanded doc is closed', async () => {
|
||||||
|
@ -26,13 +29,22 @@ describe('test useInspector', () => {
|
||||||
const requests = new RequestAdapter();
|
const requests = new RequestAdapter();
|
||||||
const lensRequests = new RequestAdapter();
|
const lensRequests = new RequestAdapter();
|
||||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||||
stateContainer.internalState.transitions.setExpandedDoc({} as unknown as DataTableRecord);
|
stateContainer.internalState.dispatch(
|
||||||
const { result } = renderHook(() => {
|
internalStateActions.setExpandedDoc({} as unknown as DataTableRecord)
|
||||||
return useInspector({
|
);
|
||||||
stateContainer,
|
const { result } = renderHook(
|
||||||
inspector: discoverServiceMock.inspector,
|
() => {
|
||||||
});
|
return useInspector({
|
||||||
});
|
stateContainer,
|
||||||
|
inspector: discoverServiceMock.inspector,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<DiscoverMainProvider value={stateContainer}>{children}</DiscoverMainProvider>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
result.current();
|
result.current();
|
||||||
});
|
});
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from '@kbn/inspector-plugin/public';
|
} from '@kbn/inspector-plugin/public';
|
||||||
import { DiscoverStateContainer } from '../state_management/discover_state';
|
import { DiscoverStateContainer } from '../state_management/discover_state';
|
||||||
import { AggregateRequestAdapter } from '../utils/aggregate_request_adapter';
|
import { AggregateRequestAdapter } from '../utils/aggregate_request_adapter';
|
||||||
|
import { internalStateActions, useInternalStateDispatch } from '../state_management/redux';
|
||||||
|
|
||||||
export interface InspectorAdapters {
|
export interface InspectorAdapters {
|
||||||
requests: RequestAdapter;
|
requests: RequestAdapter;
|
||||||
|
@ -28,11 +29,13 @@ export function useInspector({
|
||||||
inspector: InspectorPublicPluginStart;
|
inspector: InspectorPublicPluginStart;
|
||||||
stateContainer: DiscoverStateContainer;
|
stateContainer: DiscoverStateContainer;
|
||||||
}) {
|
}) {
|
||||||
|
const dispatch = useInternalStateDispatch();
|
||||||
const [inspectorSession, setInspectorSession] = useState<InspectorSession | undefined>(undefined);
|
const [inspectorSession, setInspectorSession] = useState<InspectorSession | undefined>(undefined);
|
||||||
|
|
||||||
const onOpenInspector = useCallback(() => {
|
const onOpenInspector = useCallback(() => {
|
||||||
// prevent overlapping
|
// prevent overlapping
|
||||||
stateContainer.internalState.transitions.setExpandedDoc(undefined);
|
dispatch(internalStateActions.setExpandedDoc(undefined));
|
||||||
|
|
||||||
const inspectorAdapters = stateContainer.dataState.inspectorAdapters;
|
const inspectorAdapters = stateContainer.dataState.inspectorAdapters;
|
||||||
|
|
||||||
const requestAdapters = inspectorAdapters.lensRequests
|
const requestAdapters = inspectorAdapters.lensRequests
|
||||||
|
@ -45,7 +48,12 @@ export function useInspector({
|
||||||
);
|
);
|
||||||
|
|
||||||
setInspectorSession(session);
|
setInspectorSession(session);
|
||||||
}, [stateContainer, inspector]);
|
}, [
|
||||||
|
dispatch,
|
||||||
|
stateContainer.dataState.inspectorAdapters,
|
||||||
|
stateContainer.savedSearchState,
|
||||||
|
inspector,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { DiscoverServices } from '../../../build_services';
|
||||||
import { DiscoverStateContainer } from '../state_management/discover_state';
|
import { DiscoverStateContainer } from '../state_management/discover_state';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { createSavedSearchAdHocMock, createSavedSearchMock } from '../../../__mocks__/saved_search';
|
import { createSavedSearchAdHocMock, createSavedSearchMock } from '../../../__mocks__/saved_search';
|
||||||
|
import { internalStateActions } from '../state_management/redux';
|
||||||
|
|
||||||
const renderUrlTracking = ({
|
const renderUrlTracking = ({
|
||||||
services,
|
services,
|
||||||
|
@ -55,9 +56,11 @@ describe('useUrlTracking', () => {
|
||||||
const services = createDiscoverServicesMock();
|
const services = createDiscoverServicesMock();
|
||||||
const savedSearch = omit(createSavedSearchAdHocMock(), 'id');
|
const savedSearch = omit(createSavedSearchAdHocMock(), 'id');
|
||||||
const stateContainer = getDiscoverStateMock({ savedSearch });
|
const stateContainer = getDiscoverStateMock({ savedSearch });
|
||||||
stateContainer.internalState.transitions.setDefaultProfileAdHocDataViews([
|
stateContainer.internalState.dispatch(
|
||||||
savedSearch.searchSource.getField('index')!,
|
internalStateActions.setDefaultProfileAdHocDataViews([
|
||||||
]);
|
savedSearch.searchSource.getField('index')!,
|
||||||
|
])
|
||||||
|
);
|
||||||
expect(services.urlTracker.setTrackingEnabled).not.toHaveBeenCalled();
|
expect(services.urlTracker.setTrackingEnabled).not.toHaveBeenCalled();
|
||||||
renderUrlTracking({ services, stateContainer });
|
renderUrlTracking({ services, stateContainer });
|
||||||
expect(services.urlTracker.setTrackingEnabled).toHaveBeenCalledWith(true);
|
expect(services.urlTracker.setTrackingEnabled).toHaveBeenCalledWith(true);
|
||||||
|
|
|
@ -31,7 +31,7 @@ export function useUrlTracking(stateContainer: DiscoverStateContainer) {
|
||||||
// Disable for ad hoc data views, since they can't be restored after a page refresh
|
// Disable for ad hoc data views, since they can't be restored after a page refresh
|
||||||
dataView.isPersisted() ||
|
dataView.isPersisted() ||
|
||||||
// Unless it's a default profile data view, which can be restored on refresh
|
// Unless it's a default profile data view, which can be restored on refresh
|
||||||
internalState.get().defaultProfileAdHocDataViewIds.includes(dataView.id) ||
|
internalState.getState().defaultProfileAdHocDataViewIds.includes(dataView.id) ||
|
||||||
// Or we're in ES|QL mode, in which case we don't care about the data view
|
// Or we're in ES|QL mode, in which case we don't care about the data view
|
||||||
isOfAggregateQueryType(savedSearch.searchSource.getField('query'));
|
isOfAggregateQueryType(savedSearch.searchSource.getField('query'));
|
||||||
|
|
||||||
|
|
|
@ -20,17 +20,17 @@ import { discoverServiceMock } from '../../../__mocks__/services';
|
||||||
import { getDiscoverAppStateContainer, isEqualState } from './discover_app_state_container';
|
import { getDiscoverAppStateContainer, isEqualState } from './discover_app_state_container';
|
||||||
import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/common';
|
import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/common';
|
||||||
import { createDataViewDataSource } from '../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../common/data_sources';
|
||||||
import { getInternalStateContainer } from './discover_internal_state_container';
|
|
||||||
import {
|
import {
|
||||||
DiscoverSavedSearchContainer,
|
DiscoverSavedSearchContainer,
|
||||||
getSavedSearchContainer,
|
getSavedSearchContainer,
|
||||||
} from './discover_saved_search_container';
|
} from './discover_saved_search_container';
|
||||||
import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
|
import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import { createInternalStateStore, createRuntimeStateManager, InternalStateStore } from './redux';
|
||||||
|
|
||||||
let history: History;
|
let history: History;
|
||||||
let stateStorage: IKbnUrlStateStorage;
|
let stateStorage: IKbnUrlStateStorage;
|
||||||
let internalState: ReturnType<typeof getInternalStateContainer>;
|
let internalState: InternalStateStore;
|
||||||
let savedSearchState: DiscoverSavedSearchContainer;
|
let savedSearchState: DiscoverSavedSearchContainer;
|
||||||
|
|
||||||
describe('Test discover app state container', () => {
|
describe('Test discover app state container', () => {
|
||||||
|
@ -42,18 +42,21 @@ describe('Test discover app state container', () => {
|
||||||
history,
|
history,
|
||||||
...(toasts && withNotifyOnErrors(toasts)),
|
...(toasts && withNotifyOnErrors(toasts)),
|
||||||
});
|
});
|
||||||
internalState = getInternalStateContainer();
|
internalState = createInternalStateStore({
|
||||||
|
services: discoverServiceMock,
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
|
});
|
||||||
savedSearchState = getSavedSearchContainer({
|
savedSearchState = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer: getDiscoverGlobalStateContainer(stateStorage),
|
globalStateContainer: getDiscoverGlobalStateContainer(stateStorage),
|
||||||
internalStateContainer: internalState,
|
internalState,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const getStateContainer = () =>
|
const getStateContainer = () =>
|
||||||
getDiscoverAppStateContainer({
|
getDiscoverAppStateContainer({
|
||||||
stateStorage,
|
stateStorage,
|
||||||
internalStateContainer: internalState,
|
internalState,
|
||||||
savedSearchContainer: savedSearchState,
|
savedSearchContainer: savedSearchState,
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
});
|
});
|
||||||
|
@ -271,13 +274,13 @@ describe('Test discover app state container', () => {
|
||||||
describe('initAndSync', () => {
|
describe('initAndSync', () => {
|
||||||
it('should call setResetDefaultProfileState correctly with no initial state', () => {
|
it('should call setResetDefaultProfileState correctly with no initial state', () => {
|
||||||
const state = getStateContainer();
|
const state = getStateContainer();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
});
|
});
|
||||||
state.initAndSync();
|
state.initAndSync();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: true,
|
columns: true,
|
||||||
rowHeight: true,
|
rowHeight: true,
|
||||||
breakdownField: true,
|
breakdownField: true,
|
||||||
|
@ -288,13 +291,13 @@ describe('Test discover app state container', () => {
|
||||||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||||
stateStorageGetSpy.mockReturnValue({ columns: ['test'] });
|
stateStorageGetSpy.mockReturnValue({ columns: ['test'] });
|
||||||
const state = getStateContainer();
|
const state = getStateContainer();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
});
|
});
|
||||||
state.initAndSync();
|
state.initAndSync();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: true,
|
rowHeight: true,
|
||||||
breakdownField: true,
|
breakdownField: true,
|
||||||
|
@ -305,13 +308,13 @@ describe('Test discover app state container', () => {
|
||||||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||||
stateStorageGetSpy.mockReturnValue({ rowHeight: 5 });
|
stateStorageGetSpy.mockReturnValue({ rowHeight: 5 });
|
||||||
const state = getStateContainer();
|
const state = getStateContainer();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
});
|
});
|
||||||
state.initAndSync();
|
state.initAndSync();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: true,
|
columns: true,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: true,
|
breakdownField: true,
|
||||||
|
@ -328,13 +331,13 @@ describe('Test discover app state container', () => {
|
||||||
managed: false,
|
managed: false,
|
||||||
});
|
});
|
||||||
const state = getStateContainer();
|
const state = getStateContainer();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
});
|
});
|
||||||
state.initAndSync();
|
state.initAndSync();
|
||||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
|
|
@ -40,8 +40,8 @@ import {
|
||||||
DiscoverDataSource,
|
DiscoverDataSource,
|
||||||
isDataSourceType,
|
isDataSourceType,
|
||||||
} from '../../../../common/data_sources';
|
} from '../../../../common/data_sources';
|
||||||
import type { DiscoverInternalStateContainer } from './discover_internal_state_container';
|
|
||||||
import type { DiscoverSavedSearchContainer } from './discover_saved_search_container';
|
import type { DiscoverSavedSearchContainer } from './discover_saved_search_container';
|
||||||
|
import { internalStateActions, InternalStateStore } from './redux';
|
||||||
|
|
||||||
export const APP_STATE_URL_KEY = '_a';
|
export const APP_STATE_URL_KEY = '_a';
|
||||||
export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<DiscoverAppState> {
|
export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<DiscoverAppState> {
|
||||||
|
@ -185,12 +185,12 @@ export const { Provider: DiscoverAppStateProvider, useSelector: useAppStateSelec
|
||||||
*/
|
*/
|
||||||
export const getDiscoverAppStateContainer = ({
|
export const getDiscoverAppStateContainer = ({
|
||||||
stateStorage,
|
stateStorage,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
savedSearchContainer,
|
savedSearchContainer,
|
||||||
services,
|
services,
|
||||||
}: {
|
}: {
|
||||||
stateStorage: IKbnUrlStateStorage;
|
stateStorage: IKbnUrlStateStorage;
|
||||||
internalStateContainer: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
savedSearchContainer: DiscoverSavedSearchContainer;
|
savedSearchContainer: DiscoverSavedSearchContainer;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
}): DiscoverAppStateContainer => {
|
}): DiscoverAppStateContainer => {
|
||||||
|
@ -268,11 +268,13 @@ export const getDiscoverAppStateContainer = ({
|
||||||
const { breakdownField, columns, rowHeight } = getCurrentUrlState(stateStorage, services);
|
const { breakdownField, columns, rowHeight } = getCurrentUrlState(stateStorage, services);
|
||||||
|
|
||||||
// Only set default state which is not already set in the URL
|
// Only set default state which is not already set in the URL
|
||||||
internalStateContainer.transitions.setResetDefaultProfileState({
|
internalState.dispatch(
|
||||||
columns: columns === undefined,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: rowHeight === undefined,
|
columns: columns === undefined,
|
||||||
breakdownField: breakdownField === undefined,
|
rowHeight: rowHeight === undefined,
|
||||||
});
|
breakdownField: breakdownField === undefined,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = services;
|
const { data } = services;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { DataDocuments$ } from './discover_data_state_container';
|
||||||
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
||||||
import { fetchDocuments } from '../data_fetching/fetch_documents';
|
import { fetchDocuments } from '../data_fetching/fetch_documents';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
|
import { internalStateActions } from './redux';
|
||||||
|
|
||||||
jest.mock('../data_fetching/fetch_documents', () => ({
|
jest.mock('../data_fetching/fetch_documents', () => ({
|
||||||
fetchDocuments: jest.fn().mockResolvedValue({ records: [] }),
|
fetchDocuments: jest.fn().mockResolvedValue({ records: [] }),
|
||||||
|
@ -176,11 +177,13 @@ describe('test getDataStateContainer', () => {
|
||||||
const appUnsub = stateContainer.appState.initAndSync();
|
const appUnsub = stateContainer.appState.initAndSync();
|
||||||
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||||
stateContainer.actions.setDataView(dataViewMock);
|
stateContainer.actions.setDataView(dataViewMock);
|
||||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
stateContainer.internalState.dispatch(
|
||||||
columns: true,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: true,
|
columns: true,
|
||||||
breakdownField: true,
|
rowHeight: true,
|
||||||
});
|
breakdownField: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
dataState.data$.totalHits$.next({
|
dataState.data$.totalHits$.next({
|
||||||
fetchStatus: FetchStatus.COMPLETE,
|
fetchStatus: FetchStatus.COMPLETE,
|
||||||
|
@ -191,7 +194,9 @@ describe('test getDataStateContainer', () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
||||||
});
|
});
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
@ -209,11 +214,13 @@ describe('test getDataStateContainer', () => {
|
||||||
const appUnsub = stateContainer.appState.initAndSync();
|
const appUnsub = stateContainer.appState.initAndSync();
|
||||||
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||||
stateContainer.actions.setDataView(dataViewMock);
|
stateContainer.actions.setDataView(dataViewMock);
|
||||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
stateContainer.internalState.dispatch(
|
||||||
columns: false,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: false,
|
columns: false,
|
||||||
breakdownField: false,
|
rowHeight: false,
|
||||||
});
|
breakdownField: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
dataState.data$.totalHits$.next({
|
dataState.data$.totalHits$.next({
|
||||||
fetchStatus: FetchStatus.COMPLETE,
|
fetchStatus: FetchStatus.COMPLETE,
|
||||||
result: 0,
|
result: 0,
|
||||||
|
@ -222,7 +229,9 @@ describe('test getDataStateContainer', () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
expect(dataState.data$.main$.value.fetchStatus).toBe(FetchStatus.COMPLETE);
|
||||||
});
|
});
|
||||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
expect(
|
||||||
|
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||||
|
).toEqual({
|
||||||
columns: false,
|
columns: false,
|
||||||
rowHeight: false,
|
rowHeight: false,
|
||||||
breakdownField: false,
|
breakdownField: false,
|
||||||
|
|
|
@ -28,8 +28,8 @@ import { validateTimeRange } from './utils/validate_time_range';
|
||||||
import { fetchAll, fetchMoreDocuments } from '../data_fetching/fetch_all';
|
import { fetchAll, fetchMoreDocuments } from '../data_fetching/fetch_all';
|
||||||
import { sendResetMsg } from '../hooks/use_saved_search_messages';
|
import { sendResetMsg } from '../hooks/use_saved_search_messages';
|
||||||
import { getFetch$ } from '../data_fetching/get_fetch_observable';
|
import { getFetch$ } from '../data_fetching/get_fetch_observable';
|
||||||
import type { DiscoverInternalStateContainer } from './discover_internal_state_container';
|
|
||||||
import { getDefaultProfileState } from './utils/get_default_profile_state';
|
import { getDefaultProfileState } from './utils/get_default_profile_state';
|
||||||
|
import { internalStateActions, InternalStateStore, RuntimeStateManager } from './redux';
|
||||||
|
|
||||||
export interface SavedSearchData {
|
export interface SavedSearchData {
|
||||||
main$: DataMain$;
|
main$: DataMain$;
|
||||||
|
@ -138,14 +138,16 @@ export function getDataStateContainer({
|
||||||
services,
|
services,
|
||||||
searchSessionManager,
|
searchSessionManager,
|
||||||
appStateContainer,
|
appStateContainer,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
getSavedSearch,
|
getSavedSearch,
|
||||||
setDataView,
|
setDataView,
|
||||||
}: {
|
}: {
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
searchSessionManager: DiscoverSearchSessionManager;
|
searchSessionManager: DiscoverSearchSessionManager;
|
||||||
appStateContainer: DiscoverAppStateContainer;
|
appStateContainer: DiscoverAppStateContainer;
|
||||||
internalStateContainer: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
getSavedSearch: () => SavedSearch;
|
getSavedSearch: () => SavedSearch;
|
||||||
setDataView: (dataView: DataView) => void;
|
setDataView: (dataView: DataView) => void;
|
||||||
}): DiscoverDataStateContainer {
|
}): DiscoverDataStateContainer {
|
||||||
|
@ -229,7 +231,7 @@ export function getDataStateContainer({
|
||||||
searchSessionId,
|
searchSessionId,
|
||||||
services,
|
services,
|
||||||
getAppState: appStateContainer.getState,
|
getAppState: appStateContainer.getState,
|
||||||
getInternalState: internalStateContainer.getState,
|
internalState,
|
||||||
savedSearch: getSavedSearch(),
|
savedSearch: getSavedSearch(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -254,10 +256,12 @@ export function getDataStateContainer({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
internalStateContainer.transitions.setDataRequestParams({
|
internalState.dispatch(
|
||||||
timeRangeAbsolute: timefilter.getAbsoluteTime(),
|
internalStateActions.setDataRequestParams({
|
||||||
timeRangeRelative: timefilter.getTime(),
|
timeRangeAbsolute: timefilter.getAbsoluteTime(),
|
||||||
});
|
timeRangeRelative: timefilter.getTime(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
await profilesManager.resolveDataSourceProfile({
|
await profilesManager.resolveDataSourceProfile({
|
||||||
dataSource: appStateContainer.getState().dataSource,
|
dataSource: appStateContainer.getState().dataSource,
|
||||||
|
@ -265,7 +269,8 @@ export function getDataStateContainer({
|
||||||
query: appStateContainer.getState().query,
|
query: appStateContainer.getState().query,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { resetDefaultProfileState, dataView } = internalStateContainer.getState();
|
const { resetDefaultProfileState } = internalState.getState();
|
||||||
|
const dataView = runtimeStateManager.currentDataView$.getValue();
|
||||||
const defaultProfileState = dataView
|
const defaultProfileState = dataView
|
||||||
? getDefaultProfileState({ profilesManager, resetDefaultProfileState, dataView })
|
? getDefaultProfileState({ profilesManager, resetDefaultProfileState, dataView })
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@ -294,7 +299,7 @@ export function getDataStateContainer({
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
const { resetDefaultProfileState: currentResetDefaultProfileState } =
|
const { resetDefaultProfileState: currentResetDefaultProfileState } =
|
||||||
internalStateContainer.getState();
|
internalState.getState();
|
||||||
|
|
||||||
if (currentResetDefaultProfileState.resetId !== resetDefaultProfileState.resetId) {
|
if (currentResetDefaultProfileState.resetId !== resetDefaultProfileState.resetId) {
|
||||||
return;
|
return;
|
||||||
|
@ -313,11 +318,13 @@ export function getDataStateContainer({
|
||||||
|
|
||||||
// Clear the default profile state flags after the data fetching
|
// Clear the default profile state flags after the data fetching
|
||||||
// is done so refetches don't reset the state again
|
// is done so refetches don't reset the state again
|
||||||
internalStateContainer.transitions.setResetDefaultProfileState({
|
internalState.dispatch(
|
||||||
columns: false,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: false,
|
columns: false,
|
||||||
breakdownField: false,
|
rowHeight: false,
|
||||||
});
|
breakdownField: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,228 +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 { v4 as uuidv4 } from 'uuid';
|
|
||||||
import {
|
|
||||||
createStateContainer,
|
|
||||||
createStateContainerReactHelpers,
|
|
||||||
ReduxLikeStateContainer,
|
|
||||||
} from '@kbn/kibana-utils-plugin/common';
|
|
||||||
import type { DataView, DataViewListItem } from '@kbn/data-views-plugin/common';
|
|
||||||
import type { Filter, TimeRange } from '@kbn/es-query';
|
|
||||||
import type { DataTableRecord } from '@kbn/discover-utils/types';
|
|
||||||
import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram-plugin/public';
|
|
||||||
import { differenceBy } from 'lodash';
|
|
||||||
|
|
||||||
interface InternalStateDataRequestParams {
|
|
||||||
timeRangeAbsolute?: TimeRange;
|
|
||||||
timeRangeRelative?: TimeRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InternalState {
|
|
||||||
dataView: DataView | undefined;
|
|
||||||
isDataViewLoading: boolean;
|
|
||||||
savedDataViews: DataViewListItem[];
|
|
||||||
adHocDataViews: DataView[];
|
|
||||||
defaultProfileAdHocDataViewIds: string[];
|
|
||||||
expandedDoc: DataTableRecord | undefined;
|
|
||||||
customFilters: Filter[];
|
|
||||||
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined; // it will be used during saved search saving
|
|
||||||
isESQLToDataViewTransitionModalVisible?: boolean;
|
|
||||||
resetDefaultProfileState: {
|
|
||||||
resetId: string;
|
|
||||||
columns: boolean;
|
|
||||||
rowHeight: boolean;
|
|
||||||
breakdownField: boolean;
|
|
||||||
};
|
|
||||||
dataRequestParams: InternalStateDataRequestParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InternalStateTransitions {
|
|
||||||
setDataView: (state: InternalState) => (dataView: DataView) => InternalState;
|
|
||||||
setIsDataViewLoading: (state: InternalState) => (isLoading: boolean) => InternalState;
|
|
||||||
setSavedDataViews: (state: InternalState) => (dataView: DataViewListItem[]) => InternalState;
|
|
||||||
setAdHocDataViews: (state: InternalState) => (dataViews: DataView[]) => InternalState;
|
|
||||||
setDefaultProfileAdHocDataViews: (
|
|
||||||
state: InternalState
|
|
||||||
) => (dataViews: DataView[]) => InternalState;
|
|
||||||
appendAdHocDataViews: (
|
|
||||||
state: InternalState
|
|
||||||
) => (dataViews: DataView | DataView[]) => InternalState;
|
|
||||||
replaceAdHocDataViewWithId: (
|
|
||||||
state: InternalState
|
|
||||||
) => (id: string, dataView: DataView) => InternalState;
|
|
||||||
setExpandedDoc: (
|
|
||||||
state: InternalState
|
|
||||||
) => (dataView: DataTableRecord | undefined) => InternalState;
|
|
||||||
setCustomFilters: (state: InternalState) => (customFilters: Filter[]) => InternalState;
|
|
||||||
setOverriddenVisContextAfterInvalidation: (
|
|
||||||
state: InternalState
|
|
||||||
) => (
|
|
||||||
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined
|
|
||||||
) => InternalState;
|
|
||||||
resetOnSavedSearchChange: (state: InternalState) => () => InternalState;
|
|
||||||
setIsESQLToDataViewTransitionModalVisible: (
|
|
||||||
state: InternalState
|
|
||||||
) => (isVisible: boolean) => InternalState;
|
|
||||||
setResetDefaultProfileState: (
|
|
||||||
state: InternalState
|
|
||||||
) => (
|
|
||||||
resetDefaultProfileState: Omit<InternalState['resetDefaultProfileState'], 'resetId'>
|
|
||||||
) => InternalState;
|
|
||||||
setDataRequestParams: (
|
|
||||||
state: InternalState
|
|
||||||
) => (params: InternalStateDataRequestParams) => InternalState;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DiscoverInternalStateContainer = ReduxLikeStateContainer<
|
|
||||||
InternalState,
|
|
||||||
InternalStateTransitions
|
|
||||||
>;
|
|
||||||
|
|
||||||
export const { Provider: InternalStateProvider, useSelector: useInternalStateSelector } =
|
|
||||||
createStateContainerReactHelpers<ReduxLikeStateContainer<InternalState>>();
|
|
||||||
|
|
||||||
export function getInternalStateContainer() {
|
|
||||||
return createStateContainer<InternalState, InternalStateTransitions, {}>(
|
|
||||||
{
|
|
||||||
dataView: undefined,
|
|
||||||
isDataViewLoading: false,
|
|
||||||
adHocDataViews: [],
|
|
||||||
defaultProfileAdHocDataViewIds: [],
|
|
||||||
savedDataViews: [],
|
|
||||||
expandedDoc: undefined,
|
|
||||||
customFilters: [],
|
|
||||||
overriddenVisContextAfterInvalidation: undefined,
|
|
||||||
resetDefaultProfileState: {
|
|
||||||
resetId: '',
|
|
||||||
columns: false,
|
|
||||||
rowHeight: false,
|
|
||||||
breakdownField: false,
|
|
||||||
},
|
|
||||||
dataRequestParams: {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
setDataView: (prevState: InternalState) => (nextDataView: DataView) => ({
|
|
||||||
...prevState,
|
|
||||||
dataView: nextDataView,
|
|
||||||
expandedDoc:
|
|
||||||
nextDataView?.id !== prevState.dataView?.id ? undefined : prevState.expandedDoc,
|
|
||||||
}),
|
|
||||||
setIsDataViewLoading: (prevState: InternalState) => (loading: boolean) => ({
|
|
||||||
...prevState,
|
|
||||||
isDataViewLoading: loading,
|
|
||||||
}),
|
|
||||||
setIsESQLToDataViewTransitionModalVisible:
|
|
||||||
(prevState: InternalState) => (isVisible: boolean) => ({
|
|
||||||
...prevState,
|
|
||||||
isESQLToDataViewTransitionModalVisible: isVisible,
|
|
||||||
}),
|
|
||||||
setSavedDataViews: (prevState: InternalState) => (nextDataViewList: DataViewListItem[]) => ({
|
|
||||||
...prevState,
|
|
||||||
savedDataViews: nextDataViewList,
|
|
||||||
}),
|
|
||||||
setAdHocDataViews: (prevState: InternalState) => (newAdHocDataViewList: DataView[]) => ({
|
|
||||||
...prevState,
|
|
||||||
adHocDataViews: newAdHocDataViewList,
|
|
||||||
}),
|
|
||||||
setDefaultProfileAdHocDataViews:
|
|
||||||
(prevState: InternalState) => (defaultProfileAdHocDataViews: DataView[]) => {
|
|
||||||
const adHocDataViews = prevState.adHocDataViews
|
|
||||||
.filter((dataView) => !prevState.defaultProfileAdHocDataViewIds.includes(dataView.id!))
|
|
||||||
.concat(defaultProfileAdHocDataViews);
|
|
||||||
|
|
||||||
const defaultProfileAdHocDataViewIds = defaultProfileAdHocDataViews.map(
|
|
||||||
(dataView) => dataView.id!
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
adHocDataViews,
|
|
||||||
defaultProfileAdHocDataViewIds,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
appendAdHocDataViews:
|
|
||||||
(prevState: InternalState) => (dataViewsAdHoc: DataView | DataView[]) => {
|
|
||||||
const newDataViews = Array.isArray(dataViewsAdHoc) ? dataViewsAdHoc : [dataViewsAdHoc];
|
|
||||||
const existingDataViews = differenceBy(prevState.adHocDataViews, newDataViews, 'id');
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
adHocDataViews: existingDataViews.concat(newDataViews),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
replaceAdHocDataViewWithId:
|
|
||||||
(prevState: InternalState) => (prevId: string, newDataView: DataView) => {
|
|
||||||
let defaultProfileAdHocDataViewIds = prevState.defaultProfileAdHocDataViewIds;
|
|
||||||
|
|
||||||
if (defaultProfileAdHocDataViewIds.includes(prevId)) {
|
|
||||||
defaultProfileAdHocDataViewIds = defaultProfileAdHocDataViewIds.map((id) =>
|
|
||||||
id === prevId ? newDataView.id! : id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...prevState,
|
|
||||||
adHocDataViews: prevState.adHocDataViews.map((dataView) =>
|
|
||||||
dataView.id === prevId ? newDataView : dataView
|
|
||||||
),
|
|
||||||
defaultProfileAdHocDataViewIds,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setExpandedDoc: (prevState: InternalState) => (expandedDoc: DataTableRecord | undefined) => ({
|
|
||||||
...prevState,
|
|
||||||
expandedDoc,
|
|
||||||
}),
|
|
||||||
setCustomFilters: (prevState: InternalState) => (customFilters: Filter[]) => ({
|
|
||||||
...prevState,
|
|
||||||
customFilters,
|
|
||||||
}),
|
|
||||||
setOverriddenVisContextAfterInvalidation:
|
|
||||||
(prevState: InternalState) =>
|
|
||||||
(overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined) => ({
|
|
||||||
...prevState,
|
|
||||||
overriddenVisContextAfterInvalidation,
|
|
||||||
}),
|
|
||||||
resetOnSavedSearchChange: (prevState: InternalState) => () => ({
|
|
||||||
...prevState,
|
|
||||||
overriddenVisContextAfterInvalidation: undefined,
|
|
||||||
expandedDoc: undefined,
|
|
||||||
}),
|
|
||||||
setDataRequestParams:
|
|
||||||
(prevState: InternalState) => (params: InternalStateDataRequestParams) => ({
|
|
||||||
...prevState,
|
|
||||||
dataRequestParams: params,
|
|
||||||
}),
|
|
||||||
setResetDefaultProfileState:
|
|
||||||
(prevState: InternalState) =>
|
|
||||||
(resetDefaultProfileState: Omit<InternalState['resetDefaultProfileState'], 'resetId'>) => ({
|
|
||||||
...prevState,
|
|
||||||
resetDefaultProfileState: {
|
|
||||||
...resetDefaultProfileState,
|
|
||||||
resetId: uuidv4(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{},
|
|
||||||
{ freeze: (state) => state }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const selectDataViewsForPicker = ({
|
|
||||||
savedDataViews,
|
|
||||||
adHocDataViews: originalAdHocDataViews,
|
|
||||||
defaultProfileAdHocDataViewIds,
|
|
||||||
}: InternalState) => {
|
|
||||||
const managedDataViews = originalAdHocDataViews.filter(
|
|
||||||
({ id }) => id && defaultProfileAdHocDataViewIds.includes(id)
|
|
||||||
);
|
|
||||||
const adHocDataViews = differenceBy(originalAdHocDataViews, managedDataViews, 'id');
|
|
||||||
|
|
||||||
return { savedDataViews, managedDataViews, adHocDataViews };
|
|
||||||
};
|
|
|
@ -17,20 +17,24 @@ import { getDiscoverGlobalStateContainer } from './discover_global_state_contain
|
||||||
import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import { VIEW_MODE } from '../../../../common/constants';
|
import { VIEW_MODE } from '../../../../common/constants';
|
||||||
import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
|
import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
|
||||||
import { getInternalStateContainer } from './discover_internal_state_container';
|
import { createInternalStateStore, createRuntimeStateManager } from './redux';
|
||||||
|
|
||||||
describe('DiscoverSavedSearchContainer', () => {
|
describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearch = savedSearchMock;
|
const savedSearch = savedSearchMock;
|
||||||
const services = discoverServiceMock;
|
const services = discoverServiceMock;
|
||||||
const globalStateContainer = getDiscoverGlobalStateContainer(createKbnUrlStateStorage());
|
const globalStateContainer = getDiscoverGlobalStateContainer(createKbnUrlStateStorage());
|
||||||
const internalStateContainer = getInternalStateContainer();
|
const internalState = createInternalStateStore({
|
||||||
|
services,
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
|
});
|
||||||
|
|
||||||
describe('getTitle', () => {
|
describe('getTitle', () => {
|
||||||
it('returns undefined for new saved searches', () => {
|
it('returns undefined for new saved searches', () => {
|
||||||
const container = getSavedSearchContainer({
|
const container = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
expect(container.getTitle()).toBe(undefined);
|
expect(container.getTitle()).toBe(undefined);
|
||||||
});
|
});
|
||||||
|
@ -39,7 +43,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const container = getSavedSearchContainer({
|
const container = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
container.set(savedSearch);
|
container.set(savedSearch);
|
||||||
expect(container.getTitle()).toBe(savedSearch.title);
|
expect(container.getTitle()).toBe(savedSearch.title);
|
||||||
|
@ -51,7 +56,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const container = getSavedSearchContainer({
|
const container = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
|
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
|
||||||
const result = container.set(newSavedSearch);
|
const result = container.set(newSavedSearch);
|
||||||
|
@ -68,7 +74,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const container = getSavedSearchContainer({
|
const container = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
|
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
|
||||||
|
|
||||||
|
@ -82,7 +89,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const container = getSavedSearchContainer({
|
const container = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const result = await container.new(dataViewMock);
|
const result = await container.new(dataViewMock);
|
||||||
|
|
||||||
|
@ -99,7 +107,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const container = getSavedSearchContainer({
|
const container = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const result = await container.new(dataViewMock);
|
const result = await container.new(dataViewMock);
|
||||||
expect(result.title).toBeUndefined();
|
expect(result.title).toBeUndefined();
|
||||||
|
@ -119,7 +128,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
await savedSearchContainer.load('the-saved-search-id');
|
await savedSearchContainer.load('the-saved-search-id');
|
||||||
expect(savedSearchContainer.getInitial$().getValue().id).toEqual('the-saved-search-id');
|
expect(savedSearchContainer.getInitial$().getValue().id).toEqual('the-saved-search-id');
|
||||||
|
@ -135,7 +145,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const savedSearchToPersist = {
|
const savedSearchToPersist = {
|
||||||
...savedSearchMockWithTimeField,
|
...savedSearchMockWithTimeField,
|
||||||
|
@ -161,7 +172,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await savedSearchContainer.persist(persistedSavedSearch, saveOptions);
|
const result = await savedSearchContainer.persist(persistedSavedSearch, saveOptions);
|
||||||
|
@ -174,7 +186,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const savedSearchToPersist = {
|
const savedSearchToPersist = {
|
||||||
...savedSearchMockWithTimeField,
|
...savedSearchMockWithTimeField,
|
||||||
|
@ -194,7 +207,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const savedSearchToPersist = {
|
const savedSearchToPersist = {
|
||||||
...savedSearchMockWithTimeField,
|
...savedSearchMockWithTimeField,
|
||||||
|
@ -219,7 +233,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
savedSearchContainer.set(savedSearch);
|
savedSearchContainer.set(savedSearch);
|
||||||
savedSearchContainer.update({ nextState: { hideChart: true } });
|
savedSearchContainer.update({ nextState: { hideChart: true } });
|
||||||
|
@ -241,7 +256,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
savedSearchContainer.set(savedSearch);
|
savedSearchContainer.set(savedSearch);
|
||||||
const updated = savedSearchContainer.update({ nextState: { hideChart: true } });
|
const updated = savedSearchContainer.update({ nextState: { hideChart: true } });
|
||||||
|
@ -257,7 +273,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
|
||||||
|
internalState,
|
||||||
});
|
});
|
||||||
const updated = savedSearchContainer.update({ nextDataView: dataViewMock });
|
const updated = savedSearchContainer.update({ nextDataView: dataViewMock });
|
||||||
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
|
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { DiscoverAppState, isEqualFilters } from './discover_app_state_container
|
||||||
import { DiscoverServices } from '../../../build_services';
|
import { DiscoverServices } from '../../../build_services';
|
||||||
import { getStateDefaults } from './utils/get_state_defaults';
|
import { getStateDefaults } from './utils/get_state_defaults';
|
||||||
import type { DiscoverGlobalStateContainer } from './discover_global_state_container';
|
import type { DiscoverGlobalStateContainer } from './discover_global_state_container';
|
||||||
import type { DiscoverInternalStateContainer } from './discover_internal_state_container';
|
import { InternalStateStore } from './redux';
|
||||||
|
|
||||||
const FILTERS_COMPARE_OPTIONS: FilterCompareOptions = {
|
const FILTERS_COMPARE_OPTIONS: FilterCompareOptions = {
|
||||||
...COMPARE_ALL_OPTIONS,
|
...COMPARE_ALL_OPTIONS,
|
||||||
|
@ -139,11 +139,11 @@ export interface DiscoverSavedSearchContainer {
|
||||||
export function getSavedSearchContainer({
|
export function getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
}: {
|
}: {
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
globalStateContainer: DiscoverGlobalStateContainer;
|
globalStateContainer: DiscoverGlobalStateContainer;
|
||||||
internalStateContainer: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
}): DiscoverSavedSearchContainer {
|
}): DiscoverSavedSearchContainer {
|
||||||
const initialSavedSearch = services.savedSearch.getNew();
|
const initialSavedSearch = services.savedSearch.getNew();
|
||||||
const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch);
|
const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch);
|
||||||
|
@ -183,7 +183,7 @@ export function getSavedSearchContainer({
|
||||||
addLog('[savedSearch] persist', { nextSavedSearch, saveOptions });
|
addLog('[savedSearch] persist', { nextSavedSearch, saveOptions });
|
||||||
|
|
||||||
const dataView = nextSavedSearch.searchSource.getField('index');
|
const dataView = nextSavedSearch.searchSource.getField('index');
|
||||||
const profileDataViewIds = internalStateContainer.getState().defaultProfileAdHocDataViewIds;
|
const profileDataViewIds = internalState.getState().defaultProfileAdHocDataViewIds;
|
||||||
let replacementDataView: DataView | undefined;
|
let replacementDataView: DataView | undefined;
|
||||||
|
|
||||||
// If the Discover session is using a default profile ad hoc data view,
|
// If the Discover session is using a default profile ad hoc data view,
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { copySavedSearch } from './discover_saved_search_container';
|
||||||
import { createKbnUrlStateStorage, IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
import { createKbnUrlStateStorage, IKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
|
||||||
import { mockCustomizationContext } from '../../../customizations/__mocks__/customization_context';
|
import { mockCustomizationContext } from '../../../customizations/__mocks__/customization_context';
|
||||||
import { createDataViewDataSource, createEsqlDataSource } from '../../../../common/data_sources';
|
import { createDataViewDataSource, createEsqlDataSource } from '../../../../common/data_sources';
|
||||||
|
import { createRuntimeStateManager } from './redux';
|
||||||
|
|
||||||
const startSync = (appState: DiscoverAppStateContainer) => {
|
const startSync = (appState: DiscoverAppStateContainer) => {
|
||||||
const { start, stop } = appState.syncState();
|
const { start, stop } = appState.syncState();
|
||||||
|
@ -46,17 +47,22 @@ async function getState(
|
||||||
const nextHistory = createBrowserHistory();
|
const nextHistory = createBrowserHistory();
|
||||||
nextHistory.push(url);
|
nextHistory.push(url);
|
||||||
|
|
||||||
discoverServiceMock.dataViews.create = jest.fn().mockReturnValue({
|
discoverServiceMock.dataViews.create = jest.fn().mockImplementation((spec) => {
|
||||||
...dataViewMock,
|
spec.id = spec.id ?? 'ad-hoc-id';
|
||||||
isPersisted: () => false,
|
spec.title = spec.title ?? 'test';
|
||||||
id: 'ad-hoc-id',
|
return Promise.resolve({
|
||||||
title: 'test',
|
...dataViewMock,
|
||||||
|
isPersisted: () => false,
|
||||||
|
toSpec: () => spec,
|
||||||
|
...spec,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
const runtimeStateManager = createRuntimeStateManager();
|
||||||
const nextState = getDiscoverStateContainer({
|
const nextState = getDiscoverStateContainer({
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
history: nextHistory,
|
history: nextHistory,
|
||||||
customizationContext: mockCustomizationContext,
|
customizationContext: mockCustomizationContext,
|
||||||
|
runtimeStateManager,
|
||||||
});
|
});
|
||||||
nextState.appState.isEmptyURL = jest.fn(() => isEmptyUrl ?? true);
|
nextState.appState.isEmptyURL = jest.fn(() => isEmptyUrl ?? true);
|
||||||
jest.spyOn(nextState.dataState, 'fetch');
|
jest.spyOn(nextState.dataState, 'fetch');
|
||||||
|
@ -77,6 +83,7 @@ async function getState(
|
||||||
return {
|
return {
|
||||||
history: nextHistory,
|
history: nextHistory,
|
||||||
state: nextState,
|
state: nextState,
|
||||||
|
runtimeStateManager,
|
||||||
getCurrentUrl,
|
getCurrentUrl,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -94,6 +101,7 @@ describe('Test discover state', () => {
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
history,
|
history,
|
||||||
customizationContext: mockCustomizationContext,
|
customizationContext: mockCustomizationContext,
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
});
|
});
|
||||||
state.savedSearchState.set(savedSearchMock);
|
state.savedSearchState.set(savedSearchMock);
|
||||||
state.appState.update({}, true);
|
state.appState.update({}, true);
|
||||||
|
@ -192,6 +200,7 @@ describe('Test discover state with overridden state storage', () => {
|
||||||
history,
|
history,
|
||||||
customizationContext: mockCustomizationContext,
|
customizationContext: mockCustomizationContext,
|
||||||
stateStorageContainer: stateStorage,
|
stateStorageContainer: stateStorage,
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
});
|
});
|
||||||
state.savedSearchState.set(savedSearchMock);
|
state.savedSearchState.set(savedSearchMock);
|
||||||
state.appState.update({}, true);
|
state.appState.update({}, true);
|
||||||
|
@ -283,6 +292,7 @@ describe('Test createSearchSessionRestorationDataProvider', () => {
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
history,
|
history,
|
||||||
customizationContext: mockCustomizationContext,
|
customizationContext: mockCustomizationContext,
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
});
|
});
|
||||||
discoverStateContainer.appState.update({
|
discoverStateContainer.appState.update({
|
||||||
dataSource: createDataViewDataSource({
|
dataSource: createDataViewDataSource({
|
||||||
|
@ -419,9 +429,11 @@ describe('Test discover state actions', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setDataView', async () => {
|
test('setDataView', async () => {
|
||||||
const { state } = await getState('');
|
const { state, runtimeStateManager } = await getState('');
|
||||||
|
expect(runtimeStateManager.currentDataView$.getValue()).toBeUndefined();
|
||||||
state.actions.setDataView(dataViewMock);
|
state.actions.setDataView(dataViewMock);
|
||||||
expect(state.internalState.getState().dataView).toBe(dataViewMock);
|
expect(runtimeStateManager.currentDataView$.getValue()).toBe(dataViewMock);
|
||||||
|
expect(state.internalState.getState().dataViewId).toBe(dataViewMock.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fetchData', async () => {
|
test('fetchData', async () => {
|
||||||
|
@ -717,7 +729,7 @@ describe('Test discover state actions', () => {
|
||||||
state.savedSearchState.getCurrent$().getValue().searchSource?.getField('index')?.id
|
state.savedSearchState.getCurrent$().getValue().searchSource?.getField('index')?.id
|
||||||
).toEqual(dataViewSpecMock.id);
|
).toEqual(dataViewSpecMock.id);
|
||||||
expect(state.savedSearchState.getHasChanged$().getValue()).toEqual(false);
|
expect(state.savedSearchState.getHasChanged$().getValue()).toEqual(false);
|
||||||
expect(state.internalState.getState().adHocDataViews.length).toBe(1);
|
expect(state.runtimeStateManager.adHocDataViews$.getValue().length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadSavedSearch resetting query & filters of data service', async () => {
|
test('loadSavedSearch resetting query & filters of data service', async () => {
|
||||||
|
@ -749,7 +761,7 @@ describe('Test discover state actions', () => {
|
||||||
expect(state.appState.getState().dataSource).toEqual(
|
expect(state.appState.getState().dataSource).toEqual(
|
||||||
createDataViewDataSource({ dataViewId: adHocDataViewId! })
|
createDataViewDataSource({ dataViewId: adHocDataViewId! })
|
||||||
);
|
);
|
||||||
expect(state.internalState.getState().adHocDataViews[0].id).toBe(adHocDataViewId);
|
expect(state.runtimeStateManager.adHocDataViews$.getValue()[0].id).toBe(adHocDataViewId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('loadSavedSearch with ES|QL, data view index is not overwritten by URL ', async () => {
|
test('loadSavedSearch with ES|QL, data view index is not overwritten by URL ', async () => {
|
||||||
|
@ -829,7 +841,7 @@ describe('Test discover state actions', () => {
|
||||||
const unsubscribe = state.actions.initializeAndSync();
|
const unsubscribe = state.actions.initializeAndSync();
|
||||||
await state.actions.onDataViewCreated(dataViewComplexMock);
|
await state.actions.onDataViewCreated(dataViewComplexMock);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewComplexMock.id);
|
expect(state.internalState.getState().dataViewId).toBe(dataViewComplexMock.id);
|
||||||
});
|
});
|
||||||
expect(state.appState.getState().dataSource).toEqual(
|
expect(state.appState.getState().dataSource).toEqual(
|
||||||
createDataViewDataSource({ dataViewId: dataViewComplexMock.id! })
|
createDataViewDataSource({ dataViewId: dataViewComplexMock.id! })
|
||||||
|
@ -844,9 +856,14 @@ describe('Test discover state actions', () => {
|
||||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||||
await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id });
|
await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id });
|
||||||
const unsubscribe = state.actions.initializeAndSync();
|
const unsubscribe = state.actions.initializeAndSync();
|
||||||
|
jest
|
||||||
|
.spyOn(discoverServiceMock.dataViews, 'get')
|
||||||
|
.mockImplementationOnce((id) =>
|
||||||
|
id === dataViewAdHoc.id ? Promise.resolve(dataViewAdHoc) : Promise.reject()
|
||||||
|
);
|
||||||
await state.actions.onDataViewCreated(dataViewAdHoc);
|
await state.actions.onDataViewCreated(dataViewAdHoc);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewAdHoc.id);
|
expect(state.internalState.getState().dataViewId).toBe(dataViewAdHoc.id);
|
||||||
});
|
});
|
||||||
expect(state.appState.getState().dataSource).toEqual(
|
expect(state.appState.getState().dataSource).toEqual(
|
||||||
createDataViewDataSource({ dataViewId: dataViewAdHoc.id! })
|
createDataViewDataSource({ dataViewId: dataViewAdHoc.id! })
|
||||||
|
@ -860,15 +877,12 @@ describe('Test discover state actions', () => {
|
||||||
test('onDataViewEdited - persisted data view', async () => {
|
test('onDataViewEdited - persisted data view', async () => {
|
||||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||||
await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id });
|
await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id });
|
||||||
const selectedDataView = state.internalState.getState().dataView;
|
const selectedDataViewId = state.internalState.getState().dataViewId;
|
||||||
await waitFor(() => {
|
expect(selectedDataViewId).toBe(dataViewMock.id);
|
||||||
expect(selectedDataView).toBe(dataViewMock);
|
|
||||||
});
|
|
||||||
const unsubscribe = state.actions.initializeAndSync();
|
const unsubscribe = state.actions.initializeAndSync();
|
||||||
await state.actions.onDataViewEdited(dataViewMock);
|
await state.actions.onDataViewEdited(dataViewMock);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(state.internalState.getState().dataView).not.toBe(selectedDataView);
|
expect(state.internalState.getState().dataViewId).toBe(selectedDataViewId);
|
||||||
});
|
});
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
});
|
});
|
||||||
|
@ -880,7 +894,7 @@ describe('Test discover state actions', () => {
|
||||||
const previousId = dataViewAdHoc.id;
|
const previousId = dataViewAdHoc.id;
|
||||||
await state.actions.onDataViewEdited(dataViewAdHoc);
|
await state.actions.onDataViewEdited(dataViewAdHoc);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(state.internalState.getState().dataView?.id).not.toBe(previousId);
|
expect(state.internalState.getState().dataViewId).not.toBe(previousId);
|
||||||
});
|
});
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
});
|
});
|
||||||
|
@ -916,7 +930,7 @@ describe('Test discover state actions', () => {
|
||||||
expect(state.appState.getState().dataSource).toEqual(
|
expect(state.appState.getState().dataSource).toEqual(
|
||||||
createDataViewDataSource({ dataViewId: 'ad-hoc-id' })
|
createDataViewDataSource({ dataViewId: 'ad-hoc-id' })
|
||||||
);
|
);
|
||||||
expect(state.internalState.getState().adHocDataViews[0].id).toBe('ad-hoc-id');
|
expect(state.runtimeStateManager.adHocDataViews$.getValue()[0].id).toBe('ad-hoc-id');
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -929,7 +943,7 @@ describe('Test discover state actions', () => {
|
||||||
const initialUrlState =
|
const initialUrlState =
|
||||||
'/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),dataSource:(dataViewId:the-data-view-id,type:dataView),interval:auto,sort:!())';
|
'/#?_g=(refreshInterval:(pause:!t,value:1000),time:(from:now-15d,to:now))&_a=(columns:!(default_column),dataSource:(dataViewId:the-data-view-id,type:dataView),interval:auto,sort:!())';
|
||||||
expect(getCurrentUrl()).toBe(initialUrlState);
|
expect(getCurrentUrl()).toBe(initialUrlState);
|
||||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewMock.id!);
|
expect(state.internalState.getState().dataViewId).toBe(dataViewMock.id!);
|
||||||
|
|
||||||
// Change the data view, this should change the URL and trigger a fetch
|
// Change the data view, this should change the URL and trigger a fetch
|
||||||
await state.actions.onChangeDataView(dataViewComplexMock.id!);
|
await state.actions.onChangeDataView(dataViewComplexMock.id!);
|
||||||
|
@ -940,7 +954,7 @@ describe('Test discover state actions', () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(state.dataState.fetch).toHaveBeenCalledTimes(1);
|
expect(state.dataState.fetch).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewComplexMock.id!);
|
expect(state.internalState.getState().dataViewId).toBe(dataViewComplexMock.id!);
|
||||||
|
|
||||||
// Undo all changes to the saved search, this should trigger a fetch, again
|
// Undo all changes to the saved search, this should trigger a fetch, again
|
||||||
await state.actions.undoSavedSearchChanges();
|
await state.actions.undoSavedSearchChanges();
|
||||||
|
@ -949,7 +963,7 @@ describe('Test discover state actions', () => {
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(state.dataState.fetch).toHaveBeenCalledTimes(2);
|
expect(state.dataState.fetch).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewMock.id!);
|
expect(state.internalState.getState().dataViewId).toBe(dataViewMock.id!);
|
||||||
|
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
});
|
});
|
||||||
|
@ -991,6 +1005,7 @@ describe('Test discover state with embedded mode', () => {
|
||||||
...mockCustomizationContext,
|
...mockCustomizationContext,
|
||||||
displayMode: 'embedded',
|
displayMode: 'embedded',
|
||||||
},
|
},
|
||||||
|
runtimeStateManager: createRuntimeStateManager(),
|
||||||
});
|
});
|
||||||
state.savedSearchState.set(savedSearchMock);
|
state.savedSearchState.set(savedSearchMock);
|
||||||
state.appState.update({}, true);
|
state.appState.update({}, true);
|
||||||
|
|
|
@ -47,10 +47,6 @@ import {
|
||||||
DiscoverAppStateContainer,
|
DiscoverAppStateContainer,
|
||||||
getDiscoverAppStateContainer,
|
getDiscoverAppStateContainer,
|
||||||
} from './discover_app_state_container';
|
} from './discover_app_state_container';
|
||||||
import {
|
|
||||||
DiscoverInternalStateContainer,
|
|
||||||
getInternalStateContainer,
|
|
||||||
} from './discover_internal_state_container';
|
|
||||||
import { DiscoverServices } from '../../../build_services';
|
import { DiscoverServices } from '../../../build_services';
|
||||||
import {
|
import {
|
||||||
getDefaultAppState,
|
getDefaultAppState,
|
||||||
|
@ -68,6 +64,12 @@ import {
|
||||||
DataSourceType,
|
DataSourceType,
|
||||||
isDataSourceType,
|
isDataSourceType,
|
||||||
} from '../../../../common/data_sources';
|
} from '../../../../common/data_sources';
|
||||||
|
import {
|
||||||
|
createInternalStateStore,
|
||||||
|
internalStateActions,
|
||||||
|
InternalStateStore,
|
||||||
|
RuntimeStateManager,
|
||||||
|
} from './redux';
|
||||||
|
|
||||||
export interface DiscoverStateContainerParams {
|
export interface DiscoverStateContainerParams {
|
||||||
/**
|
/**
|
||||||
|
@ -90,6 +92,10 @@ export interface DiscoverStateContainerParams {
|
||||||
* a custom url state storage
|
* a custom url state storage
|
||||||
*/
|
*/
|
||||||
stateStorageContainer?: IKbnUrlStateStorage;
|
stateStorageContainer?: IKbnUrlStateStorage;
|
||||||
|
/**
|
||||||
|
* State manager for runtime state that can't be stored in Redux
|
||||||
|
*/
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoadParams {
|
export interface LoadParams {
|
||||||
|
@ -127,7 +133,11 @@ export interface DiscoverStateContainer {
|
||||||
/**
|
/**
|
||||||
* Internal shared state that's used at several places in the UI
|
* Internal shared state that's used at several places in the UI
|
||||||
*/
|
*/
|
||||||
internalState: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
|
/**
|
||||||
|
* State manager for runtime state that can't be stored in Redux
|
||||||
|
*/
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
/**
|
/**
|
||||||
* State of saved search, the saved object of Discover
|
* State of saved search, the saved object of Discover
|
||||||
*/
|
*/
|
||||||
|
@ -242,6 +252,7 @@ export function getDiscoverStateContainer({
|
||||||
services,
|
services,
|
||||||
customizationContext,
|
customizationContext,
|
||||||
stateStorageContainer,
|
stateStorageContainer,
|
||||||
|
runtimeStateManager,
|
||||||
}: DiscoverStateContainerParams): DiscoverStateContainer {
|
}: DiscoverStateContainerParams): DiscoverStateContainer {
|
||||||
const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage');
|
const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage');
|
||||||
const toasts = services.core.notifications.toasts;
|
const toasts = services.core.notifications.toasts;
|
||||||
|
@ -272,9 +283,9 @@ export function getDiscoverStateContainer({
|
||||||
const globalStateContainer = getDiscoverGlobalStateContainer(stateStorage);
|
const globalStateContainer = getDiscoverGlobalStateContainer(stateStorage);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal State Container, state that's not persisted and not part of the URL
|
* Internal state store, state that's not persisted and not part of the URL
|
||||||
*/
|
*/
|
||||||
const internalStateContainer = getInternalStateContainer();
|
const internalState = createInternalStateStore({ services, runtimeStateManager });
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saved Search State Container, the persisted saved object of Discover
|
* Saved Search State Container, the persisted saved object of Discover
|
||||||
|
@ -282,7 +293,7 @@ export function getDiscoverStateContainer({
|
||||||
const savedSearchContainer = getSavedSearchContainer({
|
const savedSearchContainer = getSavedSearchContainer({
|
||||||
services,
|
services,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -290,7 +301,7 @@ export function getDiscoverStateContainer({
|
||||||
*/
|
*/
|
||||||
const appStateContainer = getDiscoverAppStateContainer({
|
const appStateContainer = getDiscoverAppStateContainer({
|
||||||
stateStorage,
|
stateStorage,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
savedSearchContainer,
|
savedSearchContainer,
|
||||||
services,
|
services,
|
||||||
});
|
});
|
||||||
|
@ -308,7 +319,7 @@ export function getDiscoverStateContainer({
|
||||||
};
|
};
|
||||||
|
|
||||||
const setDataView = (dataView: DataView) => {
|
const setDataView = (dataView: DataView) => {
|
||||||
internalStateContainer.transitions.setDataView(dataView);
|
internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||||
pauseAutoRefreshInterval(dataView);
|
pauseAutoRefreshInterval(dataView);
|
||||||
savedSearchContainer.getState().searchSource.setField('index', dataView);
|
savedSearchContainer.getState().searchSource.setField('index', dataView);
|
||||||
};
|
};
|
||||||
|
@ -317,14 +328,15 @@ export function getDiscoverStateContainer({
|
||||||
services,
|
services,
|
||||||
searchSessionManager,
|
searchSessionManager,
|
||||||
appStateContainer,
|
appStateContainer,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
getSavedSearch: savedSearchContainer.getState,
|
getSavedSearch: savedSearchContainer.getState,
|
||||||
setDataView,
|
setDataView,
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadDataViewList = async () => {
|
const loadDataViewList = async () => {
|
||||||
const dataViewList = await services.dataViews.getIdsWithTitle(true);
|
const savedDataViews = await services.dataViews.getIdsWithTitle(true);
|
||||||
internalStateContainer.transitions.setSavedDataViews(dataViewList);
|
internalState.dispatch(internalStateActions.setSavedDataViews(savedDataViews));
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -332,7 +344,7 @@ export function getDiscoverStateContainer({
|
||||||
* This is to prevent duplicate ids messing with our system
|
* This is to prevent duplicate ids messing with our system
|
||||||
*/
|
*/
|
||||||
const updateAdHocDataViewId = async () => {
|
const updateAdHocDataViewId = async () => {
|
||||||
const prevDataView = internalStateContainer.getState().dataView;
|
const prevDataView = runtimeStateManager.currentDataView$.getValue();
|
||||||
if (!prevDataView || prevDataView.isPersisted()) return;
|
if (!prevDataView || prevDataView.isPersisted()) return;
|
||||||
|
|
||||||
const nextDataView = await services.dataViews.create({
|
const nextDataView = await services.dataViews.create({
|
||||||
|
@ -348,7 +360,9 @@ export function getDiscoverStateContainer({
|
||||||
services,
|
services,
|
||||||
});
|
});
|
||||||
|
|
||||||
internalStateContainer.transitions.replaceAdHocDataViewWithId(prevDataView.id!, nextDataView);
|
internalState.dispatch(
|
||||||
|
internalStateActions.replaceAdHocDataViewWithId(prevDataView.id!, nextDataView)
|
||||||
|
);
|
||||||
|
|
||||||
if (isDataSourceType(appStateContainer.get().dataSource, DataSourceType.DataView)) {
|
if (isDataSourceType(appStateContainer.get().dataSource, DataSourceType.DataView)) {
|
||||||
await appStateContainer.replaceUrlState({
|
await appStateContainer.replaceUrlState({
|
||||||
|
@ -413,7 +427,7 @@ export function getDiscoverStateContainer({
|
||||||
|
|
||||||
const onDataViewCreated = async (nextDataView: DataView) => {
|
const onDataViewCreated = async (nextDataView: DataView) => {
|
||||||
if (!nextDataView.isPersisted()) {
|
if (!nextDataView.isPersisted()) {
|
||||||
internalStateContainer.transitions.appendAdHocDataViews(nextDataView);
|
internalState.dispatch(internalStateActions.appendAdHocDataViews(nextDataView));
|
||||||
} else {
|
} else {
|
||||||
await loadDataViewList();
|
await loadDataViewList();
|
||||||
}
|
}
|
||||||
|
@ -440,7 +454,8 @@ export function getDiscoverStateContainer({
|
||||||
return loadSavedSearchFn(params ?? {}, {
|
return loadSavedSearchFn(params ?? {}, {
|
||||||
appStateContainer,
|
appStateContainer,
|
||||||
dataStateContainer,
|
dataStateContainer,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
savedSearchContainer,
|
savedSearchContainer,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
services,
|
services,
|
||||||
|
@ -470,7 +485,8 @@ export function getDiscoverStateContainer({
|
||||||
appState: appStateContainer,
|
appState: appStateContainer,
|
||||||
savedSearchState: savedSearchContainer,
|
savedSearchState: savedSearchContainer,
|
||||||
dataState: dataStateContainer,
|
dataState: dataStateContainer,
|
||||||
internalState: internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
services,
|
services,
|
||||||
setDataView,
|
setDataView,
|
||||||
})
|
})
|
||||||
|
@ -482,7 +498,7 @@ export function getDiscoverStateContainer({
|
||||||
// updates saved search when query or filters change, triggers data fetching
|
// updates saved search when query or filters change, triggers data fetching
|
||||||
const filterUnsubscribe = merge(services.filterManager.getFetches$()).subscribe(() => {
|
const filterUnsubscribe = merge(services.filterManager.getFetches$()).subscribe(() => {
|
||||||
savedSearchContainer.update({
|
savedSearchContainer.update({
|
||||||
nextDataView: internalStateContainer.getState().dataView,
|
nextDataView: runtimeStateManager.currentDataView$.getValue(),
|
||||||
nextState: appStateContainer.getState(),
|
nextState: appStateContainer.getState(),
|
||||||
useFilterAndQueryServices: true,
|
useFilterAndQueryServices: true,
|
||||||
});
|
});
|
||||||
|
@ -521,8 +537,7 @@ export function getDiscoverStateContainer({
|
||||||
if (newDataView.fields.getByName('@timestamp')?.type === 'date') {
|
if (newDataView.fields.getByName('@timestamp')?.type === 'date') {
|
||||||
newDataView.timeFieldName = '@timestamp';
|
newDataView.timeFieldName = '@timestamp';
|
||||||
}
|
}
|
||||||
internalStateContainer.transitions.appendAdHocDataViews(newDataView);
|
internalState.dispatch(internalStateActions.appendAdHocDataViews(newDataView));
|
||||||
|
|
||||||
await onChangeDataView(newDataView);
|
await onChangeDataView(newDataView);
|
||||||
return newDataView;
|
return newDataView;
|
||||||
};
|
};
|
||||||
|
@ -545,10 +560,12 @@ export function getDiscoverStateContainer({
|
||||||
/**
|
/**
|
||||||
* Function e.g. triggered when user changes data view in the sidebar
|
* Function e.g. triggered when user changes data view in the sidebar
|
||||||
*/
|
*/
|
||||||
const onChangeDataView = async (id: string | DataView) => {
|
const onChangeDataView = async (dataViewId: string | DataView) => {
|
||||||
await changeDataView(id, {
|
await changeDataView({
|
||||||
|
dataViewId,
|
||||||
services,
|
services,
|
||||||
internalState: internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
appState: appStateContainer,
|
appState: appStateContainer,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -575,7 +592,7 @@ export function getDiscoverStateContainer({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
internalStateContainer.transitions.resetOnSavedSearchChange();
|
internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||||
await appStateContainer.replaceUrlState(newAppState);
|
await appStateContainer.replaceUrlState(newAppState);
|
||||||
return nextSavedSearch;
|
return nextSavedSearch;
|
||||||
};
|
};
|
||||||
|
@ -606,7 +623,8 @@ export function getDiscoverStateContainer({
|
||||||
return {
|
return {
|
||||||
globalState: globalStateContainer,
|
globalState: globalStateContainer,
|
||||||
appState: appStateContainer,
|
appState: appStateContainer,
|
||||||
internalState: internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
dataState: dataStateContainer,
|
dataState: dataStateContainer,
|
||||||
savedSearchState: savedSearchContainer,
|
savedSearchState: savedSearchContainer,
|
||||||
stateStorage,
|
stateStorage,
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext } from 'react';
|
||||||
import useObservable from 'react-use/lib/useObservable';
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||||
import { InternalStateProvider } from './discover_internal_state_container';
|
|
||||||
import { DiscoverAppStateProvider } from './discover_app_state_container';
|
import { DiscoverAppStateProvider } from './discover_app_state_container';
|
||||||
import { DiscoverStateContainer } from './discover_state';
|
import { DiscoverStateContainer } from './discover_state';
|
||||||
|
import { InternalStateProvider } from './redux';
|
||||||
|
|
||||||
function createStateHelpers() {
|
function createStateHelpers() {
|
||||||
const context = React.createContext<DiscoverStateContainer | null>(null);
|
const context = React.createContext<DiscoverStateContainer | null>(null);
|
||||||
|
@ -63,7 +63,7 @@ export const DiscoverMainProvider = ({
|
||||||
return (
|
return (
|
||||||
<DiscoverStateProvider value={value}>
|
<DiscoverStateProvider value={value}>
|
||||||
<DiscoverAppStateProvider value={value.appState}>
|
<DiscoverAppStateProvider value={value.appState}>
|
||||||
<InternalStateProvider value={value.internalState}>{children}</InternalStateProvider>
|
<InternalStateProvider store={value.internalState}>{children}</InternalStateProvider>
|
||||||
</DiscoverAppStateProvider>
|
</DiscoverAppStateProvider>
|
||||||
</DiscoverStateProvider>
|
</DiscoverStateProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* 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 type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
|
import { differenceBy } from 'lodash';
|
||||||
|
import { internalStateSlice, type InternalStateThunkActionCreator } from '../internal_state';
|
||||||
|
|
||||||
|
export const setDataView: InternalStateThunkActionCreator<[DataView]> =
|
||||||
|
(dataView) =>
|
||||||
|
(dispatch, _, { runtimeStateManager }) => {
|
||||||
|
dispatch(internalStateSlice.actions.setDataViewId(dataView.id));
|
||||||
|
runtimeStateManager.currentDataView$.next(dataView);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setAdHocDataViews: InternalStateThunkActionCreator<[DataView[]]> =
|
||||||
|
(adHocDataViews) =>
|
||||||
|
(_, __, { runtimeStateManager }) => {
|
||||||
|
runtimeStateManager.adHocDataViews$.next(adHocDataViews);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setDefaultProfileAdHocDataViews: InternalStateThunkActionCreator<[DataView[]]> =
|
||||||
|
(defaultProfileAdHocDataViews) =>
|
||||||
|
(dispatch, getState, { runtimeStateManager }) => {
|
||||||
|
const prevAdHocDataViews = runtimeStateManager.adHocDataViews$.getValue();
|
||||||
|
const prevState = getState();
|
||||||
|
|
||||||
|
const adHocDataViews = prevAdHocDataViews
|
||||||
|
.filter((dataView) => !prevState.defaultProfileAdHocDataViewIds.includes(dataView.id!))
|
||||||
|
.concat(defaultProfileAdHocDataViews);
|
||||||
|
|
||||||
|
const defaultProfileAdHocDataViewIds = defaultProfileAdHocDataViews.map(
|
||||||
|
(dataView) => dataView.id!
|
||||||
|
);
|
||||||
|
|
||||||
|
dispatch(setAdHocDataViews(adHocDataViews));
|
||||||
|
dispatch(
|
||||||
|
internalStateSlice.actions.setDefaultProfileAdHocDataViewIds(defaultProfileAdHocDataViewIds)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appendAdHocDataViews: InternalStateThunkActionCreator<[DataView | DataView[]]> =
|
||||||
|
(dataViewsAdHoc) =>
|
||||||
|
(dispatch, _, { runtimeStateManager }) => {
|
||||||
|
const prevAdHocDataViews = runtimeStateManager.adHocDataViews$.getValue();
|
||||||
|
const newDataViews = Array.isArray(dataViewsAdHoc) ? dataViewsAdHoc : [dataViewsAdHoc];
|
||||||
|
const existingDataViews = differenceBy(prevAdHocDataViews, newDataViews, 'id');
|
||||||
|
|
||||||
|
dispatch(setAdHocDataViews(existingDataViews.concat(newDataViews)));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const replaceAdHocDataViewWithId: InternalStateThunkActionCreator<[string, DataView]> =
|
||||||
|
(prevId, newDataView) =>
|
||||||
|
(dispatch, getState, { runtimeStateManager }) => {
|
||||||
|
const prevAdHocDataViews = runtimeStateManager.adHocDataViews$.getValue();
|
||||||
|
let defaultProfileAdHocDataViewIds = getState().defaultProfileAdHocDataViewIds;
|
||||||
|
|
||||||
|
if (defaultProfileAdHocDataViewIds.includes(prevId)) {
|
||||||
|
defaultProfileAdHocDataViewIds = defaultProfileAdHocDataViewIds.map((id) =>
|
||||||
|
id === prevId ? newDataView.id! : id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
setAdHocDataViews(
|
||||||
|
prevAdHocDataViews.map((dataView) => (dataView.id === prevId ? newDataView : dataView))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
internalStateSlice.actions.setDefaultProfileAdHocDataViewIds(defaultProfileAdHocDataViewIds)
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
/*
|
||||||
|
* 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".
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './data_views';
|
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* 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 { differenceBy } from 'lodash';
|
||||||
|
import {
|
||||||
|
type TypedUseSelectorHook,
|
||||||
|
type ReactReduxContextValue,
|
||||||
|
Provider as ReduxProvider,
|
||||||
|
createDispatchHook,
|
||||||
|
createSelectorHook,
|
||||||
|
} from 'react-redux';
|
||||||
|
import React, { type PropsWithChildren, useMemo, createContext } from 'react';
|
||||||
|
import { useAdHocDataViews } from './runtime_state';
|
||||||
|
import type { DiscoverInternalState } from './types';
|
||||||
|
import type { InternalStateDispatch, InternalStateStore } from './internal_state';
|
||||||
|
|
||||||
|
const internalStateContext = createContext<ReactReduxContextValue>(
|
||||||
|
// Recommended approach for versions of Redux prior to v9:
|
||||||
|
// https://github.com/reduxjs/react-redux/issues/1565#issuecomment-867143221
|
||||||
|
null as unknown as ReactReduxContextValue
|
||||||
|
);
|
||||||
|
|
||||||
|
export const InternalStateProvider = ({
|
||||||
|
store,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<{ store: InternalStateStore }>) => (
|
||||||
|
<ReduxProvider store={store} context={internalStateContext}>
|
||||||
|
{children}
|
||||||
|
</ReduxProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useInternalStateDispatch: () => InternalStateDispatch =
|
||||||
|
createDispatchHook(internalStateContext);
|
||||||
|
|
||||||
|
export const useInternalStateSelector: TypedUseSelectorHook<DiscoverInternalState> =
|
||||||
|
createSelectorHook(internalStateContext);
|
||||||
|
|
||||||
|
export const useDataViewsForPicker = () => {
|
||||||
|
const originalAdHocDataViews = useAdHocDataViews();
|
||||||
|
const savedDataViews = useInternalStateSelector((state) => state.savedDataViews);
|
||||||
|
const defaultProfileAdHocDataViewIds = useInternalStateSelector(
|
||||||
|
(state) => state.defaultProfileAdHocDataViewIds
|
||||||
|
);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const managedDataViews = originalAdHocDataViews.filter(
|
||||||
|
({ id }) => id && defaultProfileAdHocDataViewIds.includes(id)
|
||||||
|
);
|
||||||
|
const adHocDataViews = differenceBy(originalAdHocDataViews, managedDataViews, 'id');
|
||||||
|
|
||||||
|
return { savedDataViews, managedDataViews, adHocDataViews };
|
||||||
|
}, [defaultProfileAdHocDataViewIds, originalAdHocDataViews, savedDataViews]);
|
||||||
|
};
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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 { omit } from 'lodash';
|
||||||
|
import { internalStateSlice } from './internal_state';
|
||||||
|
import {
|
||||||
|
appendAdHocDataViews,
|
||||||
|
replaceAdHocDataViewWithId,
|
||||||
|
setAdHocDataViews,
|
||||||
|
setDataView,
|
||||||
|
setDefaultProfileAdHocDataViews,
|
||||||
|
} from './actions';
|
||||||
|
|
||||||
|
export type { DiscoverInternalState, InternalStateDataRequestParams } from './types';
|
||||||
|
|
||||||
|
export { type InternalStateStore, createInternalStateStore } from './internal_state';
|
||||||
|
|
||||||
|
export const internalStateActions = {
|
||||||
|
...omit(internalStateSlice.actions, 'setDataViewId', 'setDefaultProfileAdHocDataViewIds'),
|
||||||
|
setDataView,
|
||||||
|
setAdHocDataViews,
|
||||||
|
setDefaultProfileAdHocDataViews,
|
||||||
|
appendAdHocDataViews,
|
||||||
|
replaceAdHocDataViewWithId,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
InternalStateProvider,
|
||||||
|
useInternalStateDispatch,
|
||||||
|
useInternalStateSelector,
|
||||||
|
useDataViewsForPicker,
|
||||||
|
} from './hooks';
|
||||||
|
|
||||||
|
export {
|
||||||
|
type RuntimeStateManager,
|
||||||
|
createRuntimeStateManager,
|
||||||
|
useRuntimeState,
|
||||||
|
RuntimeStateProvider,
|
||||||
|
useCurrentDataView,
|
||||||
|
useAdHocDataViews,
|
||||||
|
} from './runtime_state';
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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 { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
||||||
|
import { createInternalStateStore, createRuntimeStateManager, internalStateActions } from '.';
|
||||||
|
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||||
|
|
||||||
|
describe('InternalStateStore', () => {
|
||||||
|
it('should set data view', () => {
|
||||||
|
const runtimeStateManager = createRuntimeStateManager();
|
||||||
|
const store = createInternalStateStore({
|
||||||
|
services: createDiscoverServicesMock(),
|
||||||
|
runtimeStateManager,
|
||||||
|
});
|
||||||
|
expect(store.getState().dataViewId).toBeUndefined();
|
||||||
|
expect(runtimeStateManager.currentDataView$.value).toBeUndefined();
|
||||||
|
store.dispatch(internalStateActions.setDataView(dataViewMock));
|
||||||
|
expect(store.getState().dataViewId).toBe(dataViewMock.id);
|
||||||
|
expect(runtimeStateManager.currentDataView$.value).toBe(dataViewMock);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* 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 type { DataViewListItem } from '@kbn/data-views-plugin/public';
|
||||||
|
import type { DataTableRecord } from '@kbn/discover-utils';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import {
|
||||||
|
type PayloadAction,
|
||||||
|
configureStore,
|
||||||
|
createSlice,
|
||||||
|
type ThunkAction,
|
||||||
|
type ThunkDispatch,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
import type { DiscoverServices } from '../../../../build_services';
|
||||||
|
import type { RuntimeStateManager } from './runtime_state';
|
||||||
|
import type { DiscoverInternalState, InternalStateDataRequestParams } from './types';
|
||||||
|
|
||||||
|
const initialState: DiscoverInternalState = {
|
||||||
|
dataViewId: undefined,
|
||||||
|
isDataViewLoading: false,
|
||||||
|
defaultProfileAdHocDataViewIds: [],
|
||||||
|
savedDataViews: [],
|
||||||
|
expandedDoc: undefined,
|
||||||
|
dataRequestParams: {},
|
||||||
|
overriddenVisContextAfterInvalidation: undefined,
|
||||||
|
isESQLToDataViewTransitionModalVisible: false,
|
||||||
|
resetDefaultProfileState: {
|
||||||
|
resetId: '',
|
||||||
|
columns: false,
|
||||||
|
rowHeight: false,
|
||||||
|
breakdownField: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const internalStateSlice = createSlice({
|
||||||
|
name: 'internalState',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setDataViewId: (state, action: PayloadAction<string | undefined>) => {
|
||||||
|
if (action.payload !== state.dataViewId) {
|
||||||
|
state.expandedDoc = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.dataViewId = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setIsDataViewLoading: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isDataViewLoading = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setDefaultProfileAdHocDataViewIds: (state, action: PayloadAction<string[]>) => {
|
||||||
|
state.defaultProfileAdHocDataViewIds = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setSavedDataViews: (state, action: PayloadAction<DataViewListItem[]>) => {
|
||||||
|
state.savedDataViews = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setExpandedDoc: (state, action: PayloadAction<DataTableRecord | undefined>) => {
|
||||||
|
state.expandedDoc = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setDataRequestParams: (state, action: PayloadAction<InternalStateDataRequestParams>) => {
|
||||||
|
state.dataRequestParams = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setOverriddenVisContextAfterInvalidation: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<DiscoverInternalState['overriddenVisContextAfterInvalidation']>
|
||||||
|
) => {
|
||||||
|
state.overriddenVisContextAfterInvalidation = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setIsESQLToDataViewTransitionModalVisible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.isESQLToDataViewTransitionModalVisible = action.payload;
|
||||||
|
},
|
||||||
|
|
||||||
|
setResetDefaultProfileState: {
|
||||||
|
prepare: (
|
||||||
|
resetDefaultProfileState: Omit<DiscoverInternalState['resetDefaultProfileState'], 'resetId'>
|
||||||
|
) => ({
|
||||||
|
payload: {
|
||||||
|
...resetDefaultProfileState,
|
||||||
|
resetId: uuidv4(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
reducer: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<DiscoverInternalState['resetDefaultProfileState']>
|
||||||
|
) => {
|
||||||
|
state.resetDefaultProfileState = action.payload;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
resetOnSavedSearchChange: (state) => {
|
||||||
|
state.overriddenVisContextAfterInvalidation = undefined;
|
||||||
|
state.expandedDoc = undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface InternalStateThunkDependencies {
|
||||||
|
services: DiscoverServices;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createInternalStateStore = (options: InternalStateThunkDependencies) =>
|
||||||
|
configureStore({
|
||||||
|
reducer: internalStateSlice.reducer,
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware({ thunk: { extraArgument: options } }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type InternalStateStore = ReturnType<typeof createInternalStateStore>;
|
||||||
|
|
||||||
|
export type InternalStateDispatch = InternalStateStore['dispatch'];
|
||||||
|
|
||||||
|
type InternalStateThunkAction<TReturn = void> = ThunkAction<
|
||||||
|
TReturn,
|
||||||
|
InternalStateDispatch extends ThunkDispatch<infer TState, never, never> ? TState : never,
|
||||||
|
InternalStateDispatch extends ThunkDispatch<never, infer TExtra, never> ? TExtra : never,
|
||||||
|
InternalStateDispatch extends ThunkDispatch<never, never, infer TAction> ? TAction : never
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type InternalStateThunkActionCreator<TArgs extends unknown[] = [], TReturn = void> = (
|
||||||
|
...args: TArgs
|
||||||
|
) => InternalStateThunkAction<TReturn>;
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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 type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
|
import React, { type PropsWithChildren, createContext, useContext, useMemo, useState } from 'react';
|
||||||
|
import useObservable from 'react-use/lib/useObservable';
|
||||||
|
import { BehaviorSubject, skip } from 'rxjs';
|
||||||
|
|
||||||
|
interface DiscoverRuntimeState {
|
||||||
|
currentDataView: DataView;
|
||||||
|
adHocDataViews: DataView[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type RuntimeStateManagerInternal<TNullable extends keyof DiscoverRuntimeState> = {
|
||||||
|
[key in keyof DiscoverRuntimeState as `${key}$`]: BehaviorSubject<
|
||||||
|
key extends TNullable ? DiscoverRuntimeState[key] | undefined : DiscoverRuntimeState[key]
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RuntimeStateManager = RuntimeStateManagerInternal<'currentDataView'>;
|
||||||
|
|
||||||
|
export const createRuntimeStateManager = (): RuntimeStateManager => ({
|
||||||
|
currentDataView$: new BehaviorSubject<DataView | undefined>(undefined),
|
||||||
|
adHocDataViews$: new BehaviorSubject<DataView[]>([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useRuntimeState = <T,>(stateSubject$: BehaviorSubject<T>) => {
|
||||||
|
const [stateObservable$] = useState(() => stateSubject$.pipe(skip(1)));
|
||||||
|
return useObservable(stateObservable$, stateSubject$.getValue());
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtimeStateContext = createContext<DiscoverRuntimeState | undefined>(undefined);
|
||||||
|
|
||||||
|
export const RuntimeStateProvider = ({
|
||||||
|
currentDataView,
|
||||||
|
adHocDataViews,
|
||||||
|
children,
|
||||||
|
}: PropsWithChildren<DiscoverRuntimeState>) => {
|
||||||
|
const runtimeState = useMemo<DiscoverRuntimeState>(
|
||||||
|
() => ({ currentDataView, adHocDataViews }),
|
||||||
|
[adHocDataViews, currentDataView]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<runtimeStateContext.Provider value={runtimeState}>{children}</runtimeStateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const useRuntimeStateContext = () => {
|
||||||
|
const context = useContext(runtimeStateContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useRuntimeStateContext must be used within a RuntimeStateProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCurrentDataView = () => useRuntimeStateContext().currentDataView;
|
||||||
|
export const useAdHocDataViews = () => useRuntimeStateContext().adHocDataViews;
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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 type { DataViewListItem } from '@kbn/data-views-plugin/public';
|
||||||
|
import type { DataTableRecord } from '@kbn/discover-utils';
|
||||||
|
import type { TimeRange } from '@kbn/es-query';
|
||||||
|
import type { UnifiedHistogramVisContext } from '@kbn/unified-histogram-plugin/public';
|
||||||
|
export interface InternalStateDataRequestParams {
|
||||||
|
timeRangeAbsolute?: TimeRange;
|
||||||
|
timeRangeRelative?: TimeRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiscoverInternalState {
|
||||||
|
dataViewId: string | undefined;
|
||||||
|
isDataViewLoading: boolean;
|
||||||
|
savedDataViews: DataViewListItem[];
|
||||||
|
defaultProfileAdHocDataViewIds: string[];
|
||||||
|
expandedDoc: DataTableRecord | undefined;
|
||||||
|
dataRequestParams: InternalStateDataRequestParams;
|
||||||
|
overriddenVisContextAfterInvalidation: UnifiedHistogramVisContext | {} | undefined; // it will be used during saved search saving
|
||||||
|
isESQLToDataViewTransitionModalVisible: boolean;
|
||||||
|
resetDefaultProfileState: {
|
||||||
|
resetId: string;
|
||||||
|
columns: boolean;
|
||||||
|
rowHeight: boolean;
|
||||||
|
breakdownField: boolean;
|
||||||
|
};
|
||||||
|
}
|
|
@ -30,6 +30,7 @@ describe('buildStateSubscribe', () => {
|
||||||
savedSearchState: stateContainer.savedSearchState,
|
savedSearchState: stateContainer.savedSearchState,
|
||||||
dataState: stateContainer.dataState,
|
dataState: stateContainer.dataState,
|
||||||
internalState: stateContainer.internalState,
|
internalState: stateContainer.internalState,
|
||||||
|
runtimeStateManager: stateContainer.runtimeStateManager,
|
||||||
services: discoverServiceMock,
|
services: discoverServiceMock,
|
||||||
setDataView: stateContainer.actions.setDataView,
|
setDataView: stateContainer.actions.setDataView,
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import type { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
import type { InternalStateStore, RuntimeStateManager } from '../redux';
|
||||||
import type { DiscoverServices } from '../../../../build_services';
|
import type { DiscoverServices } from '../../../../build_services';
|
||||||
import type { DiscoverSavedSearchContainer } from '../discover_saved_search_container';
|
import type { DiscoverSavedSearchContainer } from '../discover_saved_search_container';
|
||||||
import type { DiscoverDataStateContainer } from '../discover_data_state_container';
|
import type { DiscoverDataStateContainer } from '../discover_data_state_container';
|
||||||
|
@ -38,13 +38,15 @@ export const buildStateSubscribe =
|
||||||
appState,
|
appState,
|
||||||
dataState,
|
dataState,
|
||||||
internalState,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
savedSearchState,
|
savedSearchState,
|
||||||
services,
|
services,
|
||||||
setDataView,
|
setDataView,
|
||||||
}: {
|
}: {
|
||||||
appState: DiscoverAppStateContainer;
|
appState: DiscoverAppStateContainer;
|
||||||
dataState: DiscoverDataStateContainer;
|
dataState: DiscoverDataStateContainer;
|
||||||
internalState: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
savedSearchState: DiscoverSavedSearchContainer;
|
savedSearchState: DiscoverSavedSearchContainer;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
setDataView: DiscoverStateContainer['actions']['setDataView'];
|
setDataView: DiscoverStateContainer['actions']['setDataView'];
|
||||||
|
@ -106,7 +108,8 @@ export const buildStateSubscribe =
|
||||||
dataViewId,
|
dataViewId,
|
||||||
savedSearch,
|
savedSearch,
|
||||||
isEsqlMode,
|
isEsqlMode,
|
||||||
internalStateContainer: internalState,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
services,
|
services,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -16,72 +16,79 @@ import { savedSearchMock } from '../../../../__mocks__/saved_search';
|
||||||
import { discoverServiceMock } from '../../../../__mocks__/services';
|
import { discoverServiceMock } from '../../../../__mocks__/services';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||||
import { PureTransitionsToTransitions } from '@kbn/kibana-utils-plugin/common/state_containers';
|
|
||||||
import { InternalStateTransitions } from '../discover_internal_state_container';
|
|
||||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||||
|
import { createRuntimeStateManager, internalStateActions } from '../redux';
|
||||||
|
|
||||||
const setupTestParams = (dataView: DataView | undefined) => {
|
const setupTestParams = (dataView: DataView | undefined) => {
|
||||||
const savedSearch = savedSearchMock;
|
const savedSearch = savedSearchMock;
|
||||||
const services = discoverServiceMock;
|
const services = discoverServiceMock;
|
||||||
|
const runtimeStateManager = createRuntimeStateManager();
|
||||||
const discoverState = getDiscoverStateMock({
|
const discoverState = getDiscoverStateMock({ savedSearch, runtimeStateManager });
|
||||||
savedSearch,
|
discoverState.internalState.dispatch(
|
||||||
});
|
internalStateActions.setDataView(savedSearch.searchSource.getField('index')!)
|
||||||
discoverState.internalState.transitions.setDataView(savedSearch.searchSource.getField('index')!);
|
);
|
||||||
services.dataViews.get = jest.fn(() => Promise.resolve(dataView as DataView));
|
services.dataViews.get = jest.fn(() => Promise.resolve(dataView as DataView));
|
||||||
discoverState.appState.update = jest.fn();
|
discoverState.appState.update = jest.fn();
|
||||||
discoverState.internalState.transitions = {
|
|
||||||
setIsDataViewLoading: jest.fn(),
|
|
||||||
setResetDefaultProfileState: jest.fn(),
|
|
||||||
} as unknown as Readonly<PureTransitionsToTransitions<InternalStateTransitions>>;
|
|
||||||
return {
|
return {
|
||||||
services,
|
services,
|
||||||
appState: discoverState.appState,
|
appState: discoverState.appState,
|
||||||
internalState: discoverState.internalState,
|
internalState: discoverState.internalState,
|
||||||
|
runtimeStateManager,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('changeDataView', () => {
|
describe('changeDataView', () => {
|
||||||
it('should set the right app state when a valid data view (which includes the preconfigured default column) to switch to is given', async () => {
|
it('should set the right app state when a valid data view (which includes the preconfigured default column) to switch to is given', async () => {
|
||||||
const params = setupTestParams(dataViewWithDefaultColumnMock);
|
const params = setupTestParams(dataViewWithDefaultColumnMock);
|
||||||
await changeDataView(dataViewWithDefaultColumnMock.id!, params);
|
const promise = changeDataView({ dataViewId: dataViewWithDefaultColumnMock.id!, ...params });
|
||||||
|
expect(params.internalState.getState().isDataViewLoading).toBe(true);
|
||||||
|
await promise;
|
||||||
expect(params.appState.update).toHaveBeenCalledWith({
|
expect(params.appState.update).toHaveBeenCalledWith({
|
||||||
columns: ['default_column'], // default_column would be added as dataViewWithDefaultColumn has it as a mapped field
|
columns: ['default_column'], // default_column would be added as dataViewWithDefaultColumn has it as a mapped field
|
||||||
dataSource: createDataViewDataSource({ dataViewId: 'data-view-with-user-default-column-id' }),
|
dataSource: createDataViewDataSource({ dataViewId: 'data-view-with-user-default-column-id' }),
|
||||||
sort: [['@timestamp', 'desc']],
|
sort: [['@timestamp', 'desc']],
|
||||||
});
|
});
|
||||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
expect(params.internalState.getState().isDataViewLoading).toBe(false);
|
||||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set the right app state when a valid data view to switch to is given', async () => {
|
it('should set the right app state when a valid data view to switch to is given', async () => {
|
||||||
const params = setupTestParams(dataViewComplexMock);
|
const params = setupTestParams(dataViewComplexMock);
|
||||||
await changeDataView(dataViewComplexMock.id!, params);
|
const promise = changeDataView({ dataViewId: dataViewComplexMock.id!, ...params });
|
||||||
|
expect(params.internalState.getState().isDataViewLoading).toBe(true);
|
||||||
|
await promise;
|
||||||
expect(params.appState.update).toHaveBeenCalledWith({
|
expect(params.appState.update).toHaveBeenCalledWith({
|
||||||
columns: [], // default_column would not be added as dataViewComplexMock does not have it as a mapped field
|
columns: [], // default_column would not be added as dataViewComplexMock does not have it as a mapped field
|
||||||
dataSource: createDataViewDataSource({ dataViewId: 'data-view-with-various-field-types-id' }),
|
dataSource: createDataViewDataSource({ dataViewId: 'data-view-with-various-field-types-id' }),
|
||||||
sort: [['data', 'desc']],
|
sort: [['data', 'desc']],
|
||||||
});
|
});
|
||||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
expect(params.internalState.getState().isDataViewLoading).toBe(false);
|
||||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not set the app state when an invalid data view to switch to is given', async () => {
|
it('should not set the app state when an invalid data view to switch to is given', async () => {
|
||||||
const params = setupTestParams(undefined);
|
const params = setupTestParams(undefined);
|
||||||
await changeDataView('data-view-with-various-field-types', params);
|
const promise = changeDataView({ dataViewId: 'data-view-with-various-field-types', ...params });
|
||||||
|
expect(params.internalState.getState().isDataViewLoading).toBe(true);
|
||||||
|
await promise;
|
||||||
expect(params.appState.update).not.toHaveBeenCalled();
|
expect(params.appState.update).not.toHaveBeenCalled();
|
||||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
expect(params.internalState.getState().isDataViewLoading).toBe(false);
|
||||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call setResetDefaultProfileState correctly when switching data view', async () => {
|
it('should call setResetDefaultProfileState correctly when switching data view', async () => {
|
||||||
const params = setupTestParams(dataViewComplexMock);
|
const params = setupTestParams(dataViewComplexMock);
|
||||||
expect(params.internalState.transitions.setResetDefaultProfileState).not.toHaveBeenCalled();
|
expect(params.internalState.getState().resetDefaultProfileState).toEqual(
|
||||||
await changeDataView(dataViewComplexMock.id!, params);
|
expect.objectContaining({
|
||||||
expect(params.internalState.transitions.setResetDefaultProfileState).toHaveBeenCalledWith({
|
columns: false,
|
||||||
columns: true,
|
rowHeight: false,
|
||||||
rowHeight: true,
|
breakdownField: false,
|
||||||
breakdownField: true,
|
})
|
||||||
});
|
);
|
||||||
|
await changeDataView({ dataViewId: dataViewComplexMock.id!, ...params });
|
||||||
|
expect(params.internalState.getState().resetDefaultProfileState).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
columns: true,
|
||||||
|
rowHeight: true,
|
||||||
|
breakdownField: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,38 +14,40 @@ import {
|
||||||
SORT_DEFAULT_ORDER_SETTING,
|
SORT_DEFAULT_ORDER_SETTING,
|
||||||
DEFAULT_COLUMNS_SETTING,
|
DEFAULT_COLUMNS_SETTING,
|
||||||
} from '@kbn/discover-utils';
|
} from '@kbn/discover-utils';
|
||||||
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
|
||||||
import { DiscoverAppStateContainer } from '../discover_app_state_container';
|
import { DiscoverAppStateContainer } from '../discover_app_state_container';
|
||||||
import { addLog } from '../../../../utils/add_log';
|
import { addLog } from '../../../../utils/add_log';
|
||||||
import { DiscoverServices } from '../../../../build_services';
|
import { DiscoverServices } from '../../../../build_services';
|
||||||
import { getDataViewAppState } from './get_switch_data_view_app_state';
|
import { getDataViewAppState } from './get_switch_data_view_app_state';
|
||||||
|
import { internalStateActions, type InternalStateStore, type RuntimeStateManager } from '../redux';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function executed when switching data view in the UI
|
* Function executed when switching data view in the UI
|
||||||
*/
|
*/
|
||||||
export async function changeDataView(
|
export async function changeDataView({
|
||||||
id: string | DataView,
|
dataViewId,
|
||||||
{
|
services,
|
||||||
services,
|
internalState,
|
||||||
internalState,
|
runtimeStateManager,
|
||||||
appState,
|
appState,
|
||||||
}: {
|
}: {
|
||||||
services: DiscoverServices;
|
dataViewId: string | DataView;
|
||||||
internalState: DiscoverInternalStateContainer;
|
services: DiscoverServices;
|
||||||
appState: DiscoverAppStateContainer;
|
internalState: InternalStateStore;
|
||||||
}
|
runtimeStateManager: RuntimeStateManager;
|
||||||
) {
|
appState: DiscoverAppStateContainer;
|
||||||
addLog('[ui] changeDataView', { id });
|
}) {
|
||||||
|
addLog('[ui] changeDataView', { id: dataViewId });
|
||||||
|
|
||||||
const { dataViews, uiSettings } = services;
|
const { dataViews, uiSettings } = services;
|
||||||
const dataView = internalState.getState().dataView;
|
const currentDataView = runtimeStateManager.currentDataView$.getValue();
|
||||||
const state = appState.getState();
|
const state = appState.getState();
|
||||||
let nextDataView: DataView | null = null;
|
let nextDataView: DataView | null = null;
|
||||||
|
|
||||||
internalState.transitions.setIsDataViewLoading(true);
|
internalState.dispatch(internalStateActions.setIsDataViewLoading(true));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nextDataView = typeof id === 'string' ? await dataViews.get(id, false) : id;
|
nextDataView =
|
||||||
|
typeof dataViewId === 'string' ? await dataViews.get(dataViewId, false) : dataViewId;
|
||||||
|
|
||||||
// If nextDataView is an ad hoc data view with no fields, refresh its field list.
|
// If nextDataView is an ad hoc data view with no fields, refresh its field list.
|
||||||
// This can happen when default profile data views are created without fields
|
// This can happen when default profile data views are created without fields
|
||||||
|
@ -57,16 +59,18 @@ export async function changeDataView(
|
||||||
// Swallow the error and keep the current data view
|
// Swallow the error and keep the current data view
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextDataView && dataView) {
|
if (nextDataView && currentDataView) {
|
||||||
// Reset the default profile state if we are switching to a different data view
|
// Reset the default profile state if we are switching to a different data view
|
||||||
internalState.transitions.setResetDefaultProfileState({
|
internalState.dispatch(
|
||||||
columns: true,
|
internalStateActions.setResetDefaultProfileState({
|
||||||
rowHeight: true,
|
columns: true,
|
||||||
breakdownField: true,
|
rowHeight: true,
|
||||||
});
|
breakdownField: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const nextAppState = getDataViewAppState(
|
const nextAppState = getDataViewAppState(
|
||||||
dataView,
|
currentDataView,
|
||||||
nextDataView,
|
nextDataView,
|
||||||
uiSettings.get(DEFAULT_COLUMNS_SETTING, []),
|
uiSettings.get(DEFAULT_COLUMNS_SETTING, []),
|
||||||
state.columns || [],
|
state.columns || [],
|
||||||
|
@ -79,9 +83,9 @@ export async function changeDataView(
|
||||||
appState.update(nextAppState);
|
appState.update(nextAppState);
|
||||||
|
|
||||||
if (internalState.getState().expandedDoc) {
|
if (internalState.getState().expandedDoc) {
|
||||||
internalState.transitions.setExpandedDoc(undefined);
|
internalState.dispatch(internalStateActions.setExpandedDoc(undefined));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internalState.transitions.setIsDataViewLoading(false);
|
internalState.dispatch(internalStateActions.setIsDataViewLoading(false));
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,8 @@ import {
|
||||||
getMergedAccessor,
|
getMergedAccessor,
|
||||||
ProfilesManager,
|
ProfilesManager,
|
||||||
} from '../../../../context_awareness';
|
} from '../../../../context_awareness';
|
||||||
import type { InternalState } from '../discover_internal_state_container';
|
|
||||||
import type { DataDocumentsMsg } from '../discover_data_state_container';
|
import type { DataDocumentsMsg } from '../discover_data_state_container';
|
||||||
|
import type { DiscoverInternalState } from '../redux';
|
||||||
|
|
||||||
export const getDefaultProfileState = ({
|
export const getDefaultProfileState = ({
|
||||||
profilesManager,
|
profilesManager,
|
||||||
|
@ -25,7 +25,7 @@ export const getDefaultProfileState = ({
|
||||||
dataView,
|
dataView,
|
||||||
}: {
|
}: {
|
||||||
profilesManager: ProfilesManager;
|
profilesManager: ProfilesManager;
|
||||||
resetDefaultProfileState: InternalState['resetDefaultProfileState'];
|
resetDefaultProfileState: DiscoverInternalState['resetDefaultProfileState'];
|
||||||
dataView: DataView;
|
dataView: DataView;
|
||||||
}) => {
|
}) => {
|
||||||
const defaultState = getDefaultState(profilesManager, dataView);
|
const defaultState = getDefaultState(profilesManager, dataView);
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { cloneDeep, isEqual } from 'lodash';
|
||||||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||||
import { getEsqlDataView } from './get_esql_data_view';
|
import { getEsqlDataView } from './get_esql_data_view';
|
||||||
import { loadAndResolveDataView } from './resolve_data_view';
|
import { loadAndResolveDataView } from './resolve_data_view';
|
||||||
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
|
||||||
import { DiscoverDataStateContainer } from '../discover_data_state_container';
|
import { DiscoverDataStateContainer } from '../discover_data_state_container';
|
||||||
import { cleanupUrlState } from './cleanup_url_state';
|
import { cleanupUrlState } from './cleanup_url_state';
|
||||||
import { getValidFilters } from '../../../../utils/get_valid_filters';
|
import { getValidFilters } from '../../../../utils/get_valid_filters';
|
||||||
|
@ -27,11 +26,13 @@ import {
|
||||||
import { DiscoverGlobalStateContainer } from '../discover_global_state_container';
|
import { DiscoverGlobalStateContainer } from '../discover_global_state_container';
|
||||||
import { DiscoverServices } from '../../../../build_services';
|
import { DiscoverServices } from '../../../../build_services';
|
||||||
import { DataSourceType, isDataSourceType } from '../../../../../common/data_sources';
|
import { DataSourceType, isDataSourceType } from '../../../../../common/data_sources';
|
||||||
|
import { InternalStateStore, RuntimeStateManager, internalStateActions } from '../redux';
|
||||||
|
|
||||||
interface LoadSavedSearchDeps {
|
interface LoadSavedSearchDeps {
|
||||||
appStateContainer: DiscoverAppStateContainer;
|
appStateContainer: DiscoverAppStateContainer;
|
||||||
dataStateContainer: DiscoverDataStateContainer;
|
dataStateContainer: DiscoverDataStateContainer;
|
||||||
internalStateContainer: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
savedSearchContainer: DiscoverSavedSearchContainer;
|
savedSearchContainer: DiscoverSavedSearchContainer;
|
||||||
globalStateContainer: DiscoverGlobalStateContainer;
|
globalStateContainer: DiscoverGlobalStateContainer;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
|
@ -51,7 +52,8 @@ export const loadSavedSearch = async (
|
||||||
const { savedSearchId, initialAppState } = params ?? {};
|
const { savedSearchId, initialAppState } = params ?? {};
|
||||||
const {
|
const {
|
||||||
appStateContainer,
|
appStateContainer,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
savedSearchContainer,
|
savedSearchContainer,
|
||||||
globalStateContainer,
|
globalStateContainer,
|
||||||
services,
|
services,
|
||||||
|
@ -75,7 +77,8 @@ export const loadSavedSearch = async (
|
||||||
dataViewId,
|
dataViewId,
|
||||||
query: appState?.query,
|
query: appState?.query,
|
||||||
services,
|
services,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -110,7 +113,8 @@ export const loadSavedSearch = async (
|
||||||
query: appState.query,
|
query: appState.query,
|
||||||
savedSearch: nextSavedSearch,
|
savedSearch: nextSavedSearch,
|
||||||
services,
|
services,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
});
|
});
|
||||||
const dataViewDifferentToAppState = stateDataView.id !== savedSearchDataViewId;
|
const dataViewDifferentToAppState = stateDataView.id !== savedSearchDataViewId;
|
||||||
if (
|
if (
|
||||||
|
@ -142,7 +146,7 @@ export const loadSavedSearch = async (
|
||||||
nextSavedSearch = savedSearchContainer.updateWithFilterManagerFilters();
|
nextSavedSearch = savedSearchContainer.updateWithFilterManagerFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
internalStateContainer.transitions.resetOnSavedSearchChange();
|
internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||||
|
|
||||||
return nextSavedSearch;
|
return nextSavedSearch;
|
||||||
};
|
};
|
||||||
|
@ -153,12 +157,12 @@ export const loadSavedSearch = async (
|
||||||
* @param deps
|
* @param deps
|
||||||
*/
|
*/
|
||||||
function updateBySavedSearch(savedSearch: SavedSearch, deps: LoadSavedSearchDeps) {
|
function updateBySavedSearch(savedSearch: SavedSearch, deps: LoadSavedSearchDeps) {
|
||||||
const { dataStateContainer, internalStateContainer, services, setDataView } = deps;
|
const { dataStateContainer, internalState, services, setDataView } = deps;
|
||||||
const savedSearchDataView = savedSearch.searchSource.getField('index')!;
|
const savedSearchDataView = savedSearch.searchSource.getField('index')!;
|
||||||
|
|
||||||
setDataView(savedSearchDataView);
|
setDataView(savedSearchDataView);
|
||||||
if (!savedSearchDataView.isPersisted()) {
|
if (!savedSearchDataView.isPersisted()) {
|
||||||
internalStateContainer.transitions.appendAdHocDataViews(savedSearchDataView);
|
internalState.dispatch(internalStateActions.appendAdHocDataViews(savedSearchDataView));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally notify dataStateContainer, data.query and filterManager about new derived state
|
// Finally notify dataStateContainer, data.query and filterManager about new derived state
|
||||||
|
@ -196,13 +200,15 @@ const getStateDataView = async (
|
||||||
query,
|
query,
|
||||||
savedSearch,
|
savedSearch,
|
||||||
services,
|
services,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
}: {
|
}: {
|
||||||
dataViewId?: string;
|
dataViewId?: string;
|
||||||
query: DiscoverAppState['query'];
|
query: DiscoverAppState['query'];
|
||||||
savedSearch?: SavedSearch;
|
savedSearch?: SavedSearch;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
internalStateContainer: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
const { dataView, dataViewSpec } = params;
|
const { dataView, dataViewSpec } = params;
|
||||||
|
@ -222,7 +228,8 @@ const getStateDataView = async (
|
||||||
savedSearch,
|
savedSearch,
|
||||||
isEsqlMode: isEsqlQuery,
|
isEsqlMode: isEsqlQuery,
|
||||||
services,
|
services,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
return result.dataView;
|
return result.dataView;
|
||||||
|
|
|
@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n';
|
||||||
import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public';
|
import type { DataView, DataViewListItem, DataViewSpec } from '@kbn/data-views-plugin/public';
|
||||||
import type { ToastsStart } from '@kbn/core/public';
|
import type { ToastsStart } from '@kbn/core/public';
|
||||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||||
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
|
||||||
import { DiscoverServices } from '../../../../build_services';
|
import { DiscoverServices } from '../../../../build_services';
|
||||||
|
import { InternalStateStore, RuntimeStateManager } from '../redux';
|
||||||
|
|
||||||
interface DataViewData {
|
interface DataViewData {
|
||||||
/**
|
/**
|
||||||
|
@ -174,18 +174,21 @@ export const loadAndResolveDataView = async ({
|
||||||
dataViewSpec,
|
dataViewSpec,
|
||||||
savedSearch,
|
savedSearch,
|
||||||
isEsqlMode,
|
isEsqlMode,
|
||||||
internalStateContainer,
|
internalState,
|
||||||
|
runtimeStateManager,
|
||||||
services,
|
services,
|
||||||
}: {
|
}: {
|
||||||
dataViewId?: string;
|
dataViewId?: string;
|
||||||
dataViewSpec?: DataViewSpec;
|
dataViewSpec?: DataViewSpec;
|
||||||
savedSearch?: SavedSearch;
|
savedSearch?: SavedSearch;
|
||||||
isEsqlMode?: boolean;
|
isEsqlMode?: boolean;
|
||||||
internalStateContainer: DiscoverInternalStateContainer;
|
internalState: InternalStateStore;
|
||||||
|
runtimeStateManager: RuntimeStateManager;
|
||||||
services: DiscoverServices;
|
services: DiscoverServices;
|
||||||
}) => {
|
}) => {
|
||||||
const { dataViews, toastNotifications } = services;
|
const { dataViews, toastNotifications } = services;
|
||||||
const { adHocDataViews, savedDataViews } = internalStateContainer.getState();
|
const adHocDataViews = runtimeStateManager.adHocDataViews$.getValue();
|
||||||
|
const { savedDataViews } = internalState.getState();
|
||||||
|
|
||||||
// Check ad hoc data views first, unless a data view spec is supplied,
|
// Check ad hoc data views first, unless a data view spec is supplied,
|
||||||
// then attempt to load one if none is found
|
// then attempt to load one if none is found
|
||||||
|
|
|
@ -11,9 +11,11 @@ import { renderHook } from '@testing-library/react';
|
||||||
import { useDefaultAdHocDataViews } from './use_default_ad_hoc_data_views';
|
import { useDefaultAdHocDataViews } from './use_default_ad_hoc_data_views';
|
||||||
import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock';
|
import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock';
|
||||||
import { discoverServiceMock } from '../../__mocks__/services';
|
import { discoverServiceMock } from '../../__mocks__/services';
|
||||||
import { DataView } from '@kbn/data-views-plugin/common';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||||
|
import { internalStateActions } from '../../application/main/state_management/redux';
|
||||||
|
import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||||
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
const renderDefaultAdHocDataViewsHook = ({
|
const renderDefaultAdHocDataViewsHook = ({
|
||||||
rootProfileLoading,
|
rootProfileLoading,
|
||||||
|
@ -23,20 +25,22 @@ const renderDefaultAdHocDataViewsHook = ({
|
||||||
const clearInstanceCache = jest.spyOn(discoverServiceMock.dataViews, 'clearInstanceCache');
|
const clearInstanceCache = jest.spyOn(discoverServiceMock.dataViews, 'clearInstanceCache');
|
||||||
const createDataView = jest
|
const createDataView = jest
|
||||||
.spyOn(discoverServiceMock.dataViews, 'create')
|
.spyOn(discoverServiceMock.dataViews, 'create')
|
||||||
.mockImplementation((spec) => Promise.resolve(spec as unknown as DataView));
|
.mockImplementation((spec) => Promise.resolve(buildDataViewMock(omit(spec, 'fields'))));
|
||||||
const existingAdHocDataVew = { id: '1', title: 'test' } as unknown as DataView;
|
const existingAdHocDataVew = buildDataViewMock({ id: '1', title: 'test' });
|
||||||
const previousSpecs = [
|
const previousDataViews = [
|
||||||
{ id: '2', title: 'tes2' },
|
buildDataViewMock({ id: '2', title: 'tes2' }),
|
||||||
{ id: '3', title: 'test3' },
|
buildDataViewMock({ id: '3', title: 'test3' }),
|
||||||
];
|
];
|
||||||
const newSpecs = [
|
const newDataViews = [
|
||||||
{ id: '4', title: 'test4' },
|
buildDataViewMock({ id: '4', title: 'test4' }),
|
||||||
{ id: '5', title: 'test5' },
|
buildDataViewMock({ id: '5', title: 'test5' }),
|
||||||
];
|
];
|
||||||
const stateContainer = getDiscoverStateMock({});
|
const stateContainer = getDiscoverStateMock({});
|
||||||
stateContainer.internalState.transitions.appendAdHocDataViews(existingAdHocDataVew);
|
stateContainer.internalState.dispatch(
|
||||||
stateContainer.internalState.transitions.setDefaultProfileAdHocDataViews(
|
internalStateActions.appendAdHocDataViews(existingAdHocDataVew)
|
||||||
previousSpecs as unknown as DataView[]
|
);
|
||||||
|
stateContainer.internalState.dispatch(
|
||||||
|
internalStateActions.setDefaultProfileAdHocDataViews(previousDataViews)
|
||||||
);
|
);
|
||||||
const { result, unmount } = renderHook(useDefaultAdHocDataViews, {
|
const { result, unmount } = renderHook(useDefaultAdHocDataViews, {
|
||||||
initialProps: {
|
initialProps: {
|
||||||
|
@ -44,7 +48,11 @@ const renderDefaultAdHocDataViewsHook = ({
|
||||||
rootProfileState: {
|
rootProfileState: {
|
||||||
rootProfileLoading,
|
rootProfileLoading,
|
||||||
AppWrapper: () => null,
|
AppWrapper: () => null,
|
||||||
getDefaultAdHocDataViews: () => newSpecs,
|
getDefaultAdHocDataViews: () =>
|
||||||
|
newDataViews.map((dv) => {
|
||||||
|
const { id, ...restSpec } = dv.toSpec();
|
||||||
|
return { id: id!, ...restSpec };
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wrapper: ({ children }) => (
|
wrapper: ({ children }) => (
|
||||||
|
@ -58,8 +66,8 @@ const renderDefaultAdHocDataViewsHook = ({
|
||||||
createDataView,
|
createDataView,
|
||||||
stateContainer,
|
stateContainer,
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
previousSpecs,
|
previousDataViews,
|
||||||
newSpecs,
|
newDataViews,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,27 +83,26 @@ describe('useDefaultAdHocDataViews', () => {
|
||||||
createDataView,
|
createDataView,
|
||||||
stateContainer,
|
stateContainer,
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
previousSpecs,
|
previousDataViews,
|
||||||
newSpecs,
|
newDataViews,
|
||||||
} = renderDefaultAdHocDataViewsHook({ rootProfileLoading: false });
|
} = renderDefaultAdHocDataViewsHook({ rootProfileLoading: false });
|
||||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||||
expect(createDataView).not.toHaveBeenCalled();
|
expect(createDataView).not.toHaveBeenCalled();
|
||||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
...previousSpecs,
|
...previousDataViews,
|
||||||
]);
|
]);
|
||||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||||
previousSpecs.map((s) => s.id)
|
previousDataViews.map((dv) => dv.id)
|
||||||
);
|
);
|
||||||
await result.current.initializeProfileDataViews();
|
await result.current.initializeProfileDataViews();
|
||||||
expect(clearInstanceCache.mock.calls).toEqual(previousSpecs.map((s) => [s.id]));
|
expect(clearInstanceCache.mock.calls).toEqual(previousDataViews.map((dv) => [dv.id]));
|
||||||
expect(createDataView.mock.calls).toEqual(newSpecs.map((s) => [s, true]));
|
expect(createDataView.mock.calls).toEqual(newDataViews.map((dv) => [dv.toSpec(), true]));
|
||||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
expect(
|
||||||
existingAdHocDataVew,
|
stateContainer.runtimeStateManager.adHocDataViews$.getValue().map((dv) => dv.id)
|
||||||
...newSpecs,
|
).toEqual([existingAdHocDataVew.id, ...newDataViews.map((dv) => dv.id)]);
|
||||||
]);
|
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
newDataViews.map((dv) => dv.id)
|
||||||
newSpecs.map((s) => s.id)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -106,43 +113,45 @@ describe('useDefaultAdHocDataViews', () => {
|
||||||
createDataView,
|
createDataView,
|
||||||
stateContainer,
|
stateContainer,
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
previousSpecs,
|
previousDataViews,
|
||||||
} = renderDefaultAdHocDataViewsHook({ rootProfileLoading: true });
|
} = renderDefaultAdHocDataViewsHook({ rootProfileLoading: true });
|
||||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||||
expect(createDataView).not.toHaveBeenCalled();
|
expect(createDataView).not.toHaveBeenCalled();
|
||||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
...previousSpecs,
|
...previousDataViews,
|
||||||
]);
|
]);
|
||||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||||
previousSpecs.map((s) => s.id)
|
previousDataViews.map((dv) => dv.id)
|
||||||
);
|
);
|
||||||
await result.current.initializeProfileDataViews();
|
await result.current.initializeProfileDataViews();
|
||||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||||
expect(createDataView).not.toHaveBeenCalled();
|
expect(createDataView).not.toHaveBeenCalled();
|
||||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
...previousSpecs,
|
...previousDataViews,
|
||||||
]);
|
]);
|
||||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||||
previousSpecs.map((s) => s.id)
|
previousDataViews.map((dv) => dv.id)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should clear instance cache on unmount', async () => {
|
it('should clear instance cache on unmount', async () => {
|
||||||
const { unmount, clearInstanceCache, stateContainer, existingAdHocDataVew, previousSpecs } =
|
const { unmount, clearInstanceCache, stateContainer, existingAdHocDataVew, previousDataViews } =
|
||||||
renderDefaultAdHocDataViewsHook({ rootProfileLoading: false });
|
renderDefaultAdHocDataViewsHook({ rootProfileLoading: false });
|
||||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||||
existingAdHocDataVew,
|
existingAdHocDataVew,
|
||||||
...previousSpecs,
|
...previousDataViews,
|
||||||
]);
|
]);
|
||||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||||
previousSpecs.map((s) => s.id)
|
previousDataViews.map((dv) => dv.id)
|
||||||
);
|
);
|
||||||
unmount();
|
unmount();
|
||||||
expect(clearInstanceCache.mock.calls).toEqual(previousSpecs.map((s) => [s.id]));
|
expect(clearInstanceCache.mock.calls).toEqual(previousDataViews.map((s) => [s.id]));
|
||||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([existingAdHocDataVew]);
|
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual([]);
|
existingAdHocDataVew,
|
||||||
|
]);
|
||||||
|
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,6 +13,7 @@ import useUnmount from 'react-use/lib/useUnmount';
|
||||||
import type { RootProfileState } from './use_root_profile';
|
import type { RootProfileState } from './use_root_profile';
|
||||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||||
import type { DiscoverStateContainer } from '../../application/main/state_management/discover_state';
|
import type { DiscoverStateContainer } from '../../application/main/state_management/discover_state';
|
||||||
|
import { internalStateActions } from '../../application/main/state_management/redux';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to retrieve and initialize the default profile ad hoc data views
|
* Hook to retrieve and initialize the default profile ad hoc data views
|
||||||
|
@ -36,7 +37,7 @@ export const useDefaultAdHocDataViews = ({
|
||||||
|
|
||||||
// Clear the cache of old data views before creating
|
// Clear the cache of old data views before creating
|
||||||
// the new ones to avoid cache hits on duplicate IDs
|
// the new ones to avoid cache hits on duplicate IDs
|
||||||
for (const prevId of internalState.get().defaultProfileAdHocDataViewIds) {
|
for (const prevId of internalState.getState().defaultProfileAdHocDataViewIds) {
|
||||||
dataViews.clearInstanceCache(prevId);
|
dataViews.clearInstanceCache(prevId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ export const useDefaultAdHocDataViews = ({
|
||||||
profileDataViewSpecs.map((spec) => dataViews.create(spec, true))
|
profileDataViewSpecs.map((spec) => dataViews.create(spec, true))
|
||||||
);
|
);
|
||||||
|
|
||||||
internalState.transitions.setDefaultProfileAdHocDataViews(profileDataViews);
|
internalState.dispatch(internalStateActions.setDefaultProfileAdHocDataViews(profileDataViews));
|
||||||
});
|
});
|
||||||
|
|
||||||
// This approach allows us to return a callback with a stable reference
|
// This approach allows us to return a callback with a stable reference
|
||||||
|
@ -53,11 +54,11 @@ export const useDefaultAdHocDataViews = ({
|
||||||
|
|
||||||
// Make sure to clean up on unmount
|
// Make sure to clean up on unmount
|
||||||
useUnmount(() => {
|
useUnmount(() => {
|
||||||
for (const prevId of internalState.get().defaultProfileAdHocDataViewIds) {
|
for (const prevId of internalState.getState().defaultProfileAdHocDataViewIds) {
|
||||||
dataViews.clearInstanceCache(prevId);
|
dataViews.clearInstanceCache(prevId);
|
||||||
}
|
}
|
||||||
|
|
||||||
internalState.transitions.setDefaultProfileAdHocDataViews([]);
|
internalState.dispatch(internalStateActions.setDefaultProfileAdHocDataViews([]));
|
||||||
});
|
});
|
||||||
|
|
||||||
return { initializeProfileDataViews };
|
return { initializeProfileDataViews };
|
||||||
|
|
|
@ -40,17 +40,14 @@ describe('useDiscoverCustomizationService', () => {
|
||||||
customizationCallbacks: [callback],
|
customizationCallbacks: [callback],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(wrapper.result.current.isInitialized).toBe(false);
|
expect(wrapper.result.current).toBeUndefined();
|
||||||
expect(wrapper.result.current.customizationService).toBeUndefined();
|
|
||||||
expect(callback).toHaveBeenCalledTimes(1);
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
const cleanup = jest.fn();
|
const cleanup = jest.fn();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
resolveCallback(cleanup);
|
resolveCallback(cleanup);
|
||||||
await promise;
|
await promise;
|
||||||
});
|
});
|
||||||
expect(wrapper.result.current.isInitialized).toBe(true);
|
expect(wrapper.result.current).toBe(service);
|
||||||
expect(wrapper.result.current.customizationService).toBeDefined();
|
|
||||||
expect(wrapper.result.current.customizationService).toBe(service);
|
|
||||||
expect(callback).toHaveBeenCalledTimes(1);
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
expect(cleanup).not.toHaveBeenCalled();
|
expect(cleanup).not.toHaveBeenCalled();
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
|
|
|
@ -50,9 +50,7 @@ export const useDiscoverCustomizationService = ({
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const isInitialized = Boolean(customizationService);
|
return customizationService;
|
||||||
|
|
||||||
return { customizationService, isInitialized };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useDiscoverCustomization$ = <TCustomizationId extends DiscoverCustomizationId>(
|
export const useDiscoverCustomization$ = <TCustomizationId extends DiscoverCustomizationId>(
|
||||||
|
|
|
@ -100,7 +100,8 @@
|
||||||
"@kbn/esql-ast",
|
"@kbn/esql-ast",
|
||||||
"@kbn/discover-shared-plugin",
|
"@kbn/discover-shared-plugin",
|
||||||
"@kbn/response-ops-rule-form",
|
"@kbn/response-ops-rule-form",
|
||||||
"@kbn/embeddable-enhanced-plugin"
|
"@kbn/embeddable-enhanced-plugin",
|
||||||
|
"@kbn/shared-ux-page-analytics-no-data-types"
|
||||||
],
|
],
|
||||||
"exclude": ["target/**/*"]
|
"exclude": ["target/**/*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||||
const kibanaServer = getService('kibanaServer');
|
const kibanaServer = getService('kibanaServer');
|
||||||
const testSubjects = getService('testSubjects');
|
const testSubjects = getService('testSubjects');
|
||||||
const browser = getService('browser');
|
const browser = getService('browser');
|
||||||
const dataGrid = getService('dataGrid');
|
|
||||||
const retry = getService('retry');
|
const retry = getService('retry');
|
||||||
const defaultSettings = { defaultIndex: 'logstash-*' };
|
const defaultSettings = { defaultIndex: 'logstash-*' };
|
||||||
|
|
||||||
|
@ -69,33 +68,5 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||||
await browser.goBack();
|
await browser.goBack();
|
||||||
await header.waitUntilLoadingHasFinished();
|
await header.waitUntilLoadingHasFinished();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Search bar Prepend Filters exists and should apply filter properly', async () => {
|
|
||||||
// Validate custom filters are present
|
|
||||||
await testSubjects.existOrFail('customPrependedFilter');
|
|
||||||
await testSubjects.click('customPrependedFilter');
|
|
||||||
await testSubjects.existOrFail('optionsList-control-selection-exists');
|
|
||||||
|
|
||||||
// Retrieve option list popover
|
|
||||||
const optionsListControl = await testSubjects.find('optionsList-control-popover');
|
|
||||||
const optionsItems = await optionsListControl.findAllByCssSelector(
|
|
||||||
'[data-test-subj*="optionsList-control-selection-"]'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Retrieve second item in the options along with the count of documents
|
|
||||||
const item = optionsItems[1];
|
|
||||||
const countBadge = await item.findByCssSelector(
|
|
||||||
'[data-test-subj="optionsList-document-count-badge"]'
|
|
||||||
);
|
|
||||||
const documentsCount = parseInt(await countBadge.getVisibleText(), 10);
|
|
||||||
|
|
||||||
// Click the item to apply filter
|
|
||||||
await item.click();
|
|
||||||
await header.waitUntilLoadingHasFinished();
|
|
||||||
|
|
||||||
// Validate that filter is applied
|
|
||||||
const rows = await dataGrid.getDocTableRows();
|
|
||||||
await expect(documentsCount).to.eql(rows.length);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DiscoverAppState } from '@kbn/discover-plugin/public/application/main/state_management/discover_app_state_container';
|
import type { DiscoverAppState } from '@kbn/discover-plugin/public/application/main/state_management/discover_app_state_container';
|
||||||
import type { InternalState } from '@kbn/discover-plugin/public/application/main/state_management/discover_internal_state_container';
|
import type { DiscoverInternalState } from '@kbn/discover-plugin/public/application/main/state_management/redux';
|
||||||
import type { SavedSearch } from '@kbn/saved-search-plugin/common';
|
import type { SavedSearch } from '@kbn/saved-search-plugin/common';
|
||||||
|
|
||||||
export interface SecuritySolutionDiscoverState {
|
export interface SecuritySolutionDiscoverState {
|
||||||
app: DiscoverAppState | undefined;
|
app: DiscoverAppState | undefined;
|
||||||
internal: InternalState | undefined;
|
internal: DiscoverInternalState | undefined;
|
||||||
savedSearch: SavedSearch | undefined;
|
savedSearch: SavedSearch | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { useHistory } from 'react-router-dom';
|
||||||
import type { CustomizationCallback } from '@kbn/discover-plugin/public/customizations/types';
|
import type { CustomizationCallback } from '@kbn/discover-plugin/public/customizations/types';
|
||||||
import { createGlobalStyle } from 'styled-components';
|
import { createGlobalStyle } from 'styled-components';
|
||||||
import type { ScopedHistory } from '@kbn/core/public';
|
import type { ScopedHistory } from '@kbn/core/public';
|
||||||
import type { Subscription } from 'rxjs';
|
import { from, type Subscription } from 'rxjs';
|
||||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { isEqualWith } from 'lodash';
|
import { isEqualWith } from 'lodash';
|
||||||
|
@ -219,7 +219,7 @@ export const DiscoverTabContent: FC<DiscoverTabContentProps> = ({ timelineId })
|
||||||
next: setDiscoverAppState,
|
next: setDiscoverAppState,
|
||||||
});
|
});
|
||||||
|
|
||||||
const internalStateSubscription = stateContainer.internalState.state$.subscribe({
|
const internalStateSubscription = from(stateContainer.internalState).subscribe({
|
||||||
next: setDiscoverInternalState,
|
next: setDiscoverInternalState,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DiscoverAppState } from '@kbn/discover-plugin/public/application/main/state_management/discover_app_state_container';
|
import type { DiscoverAppState } from '@kbn/discover-plugin/public/application/main/state_management/discover_app_state_container';
|
||||||
import type { InternalState } from '@kbn/discover-plugin/public/application/main/state_management/discover_internal_state_container';
|
import type { DiscoverInternalState } from '@kbn/discover-plugin/public/application/main/state_management/redux';
|
||||||
import type { SavedSearch } from '@kbn/saved-search-plugin/common';
|
import type { SavedSearch } from '@kbn/saved-search-plugin/common';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
@ -22,7 +22,7 @@ export const useDiscoverState = () => {
|
||||||
const result = state.discover.app;
|
const result = state.discover.app;
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
const discoverInternalState = useSelector<State, InternalState | undefined>((state) => {
|
const discoverInternalState = useSelector<State, DiscoverInternalState | undefined>((state) => {
|
||||||
const result = state.discover.internal;
|
const result = state.discover.internal;
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ export const useDiscoverState = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const setDiscoverInternalState = useCallback(
|
const setDiscoverInternalState = useCallback(
|
||||||
(newState: InternalState) => {
|
(newState: DiscoverInternalState) => {
|
||||||
dispatch(updateDiscoverInternalState({ newState }));
|
dispatch(updateDiscoverInternalState({ newState }));
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
|
|
|
@ -17,7 +17,6 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||||
const kibanaServer = getService('kibanaServer');
|
const kibanaServer = getService('kibanaServer');
|
||||||
const testSubjects = getService('testSubjects');
|
const testSubjects = getService('testSubjects');
|
||||||
const browser = getService('browser');
|
const browser = getService('browser');
|
||||||
const dataGrid = getService('dataGrid');
|
|
||||||
const retry = getService('retry');
|
const retry = getService('retry');
|
||||||
const defaultSettings = { defaultIndex: 'logstash-*' };
|
const defaultSettings = { defaultIndex: 'logstash-*' };
|
||||||
|
|
||||||
|
@ -67,33 +66,5 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
||||||
await browser.goBack();
|
await browser.goBack();
|
||||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Search bar Prepend Filters exists and should apply filter properly', async () => {
|
|
||||||
// Validate custom filters are present
|
|
||||||
await testSubjects.existOrFail('customPrependedFilter');
|
|
||||||
await testSubjects.click('customPrependedFilter');
|
|
||||||
await testSubjects.existOrFail('optionsList-control-selection-exists');
|
|
||||||
|
|
||||||
// Retrieve option list popover
|
|
||||||
const optionsListControl = await testSubjects.find('optionsList-control-popover');
|
|
||||||
const optionsItems = await optionsListControl.findAllByCssSelector(
|
|
||||||
'[data-test-subj*="optionsList-control-selection-"]'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Retrieve second item in the options along with the count of documents
|
|
||||||
const item = optionsItems[1];
|
|
||||||
const countBadge = await item.findByCssSelector(
|
|
||||||
'[data-test-subj="optionsList-document-count-badge"]'
|
|
||||||
);
|
|
||||||
const documentsCount = parseInt(await countBadge.getVisibleText(), 10);
|
|
||||||
|
|
||||||
// Click the item to apply filter
|
|
||||||
await item.click();
|
|
||||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
|
||||||
|
|
||||||
// Validate that filter is applied
|
|
||||||
const rows = await dataGrid.getDocTableRows();
|
|
||||||
await expect(documentsCount).to.eql(rows.length);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue