mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -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 dataView = useObservable(
|
||||
stateContainer.internalState.state$,
|
||||
stateContainer.internalState.getState()
|
||||
).dataView;
|
||||
stateContainer.runtimeStateManager.currentDataView$,
|
||||
stateContainer.runtimeStateManager.currentDataView$.getValue()
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!controlGroupAPI) {
|
||||
|
@ -262,7 +262,6 @@ export class DiscoverCustomizationExamplesPlugin implements Plugin {
|
|||
});
|
||||
|
||||
const filterSubscription = controlGroupAPI.filters$.subscribe((newFilters = []) => {
|
||||
stateContainer.internalState.transitions.setCustomFilters(newFilters);
|
||||
stateContainer.actions.fetchData();
|
||||
});
|
||||
|
||||
|
|
|
@ -79,12 +79,16 @@ export const deepMockedFields = shallowMockedFields.map(
|
|||
) as DataView['fields'];
|
||||
|
||||
export const buildDataViewMock = ({
|
||||
name,
|
||||
fields: definedFields,
|
||||
id,
|
||||
title,
|
||||
name = 'data-view-mock',
|
||||
fields: definedFields = [] as unknown as DataView['fields'],
|
||||
timeFieldName,
|
||||
}: {
|
||||
name: string;
|
||||
fields: DataView['fields'];
|
||||
id?: string;
|
||||
title?: string;
|
||||
name?: string;
|
||||
fields?: DataView['fields'];
|
||||
timeFieldName?: string;
|
||||
}): DataView => {
|
||||
const dataViewFields = [...definedFields] as DataView['fields'];
|
||||
|
@ -105,9 +109,12 @@ export const buildDataViewMock = ({
|
|||
return new DataViewField(spec);
|
||||
};
|
||||
|
||||
id = id ?? `${name}-id`;
|
||||
title = title ?? `${name}-title`;
|
||||
|
||||
const dataView = {
|
||||
id: `${name}-id`,
|
||||
title: `${name}-title`,
|
||||
id,
|
||||
title,
|
||||
name,
|
||||
metaFields: ['_index', '_score'],
|
||||
fields: dataViewFields,
|
||||
|
@ -122,7 +129,7 @@ export const buildDataViewMock = ({
|
|||
getFormatterForField: jest.fn(() => ({ convert: (value: unknown) => value })),
|
||||
isTimeNanosBased: () => false,
|
||||
isPersisted: () => true,
|
||||
toSpec: () => ({}),
|
||||
toSpec: () => ({ id, title, name }),
|
||||
toMinimalSpec: () => ({}),
|
||||
getTimeField: () => {
|
||||
return dataViewFields.find((field) => field.name === timeFieldName);
|
||||
|
|
|
@ -13,13 +13,19 @@ import { savedSearchMockWithTimeField, savedSearchMock } from './saved_search';
|
|||
import { discoverServiceMock } from './services';
|
||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import { mockCustomizationContext } from '../customizations/__mocks__/customization_context';
|
||||
import {
|
||||
RuntimeStateManager,
|
||||
createRuntimeStateManager,
|
||||
} from '../application/main/state_management/redux';
|
||||
|
||||
export function getDiscoverStateMock({
|
||||
isTimeBased = true,
|
||||
savedSearch,
|
||||
runtimeStateManager,
|
||||
}: {
|
||||
isTimeBased?: boolean;
|
||||
savedSearch?: SavedSearch;
|
||||
runtimeStateManager?: RuntimeStateManager;
|
||||
}) {
|
||||
const history = createBrowserHistory();
|
||||
history.push('/');
|
||||
|
@ -27,6 +33,7 @@ export function getDiscoverStateMock({
|
|||
services: discoverServiceMock,
|
||||
history,
|
||||
customizationContext: mockCustomizationContext,
|
||||
runtimeStateManager: runtimeStateManager ?? createRuntimeStateManager(),
|
||||
});
|
||||
container.savedSearchState.set(
|
||||
savedSearch ? savedSearch : isTimeBased ? savedSearchMockWithTimeField : savedSearchMock
|
||||
|
|
|
@ -28,6 +28,7 @@ import { DiscoverCustomization, DiscoverCustomizationProvider } from '../../../.
|
|||
import { createCustomizationService } from '../../../../customizations/customization_service';
|
||||
import { DiscoverGrid } from '../../../../components/discover_grid';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { internalStateActions } from '../../state_management/redux';
|
||||
|
||||
const customisationService = createCustomizationService();
|
||||
|
||||
|
@ -46,16 +47,18 @@ async function mountComponent(fetchStatus: FetchStatus, hits: EsHitRecord[]) {
|
|||
stateContainer.appState.update({
|
||||
dataSource: createDataViewDataSource({ dataViewId: dataViewMock.id! }),
|
||||
});
|
||||
stateContainer.internalState.transitions.setDataRequestParams({
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
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 { DiscoverGrid } from '../../../../components/discover_grid';
|
||||
import { getDefaultRowsPerPage } from '../../../../../common/constants';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
import { useAppStateSelector } from '../../state_management/discover_app_state_container';
|
||||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
import { FetchStatus } from '../../../types';
|
||||
|
@ -73,6 +72,11 @@ import {
|
|||
useAdditionalCellActions,
|
||||
useProfileAccessor,
|
||||
} from '../../../../context_awareness';
|
||||
import {
|
||||
internalStateActions,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
|
||||
const containerStyles = css`
|
||||
position: relative;
|
||||
|
@ -108,6 +112,7 @@ function DiscoverDocumentsComponent({
|
|||
onFieldEdited?: () => void;
|
||||
}) {
|
||||
const services = useDiscoverServices();
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const { dataViews, capabilities, uiSettings, uiActions, ebtManager, fieldsMetadata } = services;
|
||||
|
@ -204,9 +209,9 @@ function DiscoverDocumentsComponent({
|
|||
|
||||
const setExpandedDoc = useCallback(
|
||||
(doc: DataTableRecord | undefined) => {
|
||||
stateContainer.internalState.transitions.setExpandedDoc(doc);
|
||||
dispatch(internalStateActions.setExpandedDoc(doc));
|
||||
},
|
||||
[stateContainer]
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
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 { PanelsToggle } from '../../../../components/panels_toggle';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
|
||||
function getStateContainer(savedSearch?: SavedSearch) {
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true, savedSearch });
|
||||
|
@ -46,17 +47,19 @@ function getStateContainer(savedSearch?: SavedSearch) {
|
|||
|
||||
stateContainer.appState.update(appState);
|
||||
|
||||
stateContainer.internalState.transitions.setDataView(dataView);
|
||||
stateContainer.internalState.transitions.setDataRequestParams({
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
});
|
||||
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeAbsolute: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
timeRangeRelative: {
|
||||
from: '2020-05-14T11:05:13.590',
|
||||
to: '2020-05-14T11:20:13.590',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return stateContainer;
|
||||
}
|
||||
|
@ -142,7 +145,9 @@ const mountComponent = async ({
|
|||
<KibanaRenderContextProvider {...services.core}>
|
||||
<KibanaContextProvider services={services}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<DiscoverHistogramLayout {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||
<DiscoverHistogramLayout {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaRenderContextProvider>
|
||||
|
|
|
@ -40,6 +40,7 @@ import { act } from 'react-dom/test-utils';
|
|||
import { ErrorCallout } from '../../../../components/common/error_callout';
|
||||
import { PanelsToggle } from '../../../../components/panels_toggle';
|
||||
import { createDataViewDataSource } from '../../../../../common/data_sources';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
|
||||
jest.mock('@elastic/eui', () => ({
|
||||
...jest.requireActual('@elastic/eui'),
|
||||
|
@ -104,11 +105,10 @@ async function mountComponent(
|
|||
interval: 'auto',
|
||||
query,
|
||||
});
|
||||
stateContainer.internalState.transitions.setDataView(dataView);
|
||||
stateContainer.internalState.transitions.setDataRequestParams({
|
||||
timeRangeAbsolute: time,
|
||||
timeRangeRelative: time,
|
||||
});
|
||||
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({ timeRangeAbsolute: time, timeRangeRelative: time })
|
||||
);
|
||||
|
||||
const props = {
|
||||
dataView,
|
||||
|
@ -128,9 +128,11 @@ async function mountComponent(
|
|||
const component = mountWithIntl(
|
||||
<KibanaContextProvider services={services}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<EuiProvider>
|
||||
<DiscoverLayout {...props} />
|
||||
</EuiProvider>
|
||||
<RuntimeStateProvider currentDataView={dataView} adHocDataViews={[]}>
|
||||
<EuiProvider>
|
||||
<DiscoverLayout {...props} />
|
||||
</EuiProvider>
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</KibanaContextProvider>,
|
||||
mountOptions
|
||||
|
|
|
@ -34,7 +34,6 @@ import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
|||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
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 { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
import { DiscoverNoResults } from '../no_results';
|
||||
|
@ -54,6 +53,7 @@ import { DiscoverResizableLayout } from './discover_resizable_layout';
|
|||
import { PanelsToggle, PanelsToggleProps } from '../../../../components/panels_toggle';
|
||||
import { sendErrorMsg } from '../../hooks/use_saved_search_messages';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import { useCurrentDataView, useInternalStateSelector } from '../../state_management/redux';
|
||||
|
||||
const SidebarMemoized = React.memo(DiscoverSidebarResponsive);
|
||||
const TopNavMemoized = React.memo(DiscoverTopNav);
|
||||
|
@ -89,7 +89,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
state.grid,
|
||||
]);
|
||||
const isEsqlMode = useIsEsqlMode();
|
||||
|
||||
const viewMode: VIEW_MODE = useAppStateSelector((state) => {
|
||||
const fieldStatsNotAvailable =
|
||||
!uiSettings.get(SHOW_FIELD_STATISTICS) && !!dataVisualizerService;
|
||||
|
@ -98,15 +97,10 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
}
|
||||
return state.viewMode ?? VIEW_MODE.DOCUMENT_LEVEL;
|
||||
});
|
||||
const [dataView, dataViewLoading] = useInternalStateSelector((state) => [
|
||||
state.dataView!,
|
||||
state.isDataViewLoading,
|
||||
]);
|
||||
const customFilters = useInternalStateSelector((state) => state.customFilters);
|
||||
|
||||
const dataView = useCurrentDataView();
|
||||
const dataViewLoading = useInternalStateSelector((state) => state.isDataViewLoading);
|
||||
const dataState: DataMainMsg = useDataState(main$);
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
|
||||
const fetchCounter = useRef<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -197,21 +191,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
[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>(
|
||||
(field, values, operation) => {
|
||||
if (!field || !isOfAggregateQueryType(query)) {
|
||||
|
@ -430,7 +409,6 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
sidebarToggleState$={sidebarToggleState$}
|
||||
sidebarPanel={
|
||||
<SidebarMemoized
|
||||
additionalFilters={customFilters}
|
||||
columns={currentColumns}
|
||||
documents$={stateContainer.dataState.data$.documents$}
|
||||
onAddBreakdownField={canSetBreakdownField ? onAddBreakdownField : undefined}
|
||||
|
@ -503,3 +481,18 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
</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 { useDiscoverCustomization } from '../../../../customizations';
|
||||
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();
|
||||
let mockQueryState = {
|
||||
|
@ -121,7 +123,11 @@ describe('useDiscoverHistogram', () => {
|
|||
};
|
||||
|
||||
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(
|
||||
|
@ -379,15 +385,17 @@ describe('useDiscoverHistogram', () => {
|
|||
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 stateContainer = getStateContainer();
|
||||
const timeRangeAbs = { from: '2021-05-01T20:00:00Z', to: '2021-05-02T20:00:00Z' };
|
||||
const timeRangeRel = { from: 'now-15m', to: 'now' };
|
||||
stateContainer.internalState.transitions.setDataRequestParams({
|
||||
timeRangeAbsolute: timeRangeAbs,
|
||||
timeRangeRelative: timeRangeRel,
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeAbsolute: timeRangeAbs,
|
||||
timeRangeRelative: timeRangeRel,
|
||||
})
|
||||
);
|
||||
const { hook } = await renderUseDiscoverHistogram({ stateContainer });
|
||||
act(() => {
|
||||
fetch$.next();
|
||||
|
|
|
@ -44,7 +44,6 @@ import type { InspectorAdapters } from '../../hooks/use_inspector';
|
|||
import { checkHitCount, sendErrorTo } from '../../hooks/use_saved_search_messages';
|
||||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { addLog } from '../../../../utils/add_log';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
import {
|
||||
useAppStateSelector,
|
||||
type DiscoverAppState,
|
||||
|
@ -52,6 +51,12 @@ import {
|
|||
import { DataDocumentsMsg } from '../../state_management/discover_data_state_container';
|
||||
import { useSavedSearch } from '../../state_management/discover_state_provider';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import {
|
||||
internalStateActions,
|
||||
useCurrentDataView,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
|
||||
const EMPTY_ESQL_COLUMNS: DatatableColumn[] = [];
|
||||
const EMPTY_FILTERS: Filter[] = [];
|
||||
|
@ -220,7 +225,6 @@ export const useDiscoverHistogram = ({
|
|||
*/
|
||||
const { query, filters } = useQuerySubscriber({ data: services.data });
|
||||
const requestParams = useInternalStateSelector((state) => state.dataRequestParams);
|
||||
const customFilters = useInternalStateSelector((state) => state.customFilters);
|
||||
const { timeRangeRelative: relativeTimeRange, timeRangeAbsolute: timeRange } = requestParams;
|
||||
// When in ES|QL mode, update the data view, query, and
|
||||
// columns only when documents are done fetching so the Lens suggestions
|
||||
|
@ -308,17 +312,18 @@ export const useDiscoverHistogram = ({
|
|||
};
|
||||
}, [isEsqlMode, stateContainer.dataState.fetchChart$, esqlFetchComplete$, unifiedHistogram]);
|
||||
|
||||
const dataView = useInternalStateSelector((state) => state.dataView!);
|
||||
const dataView = useCurrentDataView();
|
||||
|
||||
const histogramCustomization = useDiscoverCustomization('unified_histogram');
|
||||
|
||||
const filtersMemoized = useMemo(() => {
|
||||
const allFilters = [...(filters ?? []), ...customFilters];
|
||||
const allFilters = [...(filters ?? [])];
|
||||
return allFilters.length ? allFilters : EMPTY_FILTERS;
|
||||
}, [filters, customFilters]);
|
||||
}, [filters]);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const timeRangeMemoized = useMemo(() => timeRange, [timeRange?.from, timeRange?.to]);
|
||||
const dispatch = useInternalStateDispatch();
|
||||
|
||||
const onVisContextChanged = useCallback(
|
||||
(
|
||||
|
@ -332,31 +337,25 @@ export const useDiscoverHistogram = ({
|
|||
stateContainer.savedSearchState.updateVisContext({
|
||||
nextVisContext,
|
||||
});
|
||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation(
|
||||
undefined
|
||||
);
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(undefined));
|
||||
break;
|
||||
case UnifiedHistogramExternalVisContextStatus.automaticallyOverridden:
|
||||
// if the visualization was invalidated as incompatible and rebuilt
|
||||
// (it will be used later for saving the visualization via Save button)
|
||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation(
|
||||
nextVisContext
|
||||
);
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(nextVisContext));
|
||||
break;
|
||||
case UnifiedHistogramExternalVisContextStatus.automaticallyCreated:
|
||||
case UnifiedHistogramExternalVisContextStatus.applied:
|
||||
// clearing the value in the internal state so we don't use it during saved search saving
|
||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation(
|
||||
undefined
|
||||
);
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation(undefined));
|
||||
break;
|
||||
case UnifiedHistogramExternalVisContextStatus.unknown:
|
||||
// using `{}` to overwrite the value inside the saved search SO during saving
|
||||
stateContainer.internalState.transitions.setOverriddenVisContextAfterInvalidation({});
|
||||
dispatch(internalStateActions.setOverriddenVisContextAfterInvalidation({}));
|
||||
break;
|
||||
}
|
||||
},
|
||||
[stateContainer]
|
||||
[dispatch, stateContainer.savedSearchState]
|
||||
);
|
||||
|
||||
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 { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
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 { resetExistingFieldsCache } from '@kbn/unified-field-list/src/hooks/use_existing_fields';
|
||||
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 { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||
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 = {
|
||||
id: 'search_bar',
|
||||
|
@ -193,7 +193,7 @@ async function mountComponent(
|
|||
services?: DiscoverServices
|
||||
): Promise<ReactWrapper<DiscoverSidebarResponsiveProps>> {
|
||||
let comp: ReactWrapper<DiscoverSidebarResponsiveProps>;
|
||||
const { appState, internalState } = getStateContainer(appStateParams);
|
||||
const stateContainer = getStateContainer(appStateParams);
|
||||
const mockedServices = services ?? createMockServices();
|
||||
mockedServices.data.dataViews.getIdsWithTitle = jest.fn(async () =>
|
||||
props.selectedDataView
|
||||
|
@ -203,16 +203,18 @@ async function mountComponent(
|
|||
mockedServices.data.dataViews.get = jest.fn().mockImplementation(async (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 () => {
|
||||
comp = mountWithIntl(
|
||||
<KibanaContextProvider services={mockedServices}>
|
||||
<DiscoverAppStateProvider value={appState}>
|
||||
<InternalStateProvider value={internalState}>
|
||||
<DiscoverSidebarResponsive {...props} />
|
||||
</InternalStateProvider>
|
||||
</DiscoverAppStateProvider>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<RuntimeStateProvider currentDataView={props.selectedDataView!} adHocDataViews={[]}>
|
||||
<DiscoverSidebarResponsive {...props} />{' '}
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
// wait for lazy modules
|
||||
|
|
|
@ -37,10 +37,7 @@ import {
|
|||
import { useDiscoverCustomization } from '../../../../customizations';
|
||||
import { useAdditionalFieldGroups } from '../../hooks/sidebar/use_additional_field_groups';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import {
|
||||
selectDataViewsForPicker,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/discover_internal_state_container';
|
||||
import { useDataViewsForPicker } from '../../state_management/redux';
|
||||
|
||||
const EMPTY_FIELD_COUNTS = {};
|
||||
|
||||
|
@ -176,8 +173,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps)
|
|||
);
|
||||
const selectedDataViewRef = useRef<DataView | null | undefined>(selectedDataView);
|
||||
const showFieldList = sidebarState.status !== DiscoverSidebarReducerStatus.INITIAL;
|
||||
const { savedDataViews, managedDataViews, adHocDataViews } =
|
||||
useInternalStateSelector(selectDataViewsForPicker);
|
||||
const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker();
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = props.documents$.subscribe((documentState) => {
|
||||
|
|
|
@ -19,6 +19,7 @@ import type { SearchBarCustomization, TopNavCustomization } from '../../../../cu
|
|||
import type { DiscoverCustomizationId } from '../../../../customizations/customization_service';
|
||||
import { useDiscoverCustomization } from '../../../../customizations';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { RuntimeStateProvider, internalStateActions } from '../../state_management/redux';
|
||||
|
||||
jest.mock('@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;
|
||||
}
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
stateContainer.internalState.transitions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.dispatch(internalStateActions.setDataView(dataViewMock));
|
||||
|
||||
return {
|
||||
stateContainer,
|
||||
|
@ -110,7 +111,9 @@ describe('Discover topnav component', () => {
|
|||
const props = getProps({ capabilities: { discover_v2: { save: true } } });
|
||||
const component = mountWithIntl(
|
||||
<DiscoverMainProvider value={props.stateContainer}>
|
||||
<DiscoverTopNav {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverTopNav {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
);
|
||||
const topNavMenu = component.find(TopNavMenu);
|
||||
|
@ -122,7 +125,9 @@ describe('Discover topnav component', () => {
|
|||
const props = getProps({ capabilities: { discover_v2: { save: false } } });
|
||||
const component = mountWithIntl(
|
||||
<DiscoverMainProvider value={props.stateContainer}>
|
||||
<DiscoverTopNav {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverTopNav {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
);
|
||||
const topNavMenu = component.find(TopNavMenu).props();
|
||||
|
@ -144,7 +149,9 @@ describe('Discover topnav component', () => {
|
|||
const props = getProps();
|
||||
const component = mountWithIntl(
|
||||
<DiscoverMainProvider value={props.stateContainer}>
|
||||
<DiscoverTopNav {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverTopNav {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
);
|
||||
const topNavMenu = component.find(TopNavMenu);
|
||||
|
@ -164,7 +171,9 @@ describe('Discover topnav component', () => {
|
|||
const props = getProps();
|
||||
const component = mountWithIntl(
|
||||
<DiscoverMainProvider value={props.stateContainer}>
|
||||
<DiscoverTopNav {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverTopNav {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
);
|
||||
|
||||
|
@ -176,7 +185,9 @@ describe('Discover topnav component', () => {
|
|||
const props = getProps();
|
||||
const component = mountWithIntl(
|
||||
<DiscoverMainProvider value={props.stateContainer}>
|
||||
<DiscoverTopNav {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverTopNav {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
);
|
||||
const topNav = component.find(mockDiscoverService.navigation.ui.AggregateQueryTopNavMenu);
|
||||
|
@ -197,7 +208,9 @@ describe('Discover topnav component', () => {
|
|||
const props = getProps();
|
||||
const component = mountWithIntl(
|
||||
<DiscoverMainProvider value={props.stateContainer}>
|
||||
<DiscoverTopNav {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverTopNav {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -13,10 +13,6 @@ import type { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
|
|||
import { DiscoverFlyouts, dismissAllFlyoutsExceptFor } from '@kbn/discover-utils';
|
||||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||
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 type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
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 { ESQLToDataViewTransitionModal } from './esql_dataview_transition';
|
||||
import './top_nav.scss';
|
||||
import {
|
||||
internalStateActions,
|
||||
useCurrentDataView,
|
||||
useDataViewsForPicker,
|
||||
useInternalStateDispatch,
|
||||
useInternalStateSelector,
|
||||
} from '../../state_management/redux';
|
||||
|
||||
export interface DiscoverTopNavProps {
|
||||
savedQuery?: string;
|
||||
|
@ -46,12 +49,12 @@ export const DiscoverTopNav = ({
|
|||
isLoading,
|
||||
onCancelClick,
|
||||
}: DiscoverTopNavProps) => {
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const services = useDiscoverServices();
|
||||
const { dataViewEditor, navigation, dataViewFieldEditor, data, setHeaderActionMenu } = services;
|
||||
const query = useAppStateSelector((state) => state.query);
|
||||
const { savedDataViews, managedDataViews, adHocDataViews } =
|
||||
useInternalStateSelector(selectDataViewsForPicker);
|
||||
const dataView = useInternalStateSelector((state) => state.dataView!);
|
||||
const { savedDataViews, managedDataViews, adHocDataViews } = useDataViewsForPicker();
|
||||
const dataView = useCurrentDataView();
|
||||
const isESQLToDataViewTransitionModalVisible = useInternalStateSelector(
|
||||
(state) => state.isESQLToDataViewTransitionModalVisible
|
||||
);
|
||||
|
@ -134,7 +137,7 @@ export const DiscoverTopNav = ({
|
|||
if (shouldDismissModal) {
|
||||
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
|
||||
if (needsSave == null) {
|
||||
return;
|
||||
|
@ -145,9 +148,7 @@ export const DiscoverTopNav = ({
|
|||
services,
|
||||
state: stateContainer,
|
||||
onClose: () =>
|
||||
stateContainer.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(
|
||||
false
|
||||
),
|
||||
dispatch(internalStateActions.setIsESQLToDataViewTransitionModalVisible(false)),
|
||||
onSaveCb: () => {
|
||||
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||
},
|
||||
|
@ -156,7 +157,7 @@ export const DiscoverTopNav = ({
|
|||
stateContainer.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||
}
|
||||
},
|
||||
[dataView.id, services, stateContainer]
|
||||
[dataView.id, dispatch, services, stateContainer]
|
||||
);
|
||||
|
||||
const { topNavBadges, topNavMenu } = useDiscoverTopNav({ stateContainer });
|
||||
|
|
|
@ -20,6 +20,7 @@ import { discoverServiceMock } from '../../../../__mocks__/services';
|
|||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { mockCustomizationContext } from '../../../../customizations/__mocks__/customization_context';
|
||||
import { createRuntimeStateManager } from '../../state_management/redux';
|
||||
|
||||
function getStateContainer({ dataView }: { dataView?: DataView } = {}) {
|
||||
const savedSearch = savedSearchMock;
|
||||
|
@ -28,13 +29,14 @@ function getStateContainer({ dataView }: { dataView?: DataView } = {}) {
|
|||
services: discoverServiceMock,
|
||||
history,
|
||||
customizationContext: mockCustomizationContext,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
stateContainer.savedSearchState.set(savedSearch);
|
||||
stateContainer.appState.getState = jest.fn(() => ({
|
||||
rowsPerPage: 250,
|
||||
}));
|
||||
if (dataView) {
|
||||
stateContainer.internalState.transitions.setDataView(dataView);
|
||||
stateContainer.actions.setDataView(dataView);
|
||||
}
|
||||
return stateContainer;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { SavedSearch, SaveSavedSearchOptions } from '@kbn/saved-search-plugin/pu
|
|||
import { DiscoverServices } from '../../../../build_services';
|
||||
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { getAllowedSampleSize } from '../../../../utils/get_allowed_sample_size';
|
||||
import { internalStateActions } from '../../state_management/redux';
|
||||
|
||||
async function saveDataSource({
|
||||
savedSearch,
|
||||
|
@ -92,7 +93,7 @@ export async function onSaveSearch({
|
|||
onSaveCb?: () => void;
|
||||
}) {
|
||||
const { uiSettings, savedObjectsTagging } = services;
|
||||
const dataView = state.internalState.getState().dataView;
|
||||
const dataView = savedSearch.searchSource.getField('index');
|
||||
const overriddenVisContextAfterInvalidation =
|
||||
state.internalState.getState().overriddenVisContextAfterInvalidation;
|
||||
|
||||
|
@ -174,7 +175,7 @@ export async function onSaveSearch({
|
|||
savedSearch.tags = currentTags;
|
||||
}
|
||||
} else {
|
||||
state.internalState.transitions.resetOnSavedSearchChange();
|
||||
state.internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||
state.appState.resetInitialState();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import { useDiscoverCustomization } from '../../../../customizations';
|
|||
import { useDiscoverServices } from '../../../../hooks/use_discover_services';
|
||||
import { useInspector } from '../../hooks/use_inspector';
|
||||
import { useIsEsqlMode } from '../../hooks/use_is_esql_mode';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
import {
|
||||
useSavedSearch,
|
||||
useSavedSearchHasChanged,
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
import type { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { getTopNavBadges } from './get_top_nav_badges';
|
||||
import { useTopNavLinks } from './use_top_nav_links';
|
||||
import { useAdHocDataViews, useCurrentDataView } from '../../state_management/redux';
|
||||
|
||||
export const useDiscoverTopNav = ({
|
||||
stateContainer,
|
||||
|
@ -47,8 +47,8 @@ export const useDiscoverTopNav = ({
|
|||
const savedSearchId = useSavedSearch().id;
|
||||
const savedSearchHasChanged = useSavedSearchHasChanged();
|
||||
const shouldShowESQLToDataViewTransitionModal = !savedSearchId || savedSearchHasChanged;
|
||||
const dataView = useInternalStateSelector((state) => state.dataView);
|
||||
const adHocDataViews = useInternalStateSelector((state) => state.adHocDataViews);
|
||||
const dataView = useCurrentDataView();
|
||||
const adHocDataViews = useAdHocDataViews();
|
||||
const isEsqlMode = useIsEsqlMode();
|
||||
const onOpenInspector = useInspector({
|
||||
inspector: services.inspector,
|
||||
|
|
|
@ -15,6 +15,7 @@ import { useTopNavLinks } from './use_top_nav_links';
|
|||
import { DiscoverServices } from '../../../../build_services';
|
||||
import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock';
|
||||
import { createDiscoverServicesMock } from '../../../../__mocks__/services';
|
||||
import { DiscoverMainProvider } from '../../state_management/discover_state_provider';
|
||||
|
||||
describe('useTopNavLinks', () => {
|
||||
const services = {
|
||||
|
@ -33,7 +34,11 @@ describe('useTopNavLinks', () => {
|
|||
state.actions.setDataView(dataViewMock);
|
||||
|
||||
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', () => {
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from './app_menu_actions';
|
||||
import type { TopNavCustomization } from '../../../../customizations';
|
||||
import { useProfileAccessor } from '../../../../context_awareness';
|
||||
import { internalStateActions, useInternalStateDispatch } from '../../state_management/redux';
|
||||
|
||||
/**
|
||||
* Helper function to build the top nav links
|
||||
|
@ -52,6 +53,7 @@ export const useTopNavLinks = ({
|
|||
topNavCustomization: TopNavCustomization | undefined;
|
||||
shouldShowESQLToDataViewTransitionModal: boolean;
|
||||
}): TopNavMenuData[] => {
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const [newSearchUrl, setNewSearchUrl] = useState<string | undefined>(undefined);
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
|
@ -68,10 +70,10 @@ export const useTopNavLinks = ({
|
|||
adHocDataViews,
|
||||
onUpdateAdHocDataViews: async (adHocDataViewList) => {
|
||||
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;
|
||||
|
@ -180,7 +182,7 @@ export const useTopNavLinks = ({
|
|||
shouldShowESQLToDataViewTransitionModal &&
|
||||
!services.storage.get(ESQL_TRANSITION_MODAL_KEY)
|
||||
) {
|
||||
state.internalState.transitions.setIsESQLToDataViewTransitionModalVisible(true);
|
||||
dispatch(internalStateActions.setIsESQLToDataViewTransitionModalVisible(true));
|
||||
} else {
|
||||
state.actions.transitionFromESQLToDataView(dataView.id ?? '');
|
||||
}
|
||||
|
@ -223,12 +225,13 @@ export const useTopNavLinks = ({
|
|||
|
||||
return entries;
|
||||
}, [
|
||||
services,
|
||||
appMenuRegistry,
|
||||
state,
|
||||
dataView,
|
||||
services,
|
||||
defaultMenu?.saveItem?.disabled,
|
||||
isEsqlMode,
|
||||
dataView,
|
||||
shouldShowESQLToDataViewTransitionModal,
|
||||
defaultMenu,
|
||||
dispatch,
|
||||
state,
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -26,6 +26,7 @@ import { fetchEsql } from './fetch_esql';
|
|||
import { buildDataTableRecord } from '@kbn/discover-utils';
|
||||
import { dataViewMock, esHitsMockWithSort } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { searchResponseIncompleteWarningLocalCluster } from '@kbn/search-response-warnings/src/__mocks__/search_response_warnings';
|
||||
import { createInternalStateStore, createRuntimeStateManager } from '../state_management/redux';
|
||||
|
||||
jest.mock('./fetch_documents', () => ({
|
||||
fetchDocuments: jest.fn().mockResolvedValue([]),
|
||||
|
@ -67,22 +68,9 @@ describe('test fetchAll', () => {
|
|||
abortController: new AbortController(),
|
||||
inspectorAdapters: { requests: new RequestAdapter() },
|
||||
getAppState: () => ({}),
|
||||
getInternalState: () => ({
|
||||
dataView: undefined,
|
||||
isDataViewLoading: false,
|
||||
savedDataViews: [],
|
||||
adHocDataViews: [],
|
||||
defaultProfileAdHocDataViewIds: [],
|
||||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: {
|
||||
resetId: 'test',
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
dataRequestParams: {},
|
||||
internalState: createInternalStateStore({
|
||||
services: discoverServiceMock,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
}),
|
||||
searchSessionId: '123',
|
||||
initialFetchStatus: FetchStatus.UNINITIALIZED,
|
||||
|
@ -261,22 +249,9 @@ describe('test fetchAll', () => {
|
|||
savedSearch: savedSearchMock,
|
||||
services: discoverServiceMock,
|
||||
getAppState: () => ({ query }),
|
||||
getInternalState: () => ({
|
||||
dataView: undefined,
|
||||
isDataViewLoading: false,
|
||||
savedDataViews: [],
|
||||
adHocDataViews: [],
|
||||
defaultProfileAdHocDataViewIds: [],
|
||||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: {
|
||||
resetId: 'test',
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
dataRequestParams: {},
|
||||
internalState: createInternalStateStore({
|
||||
services: discoverServiceMock,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
}),
|
||||
};
|
||||
fetchAll(subjects, false, deps);
|
||||
|
@ -386,22 +361,9 @@ describe('test fetchAll', () => {
|
|||
savedSearch: savedSearchMock,
|
||||
services: discoverServiceMock,
|
||||
getAppState: () => ({ query }),
|
||||
getInternalState: () => ({
|
||||
dataView: undefined,
|
||||
isDataViewLoading: false,
|
||||
savedDataViews: [],
|
||||
adHocDataViews: [],
|
||||
defaultProfileAdHocDataViewIds: [],
|
||||
expandedDoc: undefined,
|
||||
customFilters: [],
|
||||
overriddenVisContextAfterInvalidation: undefined,
|
||||
resetDefaultProfileState: {
|
||||
resetId: 'test',
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
},
|
||||
dataRequestParams: {},
|
||||
internalState: createInternalStateStore({
|
||||
services: discoverServiceMock,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
}),
|
||||
};
|
||||
fetchAll(subjects, false, deps);
|
||||
|
|
|
@ -42,12 +42,12 @@ import {
|
|||
} from '../state_management/discover_data_state_container';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import { fetchEsql } from './fetch_esql';
|
||||
import { InternalState } from '../state_management/discover_internal_state_container';
|
||||
import { InternalStateStore } from '../state_management/redux';
|
||||
|
||||
export interface FetchDeps {
|
||||
abortController: AbortController;
|
||||
getAppState: () => DiscoverAppState;
|
||||
getInternalState: () => InternalState;
|
||||
internalState: InternalStateStore;
|
||||
initialFetchStatus: FetchStatus;
|
||||
inspectorAdapters: Adapters;
|
||||
savedSearch: SavedSearch;
|
||||
|
@ -71,7 +71,7 @@ export function fetchAll(
|
|||
const {
|
||||
initialFetchStatus,
|
||||
getAppState,
|
||||
getInternalState,
|
||||
internalState,
|
||||
services,
|
||||
inspectorAdapters,
|
||||
savedSearch,
|
||||
|
@ -96,8 +96,7 @@ export function fetchAll(
|
|||
dataView,
|
||||
services,
|
||||
sort: getAppState().sort as SortOrder[],
|
||||
customFilters: getInternalState().customFilters,
|
||||
inputTimeRange: getInternalState().dataRequestParams.timeRangeAbsolute,
|
||||
inputTimeRange: internalState.getState().dataRequestParams.timeRangeAbsolute,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -118,7 +117,7 @@ export function fetchAll(
|
|||
data,
|
||||
expressions,
|
||||
profilesManager,
|
||||
timeRange: getInternalState().dataRequestParams.timeRangeAbsolute,
|
||||
timeRange: internalState.getState().dataRequestParams.timeRangeAbsolute,
|
||||
})
|
||||
: fetchDocuments(searchSource, fetchDeps);
|
||||
const fetchType = isEsqlQuery ? 'fetchTextBased' : 'fetchDocuments';
|
||||
|
@ -221,7 +220,7 @@ export async function fetchMoreDocuments(
|
|||
fetchDeps: FetchDeps
|
||||
): Promise<void> {
|
||||
try {
|
||||
const { getAppState, getInternalState, services, savedSearch } = fetchDeps;
|
||||
const { getAppState, services, savedSearch } = fetchDeps;
|
||||
const searchSource = savedSearch.searchSource.createChild();
|
||||
const dataView = searchSource.getField('index')!;
|
||||
const query = getAppState().query;
|
||||
|
@ -249,7 +248,6 @@ export async function fetchMoreDocuments(
|
|||
dataView,
|
||||
services,
|
||||
sort: getAppState().sort as SortOrder[],
|
||||
customFilters: getInternalState().customFilters,
|
||||
});
|
||||
|
||||
// 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 type { SortOrder } from '@kbn/saved-search-plugin/public';
|
||||
import { discoverServiceMock } from '../../../__mocks__/services';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
||||
describe('updateVolatileSearchSource', () => {
|
||||
test('updates a given search source', async () => {
|
||||
|
@ -22,24 +21,9 @@ describe('updateVolatileSearchSource', () => {
|
|||
dataView: dataViewMock,
|
||||
services: discoverServiceMock,
|
||||
sort: [] as SortOrder[],
|
||||
customFilters: [],
|
||||
});
|
||||
|
||||
expect(searchSource.getField('fields')).toEqual([{ field: '*', include_unmapped: true }]);
|
||||
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 { 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 { SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
|
@ -24,13 +24,11 @@ export function updateVolatileSearchSource(
|
|||
dataView,
|
||||
services,
|
||||
sort,
|
||||
customFilters,
|
||||
inputTimeRange,
|
||||
}: {
|
||||
dataView: DataView;
|
||||
services: DiscoverServices;
|
||||
sort?: SortOrder[];
|
||||
customFilters: Filter[];
|
||||
inputTimeRange?: TimeRange;
|
||||
}
|
||||
) {
|
||||
|
@ -46,16 +44,14 @@ export function updateVolatileSearchSource(
|
|||
|
||||
searchSource.setField('trackTotalHits', true);
|
||||
|
||||
let filters = [...customFilters];
|
||||
|
||||
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
|
||||
const timeFilter = data.query.timefilter.timefilter.createFilter(dataView, inputTimeRange);
|
||||
filters = timeFilter ? [...filters, timeFilter] : filters;
|
||||
searchSource.setField(
|
||||
'filter',
|
||||
data.query.timefilter.timefilter.createFilter(dataView, inputTimeRange)
|
||||
);
|
||||
}
|
||||
|
||||
searchSource.setField('filter', filters);
|
||||
|
||||
searchSource.removeField('fieldsFromSource');
|
||||
searchSource.setField('fields', [{ field: '*', include_unmapped: true }]);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { Router } from '@kbn/shared-ux-router';
|
|||
import { createMemoryHistory } from 'history';
|
||||
import { getDiscoverStateMock } from '../../__mocks__/discover_state.mock';
|
||||
import { DiscoverMainProvider } from './state_management/discover_state_provider';
|
||||
import { RuntimeStateProvider, internalStateActions } from './state_management/redux';
|
||||
|
||||
discoverServiceMock.data.query.timefilter.timefilter.getTime = () => {
|
||||
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[];
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.transitions.setSavedDataViews(dataViewList);
|
||||
stateContainer.internalState.dispatch(internalStateActions.setSavedDataViews(dataViewList));
|
||||
const props = {
|
||||
stateContainer,
|
||||
};
|
||||
|
@ -41,11 +42,13 @@ describe('DiscoverMainApp', () => {
|
|||
});
|
||||
|
||||
await act(async () => {
|
||||
const component = await mountWithIntl(
|
||||
const component = mountWithIntl(
|
||||
<Router history={history}>
|
||||
<KibanaContextProvider services={discoverServiceMock}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
<DiscoverMainApp {...props} />
|
||||
<RuntimeStateProvider currentDataView={dataViewMock} adHocDataViews={[]}>
|
||||
<DiscoverMainApp {...props} />
|
||||
</RuntimeStateProvider>
|
||||
</DiscoverMainProvider>
|
||||
</KibanaContextProvider>
|
||||
</Router>
|
||||
|
@ -53,7 +56,7 @@ describe('DiscoverMainApp', () => {
|
|||
|
||||
// wait for lazy modules
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await component.update();
|
||||
component.update();
|
||||
|
||||
expect(component.find(DiscoverTopNav).exists()).toBe(true);
|
||||
});
|
||||
|
|
|
@ -31,10 +31,7 @@ jest.mock('../../customizations', () => {
|
|||
const originalModule = jest.requireActual('../../customizations');
|
||||
return {
|
||||
...originalModule,
|
||||
useDiscoverCustomizationService: () => ({
|
||||
customizationService: mockCustomizationService,
|
||||
isInitialized: Boolean(mockCustomizationService),
|
||||
}),
|
||||
useDiscoverCustomizationService: () => mockCustomizationService,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* 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 type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import {
|
||||
|
@ -22,6 +22,10 @@ import { reportPerformanceMetricEvent } from '@kbn/ebt-tools';
|
|||
import { withSuspense } from '@kbn/shared-ux-utility';
|
||||
import { getInitialESQLQuery } from '@kbn/esql-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 { useDiscoverStateContainer } from './hooks/use_discover_state_container';
|
||||
import { MainHistoryLocationState } from '../../../common';
|
||||
|
@ -41,6 +45,12 @@ import {
|
|||
import { DiscoverStateContainer, LoadParams } from './state_management/discover_state';
|
||||
import { DataSourceType, isDataSourceType } from '../../../common/data_sources';
|
||||
import { useDefaultAdHocDataViews, useRootProfile } from '../../context_awareness';
|
||||
import {
|
||||
RuntimeStateManager,
|
||||
RuntimeStateProvider,
|
||||
createRuntimeStateManager,
|
||||
useRuntimeState,
|
||||
} from './state_management/redux';
|
||||
|
||||
const DiscoverMainAppMemoized = memo(DiscoverMainApp);
|
||||
|
||||
|
@ -72,17 +82,18 @@ export function DiscoverMainRoute({
|
|||
getScopedHistory,
|
||||
} = services;
|
||||
const { id: savedSearchId } = useParams<DiscoverLandingParams>();
|
||||
const [runtimeStateManager] = useState(() => createRuntimeStateManager());
|
||||
const [stateContainer, { reset: resetStateContainer }] = useDiscoverStateContainer({
|
||||
history,
|
||||
services,
|
||||
customizationContext,
|
||||
stateStorageContainer,
|
||||
runtimeStateManager,
|
||||
});
|
||||
const customizationService = useDiscoverCustomizationService({
|
||||
customizationCallbacks,
|
||||
stateContainer,
|
||||
});
|
||||
const { customizationService, isInitialized: isCustomizationServiceInitialized } =
|
||||
useDiscoverCustomizationService({
|
||||
customizationCallbacks,
|
||||
stateContainer,
|
||||
});
|
||||
const [error, setError] = useState<Error>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [noDataState, setNoDataState] = useState({
|
||||
|
@ -110,6 +121,7 @@ export function DiscoverMainRoute({
|
|||
page: 'app',
|
||||
id: savedSearchId || 'new',
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper function to determine when to skip the no data page
|
||||
*/
|
||||
|
@ -134,8 +146,7 @@ export function DiscoverMainRoute({
|
|||
]);
|
||||
|
||||
const persistedDataViewsExist = hasUserDataViewValue && defaultDataViewExists;
|
||||
const adHocDataViewsExist =
|
||||
stateContainer.internalState.getState().adHocDataViews.length > 0;
|
||||
const adHocDataViewsExist = runtimeStateManager.adHocDataViews$.getValue().length > 0;
|
||||
const locationStateHasDataViewSpec = Boolean(historyLocationState?.dataViewSpec);
|
||||
const canAccessWithAdHocDataViews =
|
||||
hasESDataValue && (adHocDataViewsExist || locationStateHasDataViewSpec);
|
||||
|
@ -156,7 +167,13 @@ export function DiscoverMainRoute({
|
|||
return false;
|
||||
}
|
||||
},
|
||||
[data.dataViews, historyLocationState?.dataViewSpec, savedSearchId, stateContainer]
|
||||
[
|
||||
data.dataViews,
|
||||
historyLocationState?.dataViewSpec,
|
||||
runtimeStateManager,
|
||||
savedSearchId,
|
||||
stateContainer,
|
||||
]
|
||||
);
|
||||
|
||||
const loadSavedSearch = useCallback(
|
||||
|
@ -243,7 +260,7 @@ export function DiscoverMainRoute({
|
|||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isCustomizationServiceInitialized || rootProfileState.rootProfileLoading) {
|
||||
if (!customizationService || rootProfileState.rootProfileLoading) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -262,16 +279,17 @@ export function DiscoverMainRoute({
|
|||
await loadSavedSearch();
|
||||
} else {
|
||||
// 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();
|
||||
}, [
|
||||
customizationService,
|
||||
initializeProfileDataViews,
|
||||
isCustomizationServiceInitialized,
|
||||
loadSavedSearch,
|
||||
rootProfileState.rootProfileLoading,
|
||||
runtimeStateManager,
|
||||
savedSearchId,
|
||||
stateContainer,
|
||||
]);
|
||||
|
@ -280,10 +298,10 @@ export function DiscoverMainRoute({
|
|||
useUrl({
|
||||
history,
|
||||
savedSearchId,
|
||||
onNewUrl: () => {
|
||||
onNewUrl: useCallback(() => {
|
||||
// restore the previously selected data view for a new state
|
||||
loadSavedSearch(getLoadParamsForNewSearch(stateContainer));
|
||||
},
|
||||
loadSavedSearch(getLoadParamsForNewSearch({ stateContainer, runtimeStateManager }));
|
||||
}, [loadSavedSearch, runtimeStateManager, stateContainer]),
|
||||
});
|
||||
|
||||
const onDataViewCreated = useCallback(
|
||||
|
@ -302,7 +320,7 @@ export function DiscoverMainRoute({
|
|||
resetStateContainer();
|
||||
}, [resetStateContainer]);
|
||||
|
||||
const noDataDependencies = useMemo(
|
||||
const noDataDependencies = useMemo<AnalyticsNoDataPageKibanaDependencies>(
|
||||
() => ({
|
||||
coreStart: core,
|
||||
dataViews: {
|
||||
|
@ -323,62 +341,39 @@ export function DiscoverMainRoute({
|
|||
[core, data.dataViews, dataViewEditor, noDataState, services.noDataPage, share]
|
||||
);
|
||||
|
||||
const loadingIndicator = useMemo(
|
||||
() => <LoadingIndicator type={hasCustomBranding ? 'spinner' : 'elastic'} />,
|
||||
[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,
|
||||
]);
|
||||
const currentDataView = useRuntimeState(runtimeStateManager.currentDataView$);
|
||||
const adHocDataViews = useRuntimeState(runtimeStateManager.adHocDataViews$);
|
||||
|
||||
if (error) {
|
||||
return <DiscoverError error={error} />;
|
||||
}
|
||||
|
||||
const loadingIndicator = <LoadingIndicator type={hasCustomBranding ? 'spinner' : 'elastic'} />;
|
||||
|
||||
if (!customizationService || rootProfileState.rootProfileLoading) {
|
||||
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 (
|
||||
<DiscoverCustomizationProvider value={customizationService}>
|
||||
<DiscoverMainProvider value={stateContainer}>
|
||||
|
@ -387,15 +382,45 @@ export function DiscoverMainRoute({
|
|||
</DiscoverCustomizationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
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'];
|
||||
initialAppState: LoadParams['initialAppState'];
|
||||
} {
|
||||
const prevAppState = stateContainer.appState.getState();
|
||||
const prevDataView = stateContainer.internalState.getState().dataView;
|
||||
const prevDataView = runtimeStateManager.currentDataView$.getValue();
|
||||
const initialAppState =
|
||||
isDataSourceType(prevAppState.dataSource, DataSourceType.Esql) &&
|
||||
prevDataView &&
|
||||
|
|
|
@ -11,11 +11,11 @@ import { useEffect } from 'react';
|
|||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
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 { DiscoverStateContainer } from '../state_management/discover_state';
|
||||
import { useFiltersValidation } from './use_filters_validation';
|
||||
import { useIsEsqlMode } from './use_is_esql_mode';
|
||||
import { useCurrentDataView } from '../state_management/redux';
|
||||
|
||||
export const useAdHocDataViews = ({
|
||||
services,
|
||||
|
@ -23,7 +23,7 @@ export const useAdHocDataViews = ({
|
|||
stateContainer: DiscoverStateContainer;
|
||||
services: DiscoverServices;
|
||||
}) => {
|
||||
const dataView = useInternalStateSelector((state) => state.dataView);
|
||||
const dataView = useCurrentDataView();
|
||||
const savedSearch = useSavedSearch();
|
||||
const isEsqlMode = useIsEsqlMode();
|
||||
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 { buildDataTableRecord, EsHitRecord } from '@kbn/discover-utils';
|
||||
import { omit } from 'lodash';
|
||||
import { internalStateActions } from '../state_management/redux';
|
||||
|
||||
function getHookProps(
|
||||
query: AggregateQuery | Query | undefined,
|
||||
|
@ -37,7 +38,9 @@ function getHookProps(
|
|||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
stateContainer.appState.replaceUrlState = replaceUrlState;
|
||||
stateContainer.appState.update({ columns: [], ...appState });
|
||||
stateContainer.internalState.transitions.setSavedDataViews([dataViewMock as DataViewListItem]);
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setSavedDataViews([dataViewMock as DataViewListItem])
|
||||
);
|
||||
|
||||
const msgLoading = {
|
||||
fetchStatus: defaultFetchStatus,
|
||||
|
@ -502,7 +505,9 @@ describe('useEsqlMode', () => {
|
|||
FetchStatus.LOADING
|
||||
);
|
||||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(
|
||||
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -517,7 +522,9 @@ describe('useEsqlMode', () => {
|
|||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(
|
||||
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -527,18 +534,22 @@ describe('useEsqlMode', () => {
|
|||
fetchStatus: FetchStatus.PARTIAL,
|
||||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
})
|
||||
);
|
||||
stateContainer.appState.update({ query: { esql: 'from pattern1' } });
|
||||
documents$.next({
|
||||
fetchStatus: FetchStatus.LOADING,
|
||||
query: { esql: 'from pattern1' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(
|
||||
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -554,7 +565,9 @@ describe('useEsqlMode', () => {
|
|||
query: { esql: 'from pattern2' },
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(
|
||||
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -571,7 +584,9 @@ describe('useEsqlMode', () => {
|
|||
const documents$ = stateContainer.dataState.data$.documents$;
|
||||
const result1 = [buildDataTableRecord({ message: 'foo' } 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,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -582,7 +597,9 @@ describe('useEsqlMode', () => {
|
|||
result: result1,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(
|
||||
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -594,7 +611,9 @@ describe('useEsqlMode', () => {
|
|||
result: result2,
|
||||
});
|
||||
await waitFor(() =>
|
||||
expect(omit(stateContainer.internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(
|
||||
omit(stateContainer.internalState.getState().resetDefaultProfileState, 'resetId')
|
||||
).toEqual({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { useSavedSearchInitial } from '../state_management/discover_state_provid
|
|||
import type { DiscoverStateContainer } from '../state_management/discover_state';
|
||||
import { getValidViewMode } from '../utils/get_valid_view_mode';
|
||||
import { FetchStatus } from '../../types';
|
||||
import { internalStateActions, useInternalStateDispatch } from '../state_management/redux';
|
||||
|
||||
const MAX_NUM_OF_COLUMNS = 50;
|
||||
|
||||
|
@ -31,6 +32,7 @@ export function useEsqlMode({
|
|||
stateContainer: DiscoverStateContainer;
|
||||
dataViews: DataViewsContract;
|
||||
}) {
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const savedSearch = useSavedSearchInitial();
|
||||
const prev = useRef<{
|
||||
initialFetch: boolean;
|
||||
|
@ -93,11 +95,13 @@ export function useEsqlMode({
|
|||
|
||||
// Reset all default profile state when index pattern changes
|
||||
if (indexPatternChanged) {
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
});
|
||||
dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: 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
|
||||
// due to transformational commands, reset the associated default profile state
|
||||
if (!indexPatternChanged && allColumnsChanged) {
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
prev.current.allColumns = nextAllColumns;
|
||||
|
@ -186,5 +192,5 @@ export function useEsqlMode({
|
|||
cleanup();
|
||||
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 { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
||||
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', () => {
|
||||
test('inspector open function is executed, expanded doc is closed', async () => {
|
||||
|
@ -26,13 +29,22 @@ describe('test useInspector', () => {
|
|||
const requests = new RequestAdapter();
|
||||
const lensRequests = new RequestAdapter();
|
||||
const stateContainer = getDiscoverStateMock({ isTimeBased: true });
|
||||
stateContainer.internalState.transitions.setExpandedDoc({} as unknown as DataTableRecord);
|
||||
const { result } = renderHook(() => {
|
||||
return useInspector({
|
||||
stateContainer,
|
||||
inspector: discoverServiceMock.inspector,
|
||||
});
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setExpandedDoc({} as unknown as DataTableRecord)
|
||||
);
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return useInspector({
|
||||
stateContainer,
|
||||
inspector: discoverServiceMock.inspector,
|
||||
});
|
||||
},
|
||||
{
|
||||
wrapper: ({ children }) => (
|
||||
<DiscoverMainProvider value={stateContainer}>{children}</DiscoverMainProvider>
|
||||
),
|
||||
}
|
||||
);
|
||||
await act(async () => {
|
||||
result.current();
|
||||
});
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@kbn/inspector-plugin/public';
|
||||
import { DiscoverStateContainer } from '../state_management/discover_state';
|
||||
import { AggregateRequestAdapter } from '../utils/aggregate_request_adapter';
|
||||
import { internalStateActions, useInternalStateDispatch } from '../state_management/redux';
|
||||
|
||||
export interface InspectorAdapters {
|
||||
requests: RequestAdapter;
|
||||
|
@ -28,11 +29,13 @@ export function useInspector({
|
|||
inspector: InspectorPublicPluginStart;
|
||||
stateContainer: DiscoverStateContainer;
|
||||
}) {
|
||||
const dispatch = useInternalStateDispatch();
|
||||
const [inspectorSession, setInspectorSession] = useState<InspectorSession | undefined>(undefined);
|
||||
|
||||
const onOpenInspector = useCallback(() => {
|
||||
// prevent overlapping
|
||||
stateContainer.internalState.transitions.setExpandedDoc(undefined);
|
||||
dispatch(internalStateActions.setExpandedDoc(undefined));
|
||||
|
||||
const inspectorAdapters = stateContainer.dataState.inspectorAdapters;
|
||||
|
||||
const requestAdapters = inspectorAdapters.lensRequests
|
||||
|
@ -45,7 +48,12 @@ export function useInspector({
|
|||
);
|
||||
|
||||
setInspectorSession(session);
|
||||
}, [stateContainer, inspector]);
|
||||
}, [
|
||||
dispatch,
|
||||
stateContainer.dataState.inspectorAdapters,
|
||||
stateContainer.savedSearchState,
|
||||
inspector,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import { DiscoverServices } from '../../../build_services';
|
|||
import { DiscoverStateContainer } from '../state_management/discover_state';
|
||||
import { omit } from 'lodash';
|
||||
import { createSavedSearchAdHocMock, createSavedSearchMock } from '../../../__mocks__/saved_search';
|
||||
import { internalStateActions } from '../state_management/redux';
|
||||
|
||||
const renderUrlTracking = ({
|
||||
services,
|
||||
|
@ -55,9 +56,11 @@ describe('useUrlTracking', () => {
|
|||
const services = createDiscoverServicesMock();
|
||||
const savedSearch = omit(createSavedSearchAdHocMock(), 'id');
|
||||
const stateContainer = getDiscoverStateMock({ savedSearch });
|
||||
stateContainer.internalState.transitions.setDefaultProfileAdHocDataViews([
|
||||
savedSearch.searchSource.getField('index')!,
|
||||
]);
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDefaultProfileAdHocDataViews([
|
||||
savedSearch.searchSource.getField('index')!,
|
||||
])
|
||||
);
|
||||
expect(services.urlTracker.setTrackingEnabled).not.toHaveBeenCalled();
|
||||
renderUrlTracking({ services, stateContainer });
|
||||
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
|
||||
dataView.isPersisted() ||
|
||||
// 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
|
||||
isOfAggregateQueryType(savedSearch.searchSource.getField('query'));
|
||||
|
||||
|
|
|
@ -20,17 +20,17 @@ import { discoverServiceMock } from '../../../__mocks__/services';
|
|||
import { getDiscoverAppStateContainer, isEqualState } from './discover_app_state_container';
|
||||
import { SavedSearch, VIEW_MODE } from '@kbn/saved-search-plugin/common';
|
||||
import { createDataViewDataSource } from '../../../../common/data_sources';
|
||||
import { getInternalStateContainer } from './discover_internal_state_container';
|
||||
import {
|
||||
DiscoverSavedSearchContainer,
|
||||
getSavedSearchContainer,
|
||||
} from './discover_saved_search_container';
|
||||
import { getDiscoverGlobalStateContainer } from './discover_global_state_container';
|
||||
import { omit } from 'lodash';
|
||||
import { createInternalStateStore, createRuntimeStateManager, InternalStateStore } from './redux';
|
||||
|
||||
let history: History;
|
||||
let stateStorage: IKbnUrlStateStorage;
|
||||
let internalState: ReturnType<typeof getInternalStateContainer>;
|
||||
let internalState: InternalStateStore;
|
||||
let savedSearchState: DiscoverSavedSearchContainer;
|
||||
|
||||
describe('Test discover app state container', () => {
|
||||
|
@ -42,18 +42,21 @@ describe('Test discover app state container', () => {
|
|||
history,
|
||||
...(toasts && withNotifyOnErrors(toasts)),
|
||||
});
|
||||
internalState = getInternalStateContainer();
|
||||
internalState = createInternalStateStore({
|
||||
services: discoverServiceMock,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
savedSearchState = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer: getDiscoverGlobalStateContainer(stateStorage),
|
||||
internalStateContainer: internalState,
|
||||
internalState,
|
||||
});
|
||||
});
|
||||
|
||||
const getStateContainer = () =>
|
||||
getDiscoverAppStateContainer({
|
||||
stateStorage,
|
||||
internalStateContainer: internalState,
|
||||
internalState,
|
||||
savedSearchContainer: savedSearchState,
|
||||
services: discoverServiceMock,
|
||||
});
|
||||
|
@ -271,13 +274,13 @@ describe('Test discover app state container', () => {
|
|||
describe('initAndSync', () => {
|
||||
it('should call setResetDefaultProfileState correctly with no initial state', () => {
|
||||
const state = getStateContainer();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -288,13 +291,13 @@ describe('Test discover app state container', () => {
|
|||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ columns: ['test'] });
|
||||
const state = getStateContainer();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
|
@ -305,13 +308,13 @@ describe('Test discover app state container', () => {
|
|||
const stateStorageGetSpy = jest.spyOn(stateStorage, 'get');
|
||||
stateStorageGetSpy.mockReturnValue({ rowHeight: 5 });
|
||||
const state = getStateContainer();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: true,
|
||||
rowHeight: false,
|
||||
breakdownField: true,
|
||||
|
@ -328,13 +331,13 @@ describe('Test discover app state container', () => {
|
|||
managed: false,
|
||||
});
|
||||
const state = getStateContainer();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
state.initAndSync();
|
||||
expect(omit(internalState.get().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
expect(omit(internalState.getState().resetDefaultProfileState, 'resetId')).toEqual({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
|
|
@ -40,8 +40,8 @@ import {
|
|||
DiscoverDataSource,
|
||||
isDataSourceType,
|
||||
} from '../../../../common/data_sources';
|
||||
import type { DiscoverInternalStateContainer } from './discover_internal_state_container';
|
||||
import type { DiscoverSavedSearchContainer } from './discover_saved_search_container';
|
||||
import { internalStateActions, InternalStateStore } from './redux';
|
||||
|
||||
export const APP_STATE_URL_KEY = '_a';
|
||||
export interface DiscoverAppStateContainer extends ReduxLikeStateContainer<DiscoverAppState> {
|
||||
|
@ -185,12 +185,12 @@ export const { Provider: DiscoverAppStateProvider, useSelector: useAppStateSelec
|
|||
*/
|
||||
export const getDiscoverAppStateContainer = ({
|
||||
stateStorage,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
savedSearchContainer,
|
||||
services,
|
||||
}: {
|
||||
stateStorage: IKbnUrlStateStorage;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
savedSearchContainer: DiscoverSavedSearchContainer;
|
||||
services: DiscoverServices;
|
||||
}): DiscoverAppStateContainer => {
|
||||
|
@ -268,11 +268,13 @@ export const getDiscoverAppStateContainer = ({
|
|||
const { breakdownField, columns, rowHeight } = getCurrentUrlState(stateStorage, services);
|
||||
|
||||
// Only set default state which is not already set in the URL
|
||||
internalStateContainer.transitions.setResetDefaultProfileState({
|
||||
columns: columns === undefined,
|
||||
rowHeight: rowHeight === undefined,
|
||||
breakdownField: breakdownField === undefined,
|
||||
});
|
||||
internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: columns === undefined,
|
||||
rowHeight: rowHeight === undefined,
|
||||
breakdownField: breakdownField === undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const { data } = services;
|
||||
|
|
|
@ -17,6 +17,7 @@ import { DataDocuments$ } from './discover_data_state_container';
|
|||
import { getDiscoverStateMock } from '../../../__mocks__/discover_state.mock';
|
||||
import { fetchDocuments } from '../data_fetching/fetch_documents';
|
||||
import { omit } from 'lodash';
|
||||
import { internalStateActions } from './redux';
|
||||
|
||||
jest.mock('../data_fetching/fetch_documents', () => ({
|
||||
fetchDocuments: jest.fn().mockResolvedValue({ records: [] }),
|
||||
|
@ -176,11 +177,13 @@ describe('test getDataStateContainer', () => {
|
|||
const appUnsub = stateContainer.appState.initAndSync();
|
||||
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
})
|
||||
);
|
||||
|
||||
dataState.data$.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
|
@ -191,7 +194,9 @@ describe('test getDataStateContainer', () => {
|
|||
await waitFor(() => {
|
||||
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,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
@ -209,11 +214,13 @@ describe('test getDataStateContainer', () => {
|
|||
const appUnsub = stateContainer.appState.initAndSync();
|
||||
await discoverServiceMock.profilesManager.resolveDataSourceProfile({});
|
||||
stateContainer.actions.setDataView(dataViewMock);
|
||||
stateContainer.internalState.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
})
|
||||
);
|
||||
dataState.data$.totalHits$.next({
|
||||
fetchStatus: FetchStatus.COMPLETE,
|
||||
result: 0,
|
||||
|
@ -222,7 +229,9 @@ describe('test getDataStateContainer', () => {
|
|||
await waitFor(() => {
|
||||
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,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
|
|
|
@ -28,8 +28,8 @@ import { validateTimeRange } from './utils/validate_time_range';
|
|||
import { fetchAll, fetchMoreDocuments } from '../data_fetching/fetch_all';
|
||||
import { sendResetMsg } from '../hooks/use_saved_search_messages';
|
||||
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 { internalStateActions, InternalStateStore, RuntimeStateManager } from './redux';
|
||||
|
||||
export interface SavedSearchData {
|
||||
main$: DataMain$;
|
||||
|
@ -138,14 +138,16 @@ export function getDataStateContainer({
|
|||
services,
|
||||
searchSessionManager,
|
||||
appStateContainer,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
getSavedSearch,
|
||||
setDataView,
|
||||
}: {
|
||||
services: DiscoverServices;
|
||||
searchSessionManager: DiscoverSearchSessionManager;
|
||||
appStateContainer: DiscoverAppStateContainer;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
getSavedSearch: () => SavedSearch;
|
||||
setDataView: (dataView: DataView) => void;
|
||||
}): DiscoverDataStateContainer {
|
||||
|
@ -229,7 +231,7 @@ export function getDataStateContainer({
|
|||
searchSessionId,
|
||||
services,
|
||||
getAppState: appStateContainer.getState,
|
||||
getInternalState: internalStateContainer.getState,
|
||||
internalState,
|
||||
savedSearch: getSavedSearch(),
|
||||
};
|
||||
|
||||
|
@ -254,10 +256,12 @@ export function getDataStateContainer({
|
|||
return;
|
||||
}
|
||||
|
||||
internalStateContainer.transitions.setDataRequestParams({
|
||||
timeRangeAbsolute: timefilter.getAbsoluteTime(),
|
||||
timeRangeRelative: timefilter.getTime(),
|
||||
});
|
||||
internalState.dispatch(
|
||||
internalStateActions.setDataRequestParams({
|
||||
timeRangeAbsolute: timefilter.getAbsoluteTime(),
|
||||
timeRangeRelative: timefilter.getTime(),
|
||||
})
|
||||
);
|
||||
|
||||
await profilesManager.resolveDataSourceProfile({
|
||||
dataSource: appStateContainer.getState().dataSource,
|
||||
|
@ -265,7 +269,8 @@ export function getDataStateContainer({
|
|||
query: appStateContainer.getState().query,
|
||||
});
|
||||
|
||||
const { resetDefaultProfileState, dataView } = internalStateContainer.getState();
|
||||
const { resetDefaultProfileState } = internalState.getState();
|
||||
const dataView = runtimeStateManager.currentDataView$.getValue();
|
||||
const defaultProfileState = dataView
|
||||
? getDefaultProfileState({ profilesManager, resetDefaultProfileState, dataView })
|
||||
: undefined;
|
||||
|
@ -294,7 +299,7 @@ export function getDataStateContainer({
|
|||
},
|
||||
async () => {
|
||||
const { resetDefaultProfileState: currentResetDefaultProfileState } =
|
||||
internalStateContainer.getState();
|
||||
internalState.getState();
|
||||
|
||||
if (currentResetDefaultProfileState.resetId !== resetDefaultProfileState.resetId) {
|
||||
return;
|
||||
|
@ -313,11 +318,13 @@ export function getDataStateContainer({
|
|||
|
||||
// Clear the default profile state flags after the data fetching
|
||||
// is done so refetches don't reset the state again
|
||||
internalStateContainer.transitions.setResetDefaultProfileState({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
});
|
||||
internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: 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 { VIEW_MODE } from '../../../../common/constants';
|
||||
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', () => {
|
||||
const savedSearch = savedSearchMock;
|
||||
const services = discoverServiceMock;
|
||||
const globalStateContainer = getDiscoverGlobalStateContainer(createKbnUrlStateStorage());
|
||||
const internalStateContainer = getInternalStateContainer();
|
||||
const internalState = createInternalStateStore({
|
||||
services,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
|
||||
describe('getTitle', () => {
|
||||
it('returns undefined for new saved searches', () => {
|
||||
const container = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
expect(container.getTitle()).toBe(undefined);
|
||||
});
|
||||
|
@ -39,7 +43,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const container = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
container.set(savedSearch);
|
||||
expect(container.getTitle()).toBe(savedSearch.title);
|
||||
|
@ -51,7 +56,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const container = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
|
||||
const result = container.set(newSavedSearch);
|
||||
|
@ -68,7 +74,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const container = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const newSavedSearch: SavedSearch = { ...savedSearch, title: 'New title' };
|
||||
|
||||
|
@ -82,7 +89,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const container = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const result = await container.new(dataViewMock);
|
||||
|
||||
|
@ -99,7 +107,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const container = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const result = await container.new(dataViewMock);
|
||||
expect(result.title).toBeUndefined();
|
||||
|
@ -119,7 +128,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
await savedSearchContainer.load('the-saved-search-id');
|
||||
expect(savedSearchContainer.getInitial$().getValue().id).toEqual('the-saved-search-id');
|
||||
|
@ -135,7 +145,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const savedSearchToPersist = {
|
||||
...savedSearchMockWithTimeField,
|
||||
|
@ -161,7 +172,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
|
||||
const result = await savedSearchContainer.persist(persistedSavedSearch, saveOptions);
|
||||
|
@ -174,7 +186,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const savedSearchToPersist = {
|
||||
...savedSearchMockWithTimeField,
|
||||
|
@ -194,7 +207,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const savedSearchToPersist = {
|
||||
...savedSearchMockWithTimeField,
|
||||
|
@ -219,7 +233,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
savedSearchContainer.set(savedSearch);
|
||||
savedSearchContainer.update({ nextState: { hideChart: true } });
|
||||
|
@ -241,7 +256,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
savedSearchContainer.set(savedSearch);
|
||||
const updated = savedSearchContainer.update({ nextState: { hideChart: true } });
|
||||
|
@ -257,7 +273,8 @@ describe('DiscoverSavedSearchContainer', () => {
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services: discoverServiceMock,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
|
||||
internalState,
|
||||
});
|
||||
const updated = savedSearchContainer.update({ nextDataView: dataViewMock });
|
||||
expect(savedSearchContainer.getHasChanged$().getValue()).toBe(true);
|
||||
|
|
|
@ -30,7 +30,7 @@ import { DiscoverAppState, isEqualFilters } from './discover_app_state_container
|
|||
import { DiscoverServices } from '../../../build_services';
|
||||
import { getStateDefaults } from './utils/get_state_defaults';
|
||||
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 = {
|
||||
...COMPARE_ALL_OPTIONS,
|
||||
|
@ -139,11 +139,11 @@ export interface DiscoverSavedSearchContainer {
|
|||
export function getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
}: {
|
||||
services: DiscoverServices;
|
||||
globalStateContainer: DiscoverGlobalStateContainer;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
}): DiscoverSavedSearchContainer {
|
||||
const initialSavedSearch = services.savedSearch.getNew();
|
||||
const savedSearchInitial$ = new BehaviorSubject(initialSavedSearch);
|
||||
|
@ -183,7 +183,7 @@ export function getSavedSearchContainer({
|
|||
addLog('[savedSearch] persist', { nextSavedSearch, saveOptions });
|
||||
|
||||
const dataView = nextSavedSearch.searchSource.getField('index');
|
||||
const profileDataViewIds = internalStateContainer.getState().defaultProfileAdHocDataViewIds;
|
||||
const profileDataViewIds = internalState.getState().defaultProfileAdHocDataViewIds;
|
||||
let replacementDataView: DataView | undefined;
|
||||
|
||||
// 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 { mockCustomizationContext } from '../../../customizations/__mocks__/customization_context';
|
||||
import { createDataViewDataSource, createEsqlDataSource } from '../../../../common/data_sources';
|
||||
import { createRuntimeStateManager } from './redux';
|
||||
|
||||
const startSync = (appState: DiscoverAppStateContainer) => {
|
||||
const { start, stop } = appState.syncState();
|
||||
|
@ -46,17 +47,22 @@ async function getState(
|
|||
const nextHistory = createBrowserHistory();
|
||||
nextHistory.push(url);
|
||||
|
||||
discoverServiceMock.dataViews.create = jest.fn().mockReturnValue({
|
||||
...dataViewMock,
|
||||
isPersisted: () => false,
|
||||
id: 'ad-hoc-id',
|
||||
title: 'test',
|
||||
discoverServiceMock.dataViews.create = jest.fn().mockImplementation((spec) => {
|
||||
spec.id = spec.id ?? 'ad-hoc-id';
|
||||
spec.title = spec.title ?? 'test';
|
||||
return Promise.resolve({
|
||||
...dataViewMock,
|
||||
isPersisted: () => false,
|
||||
toSpec: () => spec,
|
||||
...spec,
|
||||
});
|
||||
});
|
||||
|
||||
const runtimeStateManager = createRuntimeStateManager();
|
||||
const nextState = getDiscoverStateContainer({
|
||||
services: discoverServiceMock,
|
||||
history: nextHistory,
|
||||
customizationContext: mockCustomizationContext,
|
||||
runtimeStateManager,
|
||||
});
|
||||
nextState.appState.isEmptyURL = jest.fn(() => isEmptyUrl ?? true);
|
||||
jest.spyOn(nextState.dataState, 'fetch');
|
||||
|
@ -77,6 +83,7 @@ async function getState(
|
|||
return {
|
||||
history: nextHistory,
|
||||
state: nextState,
|
||||
runtimeStateManager,
|
||||
getCurrentUrl,
|
||||
};
|
||||
}
|
||||
|
@ -94,6 +101,7 @@ describe('Test discover state', () => {
|
|||
services: discoverServiceMock,
|
||||
history,
|
||||
customizationContext: mockCustomizationContext,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
state.savedSearchState.set(savedSearchMock);
|
||||
state.appState.update({}, true);
|
||||
|
@ -192,6 +200,7 @@ describe('Test discover state with overridden state storage', () => {
|
|||
history,
|
||||
customizationContext: mockCustomizationContext,
|
||||
stateStorageContainer: stateStorage,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
state.savedSearchState.set(savedSearchMock);
|
||||
state.appState.update({}, true);
|
||||
|
@ -283,6 +292,7 @@ describe('Test createSearchSessionRestorationDataProvider', () => {
|
|||
services: discoverServiceMock,
|
||||
history,
|
||||
customizationContext: mockCustomizationContext,
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
discoverStateContainer.appState.update({
|
||||
dataSource: createDataViewDataSource({
|
||||
|
@ -419,9 +429,11 @@ describe('Test discover state actions', () => {
|
|||
});
|
||||
|
||||
test('setDataView', async () => {
|
||||
const { state } = await getState('');
|
||||
const { state, runtimeStateManager } = await getState('');
|
||||
expect(runtimeStateManager.currentDataView$.getValue()).toBeUndefined();
|
||||
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 () => {
|
||||
|
@ -717,7 +729,7 @@ describe('Test discover state actions', () => {
|
|||
state.savedSearchState.getCurrent$().getValue().searchSource?.getField('index')?.id
|
||||
).toEqual(dataViewSpecMock.id);
|
||||
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 () => {
|
||||
|
@ -749,7 +761,7 @@ describe('Test discover state actions', () => {
|
|||
expect(state.appState.getState().dataSource).toEqual(
|
||||
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 () => {
|
||||
|
@ -829,7 +841,7 @@ describe('Test discover state actions', () => {
|
|||
const unsubscribe = state.actions.initializeAndSync();
|
||||
await state.actions.onDataViewCreated(dataViewComplexMock);
|
||||
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(
|
||||
createDataViewDataSource({ dataViewId: dataViewComplexMock.id! })
|
||||
|
@ -844,9 +856,14 @@ describe('Test discover state actions', () => {
|
|||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id });
|
||||
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 waitFor(() => {
|
||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewAdHoc.id);
|
||||
expect(state.internalState.getState().dataViewId).toBe(dataViewAdHoc.id);
|
||||
});
|
||||
expect(state.appState.getState().dataSource).toEqual(
|
||||
createDataViewDataSource({ dataViewId: dataViewAdHoc.id! })
|
||||
|
@ -860,15 +877,12 @@ describe('Test discover state actions', () => {
|
|||
test('onDataViewEdited - persisted data view', async () => {
|
||||
const { state } = await getState('/', { savedSearch: savedSearchMock });
|
||||
await state.actions.loadSavedSearch({ savedSearchId: savedSearchMock.id });
|
||||
const selectedDataView = state.internalState.getState().dataView;
|
||||
await waitFor(() => {
|
||||
expect(selectedDataView).toBe(dataViewMock);
|
||||
});
|
||||
const selectedDataViewId = state.internalState.getState().dataViewId;
|
||||
expect(selectedDataViewId).toBe(dataViewMock.id);
|
||||
const unsubscribe = state.actions.initializeAndSync();
|
||||
await state.actions.onDataViewEdited(dataViewMock);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(state.internalState.getState().dataView).not.toBe(selectedDataView);
|
||||
expect(state.internalState.getState().dataViewId).toBe(selectedDataViewId);
|
||||
});
|
||||
unsubscribe();
|
||||
});
|
||||
|
@ -880,7 +894,7 @@ describe('Test discover state actions', () => {
|
|||
const previousId = dataViewAdHoc.id;
|
||||
await state.actions.onDataViewEdited(dataViewAdHoc);
|
||||
await waitFor(() => {
|
||||
expect(state.internalState.getState().dataView?.id).not.toBe(previousId);
|
||||
expect(state.internalState.getState().dataViewId).not.toBe(previousId);
|
||||
});
|
||||
unsubscribe();
|
||||
});
|
||||
|
@ -916,7 +930,7 @@ describe('Test discover state actions', () => {
|
|||
expect(state.appState.getState().dataSource).toEqual(
|
||||
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();
|
||||
});
|
||||
|
||||
|
@ -929,7 +943,7 @@ describe('Test discover state actions', () => {
|
|||
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:!())';
|
||||
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
|
||||
await state.actions.onChangeDataView(dataViewComplexMock.id!);
|
||||
|
@ -940,7 +954,7 @@ describe('Test discover state actions', () => {
|
|||
await waitFor(() => {
|
||||
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
|
||||
await state.actions.undoSavedSearchChanges();
|
||||
|
@ -949,7 +963,7 @@ describe('Test discover state actions', () => {
|
|||
await waitFor(() => {
|
||||
expect(state.dataState.fetch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(state.internalState.getState().dataView?.id).toBe(dataViewMock.id!);
|
||||
expect(state.internalState.getState().dataViewId).toBe(dataViewMock.id!);
|
||||
|
||||
unsubscribe();
|
||||
});
|
||||
|
@ -991,6 +1005,7 @@ describe('Test discover state with embedded mode', () => {
|
|||
...mockCustomizationContext,
|
||||
displayMode: 'embedded',
|
||||
},
|
||||
runtimeStateManager: createRuntimeStateManager(),
|
||||
});
|
||||
state.savedSearchState.set(savedSearchMock);
|
||||
state.appState.update({}, true);
|
||||
|
|
|
@ -47,10 +47,6 @@ import {
|
|||
DiscoverAppStateContainer,
|
||||
getDiscoverAppStateContainer,
|
||||
} from './discover_app_state_container';
|
||||
import {
|
||||
DiscoverInternalStateContainer,
|
||||
getInternalStateContainer,
|
||||
} from './discover_internal_state_container';
|
||||
import { DiscoverServices } from '../../../build_services';
|
||||
import {
|
||||
getDefaultAppState,
|
||||
|
@ -68,6 +64,12 @@ import {
|
|||
DataSourceType,
|
||||
isDataSourceType,
|
||||
} from '../../../../common/data_sources';
|
||||
import {
|
||||
createInternalStateStore,
|
||||
internalStateActions,
|
||||
InternalStateStore,
|
||||
RuntimeStateManager,
|
||||
} from './redux';
|
||||
|
||||
export interface DiscoverStateContainerParams {
|
||||
/**
|
||||
|
@ -90,6 +92,10 @@ export interface DiscoverStateContainerParams {
|
|||
* a custom url state storage
|
||||
*/
|
||||
stateStorageContainer?: IKbnUrlStateStorage;
|
||||
/**
|
||||
* State manager for runtime state that can't be stored in Redux
|
||||
*/
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
}
|
||||
|
||||
export interface LoadParams {
|
||||
|
@ -127,7 +133,11 @@ export interface DiscoverStateContainer {
|
|||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@ -242,6 +252,7 @@ export function getDiscoverStateContainer({
|
|||
services,
|
||||
customizationContext,
|
||||
stateStorageContainer,
|
||||
runtimeStateManager,
|
||||
}: DiscoverStateContainerParams): DiscoverStateContainer {
|
||||
const storeInSessionStorage = services.uiSettings.get('state:storeInSessionStorage');
|
||||
const toasts = services.core.notifications.toasts;
|
||||
|
@ -272,9 +283,9 @@ export function getDiscoverStateContainer({
|
|||
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
|
||||
|
@ -282,7 +293,7 @@ export function getDiscoverStateContainer({
|
|||
const savedSearchContainer = getSavedSearchContainer({
|
||||
services,
|
||||
globalStateContainer,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -290,7 +301,7 @@ export function getDiscoverStateContainer({
|
|||
*/
|
||||
const appStateContainer = getDiscoverAppStateContainer({
|
||||
stateStorage,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
savedSearchContainer,
|
||||
services,
|
||||
});
|
||||
|
@ -308,7 +319,7 @@ export function getDiscoverStateContainer({
|
|||
};
|
||||
|
||||
const setDataView = (dataView: DataView) => {
|
||||
internalStateContainer.transitions.setDataView(dataView);
|
||||
internalState.dispatch(internalStateActions.setDataView(dataView));
|
||||
pauseAutoRefreshInterval(dataView);
|
||||
savedSearchContainer.getState().searchSource.setField('index', dataView);
|
||||
};
|
||||
|
@ -317,14 +328,15 @@ export function getDiscoverStateContainer({
|
|||
services,
|
||||
searchSessionManager,
|
||||
appStateContainer,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
getSavedSearch: savedSearchContainer.getState,
|
||||
setDataView,
|
||||
});
|
||||
|
||||
const loadDataViewList = async () => {
|
||||
const dataViewList = await services.dataViews.getIdsWithTitle(true);
|
||||
internalStateContainer.transitions.setSavedDataViews(dataViewList);
|
||||
const savedDataViews = await services.dataViews.getIdsWithTitle(true);
|
||||
internalState.dispatch(internalStateActions.setSavedDataViews(savedDataViews));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -332,7 +344,7 @@ export function getDiscoverStateContainer({
|
|||
* This is to prevent duplicate ids messing with our system
|
||||
*/
|
||||
const updateAdHocDataViewId = async () => {
|
||||
const prevDataView = internalStateContainer.getState().dataView;
|
||||
const prevDataView = runtimeStateManager.currentDataView$.getValue();
|
||||
if (!prevDataView || prevDataView.isPersisted()) return;
|
||||
|
||||
const nextDataView = await services.dataViews.create({
|
||||
|
@ -348,7 +360,9 @@ export function getDiscoverStateContainer({
|
|||
services,
|
||||
});
|
||||
|
||||
internalStateContainer.transitions.replaceAdHocDataViewWithId(prevDataView.id!, nextDataView);
|
||||
internalState.dispatch(
|
||||
internalStateActions.replaceAdHocDataViewWithId(prevDataView.id!, nextDataView)
|
||||
);
|
||||
|
||||
if (isDataSourceType(appStateContainer.get().dataSource, DataSourceType.DataView)) {
|
||||
await appStateContainer.replaceUrlState({
|
||||
|
@ -413,7 +427,7 @@ export function getDiscoverStateContainer({
|
|||
|
||||
const onDataViewCreated = async (nextDataView: DataView) => {
|
||||
if (!nextDataView.isPersisted()) {
|
||||
internalStateContainer.transitions.appendAdHocDataViews(nextDataView);
|
||||
internalState.dispatch(internalStateActions.appendAdHocDataViews(nextDataView));
|
||||
} else {
|
||||
await loadDataViewList();
|
||||
}
|
||||
|
@ -440,7 +454,8 @@ export function getDiscoverStateContainer({
|
|||
return loadSavedSearchFn(params ?? {}, {
|
||||
appStateContainer,
|
||||
dataStateContainer,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
savedSearchContainer,
|
||||
globalStateContainer,
|
||||
services,
|
||||
|
@ -470,7 +485,8 @@ export function getDiscoverStateContainer({
|
|||
appState: appStateContainer,
|
||||
savedSearchState: savedSearchContainer,
|
||||
dataState: dataStateContainer,
|
||||
internalState: internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
services,
|
||||
setDataView,
|
||||
})
|
||||
|
@ -482,7 +498,7 @@ export function getDiscoverStateContainer({
|
|||
// updates saved search when query or filters change, triggers data fetching
|
||||
const filterUnsubscribe = merge(services.filterManager.getFetches$()).subscribe(() => {
|
||||
savedSearchContainer.update({
|
||||
nextDataView: internalStateContainer.getState().dataView,
|
||||
nextDataView: runtimeStateManager.currentDataView$.getValue(),
|
||||
nextState: appStateContainer.getState(),
|
||||
useFilterAndQueryServices: true,
|
||||
});
|
||||
|
@ -521,8 +537,7 @@ export function getDiscoverStateContainer({
|
|||
if (newDataView.fields.getByName('@timestamp')?.type === 'date') {
|
||||
newDataView.timeFieldName = '@timestamp';
|
||||
}
|
||||
internalStateContainer.transitions.appendAdHocDataViews(newDataView);
|
||||
|
||||
internalState.dispatch(internalStateActions.appendAdHocDataViews(newDataView));
|
||||
await onChangeDataView(newDataView);
|
||||
return newDataView;
|
||||
};
|
||||
|
@ -545,10 +560,12 @@ export function getDiscoverStateContainer({
|
|||
/**
|
||||
* Function e.g. triggered when user changes data view in the sidebar
|
||||
*/
|
||||
const onChangeDataView = async (id: string | DataView) => {
|
||||
await changeDataView(id, {
|
||||
const onChangeDataView = async (dataViewId: string | DataView) => {
|
||||
await changeDataView({
|
||||
dataViewId,
|
||||
services,
|
||||
internalState: internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
appState: appStateContainer,
|
||||
});
|
||||
};
|
||||
|
@ -575,7 +592,7 @@ export function getDiscoverStateContainer({
|
|||
});
|
||||
}
|
||||
|
||||
internalStateContainer.transitions.resetOnSavedSearchChange();
|
||||
internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||
await appStateContainer.replaceUrlState(newAppState);
|
||||
return nextSavedSearch;
|
||||
};
|
||||
|
@ -606,7 +623,8 @@ export function getDiscoverStateContainer({
|
|||
return {
|
||||
globalState: globalStateContainer,
|
||||
appState: appStateContainer,
|
||||
internalState: internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
dataState: dataStateContainer,
|
||||
savedSearchState: savedSearchContainer,
|
||||
stateStorage,
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
import React, { useContext } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import { InternalStateProvider } from './discover_internal_state_container';
|
||||
import { DiscoverAppStateProvider } from './discover_app_state_container';
|
||||
import { DiscoverStateContainer } from './discover_state';
|
||||
import { InternalStateProvider } from './redux';
|
||||
|
||||
function createStateHelpers() {
|
||||
const context = React.createContext<DiscoverStateContainer | null>(null);
|
||||
|
@ -63,7 +63,7 @@ export const DiscoverMainProvider = ({
|
|||
return (
|
||||
<DiscoverStateProvider value={value}>
|
||||
<DiscoverAppStateProvider value={value.appState}>
|
||||
<InternalStateProvider value={value.internalState}>{children}</InternalStateProvider>
|
||||
<InternalStateProvider store={value.internalState}>{children}</InternalStateProvider>
|
||||
</DiscoverAppStateProvider>
|
||||
</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,
|
||||
dataState: stateContainer.dataState,
|
||||
internalState: stateContainer.internalState,
|
||||
runtimeStateManager: stateContainer.runtimeStateManager,
|
||||
services: discoverServiceMock,
|
||||
setDataView: stateContainer.actions.setDataView,
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
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 { DiscoverSavedSearchContainer } from '../discover_saved_search_container';
|
||||
import type { DiscoverDataStateContainer } from '../discover_data_state_container';
|
||||
|
@ -38,13 +38,15 @@ export const buildStateSubscribe =
|
|||
appState,
|
||||
dataState,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
savedSearchState,
|
||||
services,
|
||||
setDataView,
|
||||
}: {
|
||||
appState: DiscoverAppStateContainer;
|
||||
dataState: DiscoverDataStateContainer;
|
||||
internalState: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
savedSearchState: DiscoverSavedSearchContainer;
|
||||
services: DiscoverServices;
|
||||
setDataView: DiscoverStateContainer['actions']['setDataView'];
|
||||
|
@ -106,7 +108,8 @@ export const buildStateSubscribe =
|
|||
dataViewId,
|
||||
savedSearch,
|
||||
isEsqlMode,
|
||||
internalStateContainer: internalState,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
services,
|
||||
});
|
||||
|
||||
|
|
|
@ -16,72 +16,79 @@ import { savedSearchMock } from '../../../../__mocks__/saved_search';
|
|||
import { discoverServiceMock } from '../../../../__mocks__/services';
|
||||
import type { DataView } from '@kbn/data-views-plugin/common';
|
||||
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 { createRuntimeStateManager, internalStateActions } from '../redux';
|
||||
|
||||
const setupTestParams = (dataView: DataView | undefined) => {
|
||||
const savedSearch = savedSearchMock;
|
||||
const services = discoverServiceMock;
|
||||
|
||||
const discoverState = getDiscoverStateMock({
|
||||
savedSearch,
|
||||
});
|
||||
discoverState.internalState.transitions.setDataView(savedSearch.searchSource.getField('index')!);
|
||||
const runtimeStateManager = createRuntimeStateManager();
|
||||
const discoverState = getDiscoverStateMock({ savedSearch, runtimeStateManager });
|
||||
discoverState.internalState.dispatch(
|
||||
internalStateActions.setDataView(savedSearch.searchSource.getField('index')!)
|
||||
);
|
||||
services.dataViews.get = jest.fn(() => Promise.resolve(dataView as DataView));
|
||||
discoverState.appState.update = jest.fn();
|
||||
discoverState.internalState.transitions = {
|
||||
setIsDataViewLoading: jest.fn(),
|
||||
setResetDefaultProfileState: jest.fn(),
|
||||
} as unknown as Readonly<PureTransitionsToTransitions<InternalStateTransitions>>;
|
||||
return {
|
||||
services,
|
||||
appState: discoverState.appState,
|
||||
internalState: discoverState.internalState,
|
||||
runtimeStateManager,
|
||||
};
|
||||
};
|
||||
|
||||
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 () => {
|
||||
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({
|
||||
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' }),
|
||||
sort: [['@timestamp', 'desc']],
|
||||
});
|
||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
||||
expect(params.internalState.getState().isDataViewLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should set the right app state when a valid data view to switch to is given', async () => {
|
||||
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({
|
||||
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' }),
|
||||
sort: [['data', 'desc']],
|
||||
});
|
||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
||||
expect(params.internalState.getState().isDataViewLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should not set the app state when an invalid data view to switch to is given', async () => {
|
||||
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.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(1, true);
|
||||
expect(params.internalState.transitions.setIsDataViewLoading).toHaveBeenNthCalledWith(2, false);
|
||||
expect(params.internalState.getState().isDataViewLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('should call setResetDefaultProfileState correctly when switching data view', async () => {
|
||||
const params = setupTestParams(dataViewComplexMock);
|
||||
expect(params.internalState.transitions.setResetDefaultProfileState).not.toHaveBeenCalled();
|
||||
await changeDataView(dataViewComplexMock.id!, params);
|
||||
expect(params.internalState.transitions.setResetDefaultProfileState).toHaveBeenCalledWith({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
});
|
||||
expect(params.internalState.getState().resetDefaultProfileState).toEqual(
|
||||
expect.objectContaining({
|
||||
columns: false,
|
||||
rowHeight: false,
|
||||
breakdownField: false,
|
||||
})
|
||||
);
|
||||
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,
|
||||
DEFAULT_COLUMNS_SETTING,
|
||||
} from '@kbn/discover-utils';
|
||||
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
||||
import { DiscoverAppStateContainer } from '../discover_app_state_container';
|
||||
import { addLog } from '../../../../utils/add_log';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
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
|
||||
*/
|
||||
export async function changeDataView(
|
||||
id: string | DataView,
|
||||
{
|
||||
services,
|
||||
internalState,
|
||||
appState,
|
||||
}: {
|
||||
services: DiscoverServices;
|
||||
internalState: DiscoverInternalStateContainer;
|
||||
appState: DiscoverAppStateContainer;
|
||||
}
|
||||
) {
|
||||
addLog('[ui] changeDataView', { id });
|
||||
export async function changeDataView({
|
||||
dataViewId,
|
||||
services,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
appState,
|
||||
}: {
|
||||
dataViewId: string | DataView;
|
||||
services: DiscoverServices;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
appState: DiscoverAppStateContainer;
|
||||
}) {
|
||||
addLog('[ui] changeDataView', { id: dataViewId });
|
||||
|
||||
const { dataViews, uiSettings } = services;
|
||||
const dataView = internalState.getState().dataView;
|
||||
const currentDataView = runtimeStateManager.currentDataView$.getValue();
|
||||
const state = appState.getState();
|
||||
let nextDataView: DataView | null = null;
|
||||
|
||||
internalState.transitions.setIsDataViewLoading(true);
|
||||
internalState.dispatch(internalStateActions.setIsDataViewLoading(true));
|
||||
|
||||
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.
|
||||
// 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
|
||||
}
|
||||
|
||||
if (nextDataView && dataView) {
|
||||
if (nextDataView && currentDataView) {
|
||||
// Reset the default profile state if we are switching to a different data view
|
||||
internalState.transitions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
});
|
||||
internalState.dispatch(
|
||||
internalStateActions.setResetDefaultProfileState({
|
||||
columns: true,
|
||||
rowHeight: true,
|
||||
breakdownField: true,
|
||||
})
|
||||
);
|
||||
|
||||
const nextAppState = getDataViewAppState(
|
||||
dataView,
|
||||
currentDataView,
|
||||
nextDataView,
|
||||
uiSettings.get(DEFAULT_COLUMNS_SETTING, []),
|
||||
state.columns || [],
|
||||
|
@ -79,9 +83,9 @@ export async function changeDataView(
|
|||
appState.update(nextAppState);
|
||||
|
||||
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,
|
||||
ProfilesManager,
|
||||
} from '../../../../context_awareness';
|
||||
import type { InternalState } from '../discover_internal_state_container';
|
||||
import type { DataDocumentsMsg } from '../discover_data_state_container';
|
||||
import type { DiscoverInternalState } from '../redux';
|
||||
|
||||
export const getDefaultProfileState = ({
|
||||
profilesManager,
|
||||
|
@ -25,7 +25,7 @@ export const getDefaultProfileState = ({
|
|||
dataView,
|
||||
}: {
|
||||
profilesManager: ProfilesManager;
|
||||
resetDefaultProfileState: InternalState['resetDefaultProfileState'];
|
||||
resetDefaultProfileState: DiscoverInternalState['resetDefaultProfileState'];
|
||||
dataView: DataView;
|
||||
}) => {
|
||||
const defaultState = getDefaultState(profilesManager, dataView);
|
||||
|
|
|
@ -12,7 +12,6 @@ import { cloneDeep, isEqual } from 'lodash';
|
|||
import { isOfAggregateQueryType } from '@kbn/es-query';
|
||||
import { getEsqlDataView } from './get_esql_data_view';
|
||||
import { loadAndResolveDataView } from './resolve_data_view';
|
||||
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
||||
import { DiscoverDataStateContainer } from '../discover_data_state_container';
|
||||
import { cleanupUrlState } from './cleanup_url_state';
|
||||
import { getValidFilters } from '../../../../utils/get_valid_filters';
|
||||
|
@ -27,11 +26,13 @@ import {
|
|||
import { DiscoverGlobalStateContainer } from '../discover_global_state_container';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
import { DataSourceType, isDataSourceType } from '../../../../../common/data_sources';
|
||||
import { InternalStateStore, RuntimeStateManager, internalStateActions } from '../redux';
|
||||
|
||||
interface LoadSavedSearchDeps {
|
||||
appStateContainer: DiscoverAppStateContainer;
|
||||
dataStateContainer: DiscoverDataStateContainer;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
savedSearchContainer: DiscoverSavedSearchContainer;
|
||||
globalStateContainer: DiscoverGlobalStateContainer;
|
||||
services: DiscoverServices;
|
||||
|
@ -51,7 +52,8 @@ export const loadSavedSearch = async (
|
|||
const { savedSearchId, initialAppState } = params ?? {};
|
||||
const {
|
||||
appStateContainer,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
savedSearchContainer,
|
||||
globalStateContainer,
|
||||
services,
|
||||
|
@ -75,7 +77,8 @@ export const loadSavedSearch = async (
|
|||
dataViewId,
|
||||
query: appState?.query,
|
||||
services,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -110,7 +113,8 @@ export const loadSavedSearch = async (
|
|||
query: appState.query,
|
||||
savedSearch: nextSavedSearch,
|
||||
services,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
});
|
||||
const dataViewDifferentToAppState = stateDataView.id !== savedSearchDataViewId;
|
||||
if (
|
||||
|
@ -142,7 +146,7 @@ export const loadSavedSearch = async (
|
|||
nextSavedSearch = savedSearchContainer.updateWithFilterManagerFilters();
|
||||
}
|
||||
|
||||
internalStateContainer.transitions.resetOnSavedSearchChange();
|
||||
internalState.dispatch(internalStateActions.resetOnSavedSearchChange());
|
||||
|
||||
return nextSavedSearch;
|
||||
};
|
||||
|
@ -153,12 +157,12 @@ export const loadSavedSearch = async (
|
|||
* @param deps
|
||||
*/
|
||||
function updateBySavedSearch(savedSearch: SavedSearch, deps: LoadSavedSearchDeps) {
|
||||
const { dataStateContainer, internalStateContainer, services, setDataView } = deps;
|
||||
const { dataStateContainer, internalState, services, setDataView } = deps;
|
||||
const savedSearchDataView = savedSearch.searchSource.getField('index')!;
|
||||
|
||||
setDataView(savedSearchDataView);
|
||||
if (!savedSearchDataView.isPersisted()) {
|
||||
internalStateContainer.transitions.appendAdHocDataViews(savedSearchDataView);
|
||||
internalState.dispatch(internalStateActions.appendAdHocDataViews(savedSearchDataView));
|
||||
}
|
||||
|
||||
// Finally notify dataStateContainer, data.query and filterManager about new derived state
|
||||
|
@ -196,13 +200,15 @@ const getStateDataView = async (
|
|||
query,
|
||||
savedSearch,
|
||||
services,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
}: {
|
||||
dataViewId?: string;
|
||||
query: DiscoverAppState['query'];
|
||||
savedSearch?: SavedSearch;
|
||||
services: DiscoverServices;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
}
|
||||
) => {
|
||||
const { dataView, dataViewSpec } = params;
|
||||
|
@ -222,7 +228,8 @@ const getStateDataView = async (
|
|||
savedSearch,
|
||||
isEsqlMode: isEsqlQuery,
|
||||
services,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
});
|
||||
|
||||
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 { ToastsStart } from '@kbn/core/public';
|
||||
import { SavedSearch } from '@kbn/saved-search-plugin/public';
|
||||
import { DiscoverInternalStateContainer } from '../discover_internal_state_container';
|
||||
import { DiscoverServices } from '../../../../build_services';
|
||||
import { InternalStateStore, RuntimeStateManager } from '../redux';
|
||||
|
||||
interface DataViewData {
|
||||
/**
|
||||
|
@ -174,18 +174,21 @@ export const loadAndResolveDataView = async ({
|
|||
dataViewSpec,
|
||||
savedSearch,
|
||||
isEsqlMode,
|
||||
internalStateContainer,
|
||||
internalState,
|
||||
runtimeStateManager,
|
||||
services,
|
||||
}: {
|
||||
dataViewId?: string;
|
||||
dataViewSpec?: DataViewSpec;
|
||||
savedSearch?: SavedSearch;
|
||||
isEsqlMode?: boolean;
|
||||
internalStateContainer: DiscoverInternalStateContainer;
|
||||
internalState: InternalStateStore;
|
||||
runtimeStateManager: RuntimeStateManager;
|
||||
services: DiscoverServices;
|
||||
}) => {
|
||||
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,
|
||||
// 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 { getDiscoverStateMock } from '../../__mocks__/discover_state.mock';
|
||||
import { discoverServiceMock } from '../../__mocks__/services';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import React from 'react';
|
||||
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 = ({
|
||||
rootProfileLoading,
|
||||
|
@ -23,20 +25,22 @@ const renderDefaultAdHocDataViewsHook = ({
|
|||
const clearInstanceCache = jest.spyOn(discoverServiceMock.dataViews, 'clearInstanceCache');
|
||||
const createDataView = jest
|
||||
.spyOn(discoverServiceMock.dataViews, 'create')
|
||||
.mockImplementation((spec) => Promise.resolve(spec as unknown as DataView));
|
||||
const existingAdHocDataVew = { id: '1', title: 'test' } as unknown as DataView;
|
||||
const previousSpecs = [
|
||||
{ id: '2', title: 'tes2' },
|
||||
{ id: '3', title: 'test3' },
|
||||
.mockImplementation((spec) => Promise.resolve(buildDataViewMock(omit(spec, 'fields'))));
|
||||
const existingAdHocDataVew = buildDataViewMock({ id: '1', title: 'test' });
|
||||
const previousDataViews = [
|
||||
buildDataViewMock({ id: '2', title: 'tes2' }),
|
||||
buildDataViewMock({ id: '3', title: 'test3' }),
|
||||
];
|
||||
const newSpecs = [
|
||||
{ id: '4', title: 'test4' },
|
||||
{ id: '5', title: 'test5' },
|
||||
const newDataViews = [
|
||||
buildDataViewMock({ id: '4', title: 'test4' }),
|
||||
buildDataViewMock({ id: '5', title: 'test5' }),
|
||||
];
|
||||
const stateContainer = getDiscoverStateMock({});
|
||||
stateContainer.internalState.transitions.appendAdHocDataViews(existingAdHocDataVew);
|
||||
stateContainer.internalState.transitions.setDefaultProfileAdHocDataViews(
|
||||
previousSpecs as unknown as DataView[]
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.appendAdHocDataViews(existingAdHocDataVew)
|
||||
);
|
||||
stateContainer.internalState.dispatch(
|
||||
internalStateActions.setDefaultProfileAdHocDataViews(previousDataViews)
|
||||
);
|
||||
const { result, unmount } = renderHook(useDefaultAdHocDataViews, {
|
||||
initialProps: {
|
||||
|
@ -44,7 +48,11 @@ const renderDefaultAdHocDataViewsHook = ({
|
|||
rootProfileState: {
|
||||
rootProfileLoading,
|
||||
AppWrapper: () => null,
|
||||
getDefaultAdHocDataViews: () => newSpecs,
|
||||
getDefaultAdHocDataViews: () =>
|
||||
newDataViews.map((dv) => {
|
||||
const { id, ...restSpec } = dv.toSpec();
|
||||
return { id: id!, ...restSpec };
|
||||
}),
|
||||
},
|
||||
},
|
||||
wrapper: ({ children }) => (
|
||||
|
@ -58,8 +66,8 @@ const renderDefaultAdHocDataViewsHook = ({
|
|||
createDataView,
|
||||
stateContainer,
|
||||
existingAdHocDataVew,
|
||||
previousSpecs,
|
||||
newSpecs,
|
||||
previousDataViews,
|
||||
newDataViews,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -75,27 +83,26 @@ describe('useDefaultAdHocDataViews', () => {
|
|||
createDataView,
|
||||
stateContainer,
|
||||
existingAdHocDataVew,
|
||||
previousSpecs,
|
||||
newSpecs,
|
||||
previousDataViews,
|
||||
newDataViews,
|
||||
} = renderDefaultAdHocDataViewsHook({ rootProfileLoading: false });
|
||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||
expect(createDataView).not.toHaveBeenCalled();
|
||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
||||
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||
existingAdHocDataVew,
|
||||
...previousSpecs,
|
||||
...previousDataViews,
|
||||
]);
|
||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousSpecs.map((s) => s.id)
|
||||
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousDataViews.map((dv) => dv.id)
|
||||
);
|
||||
await result.current.initializeProfileDataViews();
|
||||
expect(clearInstanceCache.mock.calls).toEqual(previousSpecs.map((s) => [s.id]));
|
||||
expect(createDataView.mock.calls).toEqual(newSpecs.map((s) => [s, true]));
|
||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
||||
existingAdHocDataVew,
|
||||
...newSpecs,
|
||||
]);
|
||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
||||
newSpecs.map((s) => s.id)
|
||||
expect(clearInstanceCache.mock.calls).toEqual(previousDataViews.map((dv) => [dv.id]));
|
||||
expect(createDataView.mock.calls).toEqual(newDataViews.map((dv) => [dv.toSpec(), true]));
|
||||
expect(
|
||||
stateContainer.runtimeStateManager.adHocDataViews$.getValue().map((dv) => dv.id)
|
||||
).toEqual([existingAdHocDataVew.id, ...newDataViews.map((dv) => dv.id)]);
|
||||
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||
newDataViews.map((dv) => dv.id)
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -106,43 +113,45 @@ describe('useDefaultAdHocDataViews', () => {
|
|||
createDataView,
|
||||
stateContainer,
|
||||
existingAdHocDataVew,
|
||||
previousSpecs,
|
||||
previousDataViews,
|
||||
} = renderDefaultAdHocDataViewsHook({ rootProfileLoading: true });
|
||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||
expect(createDataView).not.toHaveBeenCalled();
|
||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
||||
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||
existingAdHocDataVew,
|
||||
...previousSpecs,
|
||||
...previousDataViews,
|
||||
]);
|
||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousSpecs.map((s) => s.id)
|
||||
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousDataViews.map((dv) => dv.id)
|
||||
);
|
||||
await result.current.initializeProfileDataViews();
|
||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||
expect(createDataView).not.toHaveBeenCalled();
|
||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
||||
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||
existingAdHocDataVew,
|
||||
...previousSpecs,
|
||||
...previousDataViews,
|
||||
]);
|
||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousSpecs.map((s) => s.id)
|
||||
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousDataViews.map((dv) => dv.id)
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear instance cache on unmount', async () => {
|
||||
const { unmount, clearInstanceCache, stateContainer, existingAdHocDataVew, previousSpecs } =
|
||||
const { unmount, clearInstanceCache, stateContainer, existingAdHocDataVew, previousDataViews } =
|
||||
renderDefaultAdHocDataViewsHook({ rootProfileLoading: false });
|
||||
expect(clearInstanceCache).not.toHaveBeenCalled();
|
||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([
|
||||
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).toEqual([
|
||||
existingAdHocDataVew,
|
||||
...previousSpecs,
|
||||
...previousDataViews,
|
||||
]);
|
||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousSpecs.map((s) => s.id)
|
||||
expect(stateContainer.internalState.getState().defaultProfileAdHocDataViewIds).toEqual(
|
||||
previousDataViews.map((dv) => dv.id)
|
||||
);
|
||||
unmount();
|
||||
expect(clearInstanceCache.mock.calls).toEqual(previousSpecs.map((s) => [s.id]));
|
||||
expect(stateContainer.internalState.get().adHocDataViews).toEqual([existingAdHocDataVew]);
|
||||
expect(stateContainer.internalState.get().defaultProfileAdHocDataViewIds).toEqual([]);
|
||||
expect(clearInstanceCache.mock.calls).toEqual(previousDataViews.map((s) => [s.id]));
|
||||
expect(stateContainer.runtimeStateManager.adHocDataViews$.getValue()).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 { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
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
|
||||
|
@ -36,7 +37,7 @@ export const useDefaultAdHocDataViews = ({
|
|||
|
||||
// Clear the cache of old data views before creating
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -45,7 +46,7 @@ export const useDefaultAdHocDataViews = ({
|
|||
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
|
||||
|
@ -53,11 +54,11 @@ export const useDefaultAdHocDataViews = ({
|
|||
|
||||
// Make sure to clean up on unmount
|
||||
useUnmount(() => {
|
||||
for (const prevId of internalState.get().defaultProfileAdHocDataViewIds) {
|
||||
for (const prevId of internalState.getState().defaultProfileAdHocDataViewIds) {
|
||||
dataViews.clearInstanceCache(prevId);
|
||||
}
|
||||
|
||||
internalState.transitions.setDefaultProfileAdHocDataViews([]);
|
||||
internalState.dispatch(internalStateActions.setDefaultProfileAdHocDataViews([]));
|
||||
});
|
||||
|
||||
return { initializeProfileDataViews };
|
||||
|
|
|
@ -40,17 +40,14 @@ describe('useDiscoverCustomizationService', () => {
|
|||
customizationCallbacks: [callback],
|
||||
})
|
||||
);
|
||||
expect(wrapper.result.current.isInitialized).toBe(false);
|
||||
expect(wrapper.result.current.customizationService).toBeUndefined();
|
||||
expect(wrapper.result.current).toBeUndefined();
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
const cleanup = jest.fn();
|
||||
await act(async () => {
|
||||
resolveCallback(cleanup);
|
||||
await promise;
|
||||
});
|
||||
expect(wrapper.result.current.isInitialized).toBe(true);
|
||||
expect(wrapper.result.current.customizationService).toBeDefined();
|
||||
expect(wrapper.result.current.customizationService).toBe(service);
|
||||
expect(wrapper.result.current).toBe(service);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
expect(cleanup).not.toHaveBeenCalled();
|
||||
wrapper.unmount();
|
||||
|
|
|
@ -50,9 +50,7 @@ export const useDiscoverCustomizationService = ({
|
|||
};
|
||||
});
|
||||
|
||||
const isInitialized = Boolean(customizationService);
|
||||
|
||||
return { customizationService, isInitialized };
|
||||
return customizationService;
|
||||
};
|
||||
|
||||
export const useDiscoverCustomization$ = <TCustomizationId extends DiscoverCustomizationId>(
|
||||
|
|
|
@ -100,7 +100,8 @@
|
|||
"@kbn/esql-ast",
|
||||
"@kbn/discover-shared-plugin",
|
||||
"@kbn/response-ops-rule-form",
|
||||
"@kbn/embeddable-enhanced-plugin"
|
||||
"@kbn/embeddable-enhanced-plugin",
|
||||
"@kbn/shared-ux-page-analytics-no-data-types"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
|||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const browser = getService('browser');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const retry = getService('retry');
|
||||
const defaultSettings = { defaultIndex: 'logstash-*' };
|
||||
|
||||
|
@ -69,33 +68,5 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
|||
await browser.goBack();
|
||||
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 { 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';
|
||||
|
||||
export interface SecuritySolutionDiscoverState {
|
||||
app: DiscoverAppState | undefined;
|
||||
internal: InternalState | undefined;
|
||||
internal: DiscoverInternalState | 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 { createGlobalStyle } from 'styled-components';
|
||||
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 { useQuery } from '@tanstack/react-query';
|
||||
import { isEqualWith } from 'lodash';
|
||||
|
@ -219,7 +219,7 @@ export const DiscoverTabContent: FC<DiscoverTabContentProps> = ({ timelineId })
|
|||
next: setDiscoverAppState,
|
||||
});
|
||||
|
||||
const internalStateSubscription = stateContainer.internalState.state$.subscribe({
|
||||
const internalStateSubscription = from(stateContainer.internalState).subscribe({
|
||||
next: setDiscoverInternalState,
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
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 { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
@ -22,7 +22,7 @@ export const useDiscoverState = () => {
|
|||
const result = state.discover.app;
|
||||
return result;
|
||||
});
|
||||
const discoverInternalState = useSelector<State, InternalState | undefined>((state) => {
|
||||
const discoverInternalState = useSelector<State, DiscoverInternalState | undefined>((state) => {
|
||||
const result = state.discover.internal;
|
||||
return result;
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ export const useDiscoverState = () => {
|
|||
);
|
||||
|
||||
const setDiscoverInternalState = useCallback(
|
||||
(newState: InternalState) => {
|
||||
(newState: DiscoverInternalState) => {
|
||||
dispatch(updateDiscoverInternalState({ newState }));
|
||||
},
|
||||
[dispatch]
|
||||
|
|
|
@ -17,7 +17,6 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
|||
const kibanaServer = getService('kibanaServer');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const browser = getService('browser');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const retry = getService('retry');
|
||||
const defaultSettings = { defaultIndex: 'logstash-*' };
|
||||
|
||||
|
@ -67,33 +66,5 @@ export default ({ getService, getPageObjects }: FtrProviderContext) => {
|
|||
await browser.goBack();
|
||||
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