mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Uptime] Drop requests for overview page data when filter not initialized (#120031)
* Drop requests for overview page data when the value for the es filters has not been initially set. * Update to not wait when no filters are defined. * Add tests and clean up some files. * Delete unneeded code. * Reduce check condition to single boolean value to avoid triggering effect multiple times. * Simplify implementation, fix race. Update tests and fix types. * Make `useOverviewFilterCheck` consider kuery search as well. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
18d7793230
commit
e8bf0392f0
15 changed files with 249 additions and 54 deletions
|
@ -14,6 +14,7 @@ import { useGetUrlParams } from '../../../hooks';
|
|||
import { useMonitorId } from '../../../hooks';
|
||||
import { ResponsiveWrapperProps, withResponsiveWrapper } from '../../common/higher_order';
|
||||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
import { useOverviewFilterCheck } from '../../../hooks/use_overview_filter_check';
|
||||
|
||||
interface Props {
|
||||
height: string;
|
||||
|
@ -27,6 +28,7 @@ const Container: React.FC<Props & ResponsiveWrapperProps> = ({ height }) => {
|
|||
dateRangeStart: dateStart,
|
||||
dateRangeEnd: dateEnd,
|
||||
} = useGetUrlParams();
|
||||
const filterCheck = useOverviewFilterCheck();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const monitorId = useMonitorId();
|
||||
|
@ -38,8 +40,10 @@ const Container: React.FC<Props & ResponsiveWrapperProps> = ({ height }) => {
|
|||
const { loading, pingHistogram: data } = useSelector(selectPingHistogram);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getPingHistogram.get({ monitorId, dateStart, dateEnd, query, filters: esKuery }));
|
||||
}, [dateStart, dateEnd, monitorId, lastRefresh, esKuery, dispatch, query]);
|
||||
filterCheck(() =>
|
||||
dispatch(getPingHistogram.get({ monitorId, dateStart, dateEnd, query, filters: esKuery }))
|
||||
);
|
||||
}, [filterCheck, dateStart, dateEnd, monitorId, lastRefresh, esKuery, dispatch, query]);
|
||||
return (
|
||||
<PingHistogramComponent
|
||||
data={data}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useIndexPattern, useUpdateKueryString } from '../../../../hooks';
|
||||
import { useIndexPattern, generateUpdatedKueryString } from '../../../../hooks';
|
||||
import { useFetcher } from '../../../../../../observability/public';
|
||||
import { fetchSnapshotCount } from '../../../../state/api';
|
||||
|
||||
|
@ -17,7 +17,7 @@ export const useSnapShotCount = ({ query, filters }: { query: string; filters: [
|
|||
|
||||
const indexPattern = useIndexPattern();
|
||||
|
||||
const [esKuery, error] = useUpdateKueryString(indexPattern, query, parsedFilters);
|
||||
const [esKuery, error] = generateUpdatedKueryString(indexPattern, query, parsedFilters);
|
||||
|
||||
const { data, loading } = useFetcher(
|
||||
() =>
|
||||
|
|
|
@ -14,6 +14,7 @@ import { useUrlParams } from '../../../hooks';
|
|||
import { UptimeRefreshContext } from '../../../contexts';
|
||||
import { getConnectorsAction, getMonitorAlertsAction } from '../../../state/alerts/alerts';
|
||||
import { useMappingCheck } from '../../../hooks/use_mapping_check';
|
||||
import { useOverviewFilterCheck } from '../../../hooks/use_overview_filter_check';
|
||||
|
||||
export interface MonitorListProps {
|
||||
filters?: string;
|
||||
|
@ -31,6 +32,7 @@ const getPageSizeValue = () => {
|
|||
|
||||
export const MonitorList: React.FC<MonitorListProps> = (props) => {
|
||||
const filters = useSelector(esKuerySelector);
|
||||
const filterCheck = useOverviewFilterCheck();
|
||||
|
||||
const [pageSize, setPageSize] = useState<number>(getPageSizeValue);
|
||||
|
||||
|
@ -45,22 +47,25 @@ export const MonitorList: React.FC<MonitorListProps> = (props) => {
|
|||
useMappingCheck(monitorList.error);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getMonitorList({
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
filters,
|
||||
pageSize,
|
||||
pagination,
|
||||
statusFilter,
|
||||
query,
|
||||
})
|
||||
filterCheck(() =>
|
||||
dispatch(
|
||||
getMonitorList({
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
filters,
|
||||
pageSize,
|
||||
pagination,
|
||||
statusFilter,
|
||||
query,
|
||||
})
|
||||
)
|
||||
);
|
||||
}, [
|
||||
dispatch,
|
||||
dateRangeStart,
|
||||
dateRangeEnd,
|
||||
filters,
|
||||
filterCheck,
|
||||
lastRefresh,
|
||||
pageSize,
|
||||
pagination,
|
||||
|
|
|
@ -49,7 +49,7 @@ describe.skip('useQueryBar', () => {
|
|||
);
|
||||
useUrlParamsSpy = jest.spyOn(URL, 'useUrlParams');
|
||||
useGetUrlParamsSpy = jest.spyOn(URL, 'useGetUrlParams');
|
||||
useUpdateKueryStringSpy = jest.spyOn(ES_FILTERS, 'useUpdateKueryString');
|
||||
useUpdateKueryStringSpy = jest.spyOn(ES_FILTERS, 'generateUpdatedKueryString');
|
||||
updateUrlParamsMock = jest.fn();
|
||||
|
||||
useUrlParamsSpy.mockImplementation(() => [jest.fn(), updateUrlParamsMock]);
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Query } from 'src/plugins/data/common';
|
|||
import {
|
||||
useGetUrlParams,
|
||||
useIndexPattern,
|
||||
useUpdateKueryString,
|
||||
generateUpdatedKueryString,
|
||||
useUrlParams,
|
||||
} from '../../../hooks';
|
||||
import { setEsKueryString } from '../../../state/actions';
|
||||
|
@ -74,7 +74,7 @@ export const useQueryBar = (): UseQueryBarUtils => {
|
|||
|
||||
const [, updateUrlParams] = useUrlParams();
|
||||
|
||||
const [esFilters, error] = useUpdateKueryString(
|
||||
const [esFilters, error] = generateUpdatedKueryString(
|
||||
indexPattern,
|
||||
query.language === SyntaxType.kuery ? (query.query as string) : undefined,
|
||||
paramFilters,
|
||||
|
|
|
@ -41,7 +41,7 @@ const getKueryString = (urlFilters: string, excludedFilters?: string): string =>
|
|||
return `NOT (${excludeKueryString})`;
|
||||
};
|
||||
|
||||
export const useUpdateKueryString = (
|
||||
export const generateUpdatedKueryString = (
|
||||
indexPattern: IndexPattern | null,
|
||||
filterQueryString = '',
|
||||
urlFilters: string,
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import React from 'react';
|
||||
import * as reactRedux from 'react-redux';
|
||||
import { useOverviewFilterCheck } from './use_overview_filter_check';
|
||||
import { MockRouter } from '../lib/helper/rtl_helpers';
|
||||
|
||||
function getWrapper(customSearch?: string): React.FC {
|
||||
return ({ children }) => {
|
||||
const { location, ...rest } = createMemoryHistory();
|
||||
return (
|
||||
<MockRouter
|
||||
history={{
|
||||
...rest,
|
||||
location: {
|
||||
...location,
|
||||
search: customSearch ? customSearch : location.search,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MockRouter>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
const SEARCH_WITH_FILTERS = '?dateRangeStart=now-30m&filters=%5B%5B"url.port"%2C%5B"5601"%5D%5D%5D';
|
||||
const SEARCH_WITH_KUERY = '?search=monitor.id%20%3A%20"header-test"';
|
||||
|
||||
describe('useOverviewFilterCheck', () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(reactRedux, 'useSelector').mockImplementation(() => false);
|
||||
});
|
||||
|
||||
it('returns a function that will run code when there are no filters', () => {
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useOverviewFilterCheck(), { wrapper: getWrapper() });
|
||||
|
||||
const fn = jest.fn();
|
||||
current(fn);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns a function that will not run code if there are uninitialized filters', () => {
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useOverviewFilterCheck(), {
|
||||
wrapper: getWrapper(SEARCH_WITH_FILTERS),
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
current(fn);
|
||||
expect(fn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns a function that will run code if filters are initialized', () => {
|
||||
jest.spyOn(reactRedux, 'useSelector').mockImplementation(() => true);
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useOverviewFilterCheck(), {
|
||||
wrapper: getWrapper(SEARCH_WITH_FILTERS),
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
current(fn);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns a function that will not run code if search is uninitialized', () => {
|
||||
jest.spyOn(reactRedux, 'useSelector').mockImplementation(() => '');
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useOverviewFilterCheck(), {
|
||||
wrapper: getWrapper(SEARCH_WITH_KUERY),
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
current(fn);
|
||||
expect(fn).not.toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('returns a function that will run if search is initialized', () => {
|
||||
jest.spyOn(reactRedux, 'useSelector').mockImplementation(() => 'search is initialized');
|
||||
const {
|
||||
result: { current },
|
||||
} = renderHook(() => useOverviewFilterCheck(), {
|
||||
wrapper: getWrapper(SEARCH_WITH_KUERY),
|
||||
});
|
||||
|
||||
const fn = jest.fn();
|
||||
current(fn);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { getParsedParams } from '../lib/helper/parse_search';
|
||||
import { esKuerySelector } from '../state/selectors';
|
||||
|
||||
function hasFilters(search: string) {
|
||||
const parsed = getParsedParams(search);
|
||||
|
||||
return !!parsed.filters || !!parsed.search;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifically designed for the overview page, this hook will create
|
||||
* a function that the caller can use to run code only once the filter
|
||||
* index pattern has been initialized.
|
||||
*
|
||||
* In the case where no filters are
|
||||
* defined in the URL path, the check will pass and call the function.
|
||||
*/
|
||||
export function useOverviewFilterCheck() {
|
||||
const filters = useSelector(esKuerySelector);
|
||||
const { search } = useLocation();
|
||||
|
||||
/**
|
||||
* Here, `filters` represents the pre-processed output of parsing the kuery
|
||||
* syntax and unifying it with any top-level filters the user has selected.
|
||||
*
|
||||
* The `hasFilters` flag will be true when the URL contains a truthy `filters`
|
||||
* query key, _or_ a truthy `search` key. The callback `shouldRun` if:
|
||||
*
|
||||
* 1. `filters` are defined: the initial processing has finished and the app is
|
||||
* ready to send its initial requests.
|
||||
* 2. There are no search/filters defined in the URL, i.e. `!hasFilters === true`.
|
||||
*/
|
||||
const shouldRun = !!filters || !hasFilters(search);
|
||||
|
||||
return useCallback(
|
||||
(fn: () => void) => {
|
||||
if (shouldRun) {
|
||||
fn();
|
||||
}
|
||||
},
|
||||
[shouldRun]
|
||||
);
|
||||
}
|
|
@ -6,13 +6,14 @@
|
|||
*/
|
||||
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { parse, stringify } from 'query-string';
|
||||
import { stringify } from 'query-string';
|
||||
import { useLocation, useHistory } from 'react-router-dom';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { UptimeUrlParams, getSupportedUrlParams } from '../lib/helper';
|
||||
import { selectedFiltersSelector } from '../state/selectors';
|
||||
import { setSelectedFilters } from '../state/actions/selected_filters';
|
||||
import { getFiltersFromMap } from './use_selected_filters';
|
||||
import { getParsedParams } from '../lib/helper/parse_search';
|
||||
|
||||
export type GetUrlParams = () => UptimeUrlParams;
|
||||
export type UpdateUrlParams = (updatedParams: {
|
||||
|
@ -21,10 +22,6 @@ export type UpdateUrlParams = (updatedParams: {
|
|||
|
||||
export type UptimeUrlParamsHook = () => [GetUrlParams, UpdateUrlParams];
|
||||
|
||||
const getParsedParams = (search: string) => {
|
||||
return search ? parse(search[0] === '?' ? search.slice(1) : search, { sort: false }) : {};
|
||||
};
|
||||
|
||||
export const useGetUrlParams: GetUrlParams = () => {
|
||||
const { search } = useLocation();
|
||||
|
||||
|
|
28
x-pack/plugins/uptime/public/lib/helper/parse_search.test.ts
Normal file
28
x-pack/plugins/uptime/public/lib/helper/parse_search.test.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getParsedParams } from './parse_search';
|
||||
|
||||
describe('getParsedParams', () => {
|
||||
it('parses the query operator out', () => {
|
||||
expect(getParsedParams('?val1=3&val2=5')).toEqual({
|
||||
val1: '3',
|
||||
val2: '5',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty object for no search value', () => {
|
||||
expect(getParsedParams('')).toEqual({});
|
||||
});
|
||||
|
||||
it('also parses queries if there is no query operator', () => {
|
||||
expect(getParsedParams('val1=3&val2=5')).toEqual({
|
||||
val1: '3',
|
||||
val2: '5',
|
||||
});
|
||||
});
|
||||
});
|
12
x-pack/plugins/uptime/public/lib/helper/parse_search.ts
Normal file
12
x-pack/plugins/uptime/public/lib/helper/parse_search.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { parse } from 'query-string';
|
||||
|
||||
export function getParsedParams(search: string) {
|
||||
return search ? parse(search[0] === '?' ? search.slice(1) : search, { sort: false }) : {};
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from 'react';
|
||||
import React, { ReactElement, ReactNode } from 'react';
|
||||
import { of } from 'rxjs';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import {
|
||||
|
@ -52,7 +52,7 @@ export interface KibanaProviderOptions<ExtraCore> {
|
|||
}
|
||||
|
||||
interface MockKibanaProviderProps<ExtraCore> extends KibanaProviderOptions<ExtraCore> {
|
||||
children: React.ReactNode;
|
||||
children: ReactElement | ReactNode;
|
||||
}
|
||||
|
||||
interface MockRouterProps<ExtraCore> extends MockKibanaProviderProps<ExtraCore> {
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ui reducer adds integration popover status to state 1`] = `
|
||||
Object {
|
||||
"alertFlyoutVisible": false,
|
||||
"basePath": "",
|
||||
"esKuery": "",
|
||||
"integrationsPopoverOpen": Object {
|
||||
"id": "popover-2",
|
||||
"open": true,
|
||||
},
|
||||
"monitorId": "test",
|
||||
"searchText": "",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`ui reducer sets the application's base path 1`] = `
|
||||
Object {
|
||||
"alertFlyoutVisible": false,
|
||||
"basePath": "yyz",
|
||||
"esKuery": "",
|
||||
"integrationsPopoverOpen": null,
|
||||
"monitorId": "test",
|
||||
"searchText": "",
|
||||
}
|
||||
`;
|
|
@ -29,7 +29,16 @@ describe('ui reducer', () => {
|
|||
},
|
||||
action
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"alertFlyoutVisible": false,
|
||||
"basePath": "yyz",
|
||||
"esKuery": "",
|
||||
"integrationsPopoverOpen": null,
|
||||
"monitorId": "test",
|
||||
"searchText": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('adds integration popover status to state', () => {
|
||||
|
@ -49,7 +58,19 @@ describe('ui reducer', () => {
|
|||
},
|
||||
action
|
||||
)
|
||||
).toMatchSnapshot();
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"alertFlyoutVisible": false,
|
||||
"basePath": "",
|
||||
"esKuery": "",
|
||||
"integrationsPopoverOpen": Object {
|
||||
"id": "popover-2",
|
||||
"open": true,
|
||||
},
|
||||
"monitorId": "test",
|
||||
"searchText": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('updates the alert flyout value', () => {
|
||||
|
|
|
@ -58,7 +58,6 @@ export const uiReducer = handleActions<UiState, UiPayload>(
|
|||
...state,
|
||||
esKuery: action.payload as string,
|
||||
}),
|
||||
|
||||
[String(setAlertFlyoutType)]: (state, action: Action<string>) => ({
|
||||
...state,
|
||||
alertFlyoutType: action.payload,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue