[8.6] [TIP] Fix broken refresh button on the indicators page (#146526) (#147011)

# Backport

This will backport the following commits from `main` to `8.6`:
- [[TIP] Fix broken refresh button on the indicators page
(#146526)](https://github.com/elastic/kibana/pull/146526)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Luke
Gmys","email":"lgmys@users.noreply.github.com"},"sourceCommit":{"committedDate":"2022-12-05T15:12:53Z","message":"[TIP]
Fix broken refresh button on the indicators page (#146526)\n\n##
Summary\r\n\r\nThis PR fixes the broken refresh button noticed during
the 8.6 demo.\r\n\r\nIncludes an e2e test to verify if it is working or
not.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"e89ba22953eba604fb61fe1d95ec5cbc7d22a6b8","branchLabelMapping":{"^v8.7.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:prev-minor","Team:
Protections
Experience","v8.7.0"],"number":146526,"url":"https://github.com/elastic/kibana/pull/146526","mergeCommit":{"message":"[TIP]
Fix broken refresh button on the indicators page (#146526)\n\n##
Summary\r\n\r\nThis PR fixes the broken refresh button noticed during
the 8.6 demo.\r\n\r\nIncludes an e2e test to verify if it is working or
not.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"e89ba22953eba604fb61fe1d95ec5cbc7d22a6b8"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.7.0","labelRegex":"^v8.7.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/146526","number":146526,"mergeCommit":{"message":"[TIP]
Fix broken refresh button on the indicators page (#146526)\n\n##
Summary\r\n\r\nThis PR fixes the broken refresh button noticed during
the 8.6 demo.\r\n\r\nIncludes an e2e test to verify if it is working or
not.\r\n\r\n### Checklist\r\n\r\nDelete any items that are not
applicable to this PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\nCo-authored-by: Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"e89ba22953eba604fb61fe1d95ec5cbc7d22a6b8"}}]}]
BACKPORT-->

Co-authored-by: Luke Gmys <lgmys@users.noreply.github.com>
This commit is contained in:
Kibana Machine 2022-12-05 11:45:20 -05:00 committed by GitHub
parent f3c36f206b
commit de3b26d832
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 135 additions and 21 deletions

View file

@ -24,6 +24,8 @@ import { useSourcererDataView } from '../common/containers/sourcerer';
import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper';
import { SiemSearchBar } from '../common/components/search_bar';
import { useGlobalTime } from '../common/containers/use_global_time';
import { deleteOneQuery, setQuery } from '../common/store/inputs/actions';
import { InputsModelId } from '../common/store/inputs/constants';
const ThreatIntelligence = memo(() => {
const { threatIntelligence } = useKibana().services;
@ -34,17 +36,36 @@ const ThreatIntelligence = memo(() => {
const securitySolutionStore = getStore() as Store;
const securitySolutionContext: SecuritySolutionPluginContext = {
securitySolutionStore,
getFiltersGlobalComponent: () => FiltersGlobal,
getPageWrapper: () => SecuritySolutionPageWrapper,
licenseService,
sourcererDataView: sourcererDataView as unknown as SourcererDataView,
getSecuritySolutionStore: securitySolutionStore,
getUseInvestigateInTimeline: useInvestigateInTimeline,
useQuery: () => useSelector(inputsSelectors.globalQuerySelector()),
useFilters: () => useSelector(inputsSelectors.globalFiltersQuerySelector()),
useGlobalTime,
registerQuery: (query) =>
securitySolutionStore.dispatch(
setQuery({
inputId: InputsModelId.global,
id: query.id,
refetch: query.refetch,
inspect: null,
loading: query.loading,
})
),
deregisterQuery: (query) =>
securitySolutionStore.dispatch(
deleteOneQuery({
inputId: InputsModelId.global,
id: query.id,
})
),
SiemSearchBar,
};

View file

@ -40,6 +40,7 @@ import {
TABLE_CONTROLS,
TIME_RANGE_PICKER,
TOGGLE_FLYOUT_BUTTON,
REFRESH_BUTTON,
} from '../screens/indicators';
import { login } from '../tasks/login';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
@ -232,6 +233,18 @@ describe('Indicators', () => {
cy.get(TABLE_CONTROLS).should('contain.text', 'Showing 1-25 of');
});
it('should reload the data when refresh button is pressed', () => {
cy.intercept(/bsearch/).as('search');
cy.get(REFRESH_BUTTON).should('exist').click();
cy.wait('@search');
cy.get(REFRESH_BUTTON).should('exist').click();
cy.wait('@search');
});
describe('No items match search criteria', () => {
before(() => {
// Contradictory filter set

View file

@ -198,3 +198,5 @@ export const INSPECTOR_BUTTON = '[data-test-subj="tiIndicatorsGridInspect"]';
export const INSPECTOR_PANEL = '[data-test-subj="inspectorPanel"]';
export const ADD_INTEGRATIONS_BUTTON = '[data-test-subj="add-data"]';
export const REFRESH_BUTTON = '[data-test-subj="querySubmitButton"]';

View file

@ -28,7 +28,7 @@ export const getSecuritySolutionContextMock = (): SecuritySolutionPluginContext
indexPattern: { fields: [], title: '' },
loading: false,
},
getSecuritySolutionStore: {
securitySolutionStore: {
// @ts-ignore
dispatch: () => jest.fn(),
},
@ -44,4 +44,8 @@ export const getSecuritySolutionContextMock = (): SecuritySolutionPluginContext
useGlobalTime: () => ({ from: '', to: '' }),
useQuery: () => ({ language: 'kuery', query: '' }),
registerQuery: () => {},
deregisterQuery: () => {},
});

View file

@ -95,6 +95,11 @@ describe('useAggregatedIndicators()', () => {
"isFetching": false,
"isLoading": false,
"onFieldChange": [Function],
"query": Object {
"id": "indicatorsBarchart",
"loading": false,
"refetch": [Function],
},
"selectedField": "threat.feed.name",
"series": Array [],
}

View file

@ -56,10 +56,14 @@ export interface UseAggregatedIndicatorsValue {
/** Is data update in progress? */
isFetching?: boolean;
query: { refetch: VoidFunction; id: string; loading: boolean };
}
const DEFAULT_FIELD = RawIndicatorFieldId.Feed;
const QUERY_ID = 'indicatorsBarchart';
export const useAggregatedIndicators = ({
timeRange,
filters,
@ -87,9 +91,9 @@ export const useAggregatedIndicators = ({
[inspectorAdapters, queryService, searchService]
);
const { data, isLoading, isFetching } = useQuery(
const { data, isLoading, isFetching, refetch } = useQuery(
[
'indicatorsBarchart',
QUERY_ID,
{
filters,
field,
@ -113,6 +117,11 @@ export const useAggregatedIndicators = ({
[queryService.timefilter.timefilter, timeRange]
);
const query = useMemo(
() => ({ refetch, id: QUERY_ID, loading: isLoading }),
[isLoading, refetch]
);
return {
dateRange,
series: data || [],
@ -120,5 +129,6 @@ export const useAggregatedIndicators = ({
selectedField: field,
isLoading,
isFetching,
query,
};
};

View file

@ -106,7 +106,6 @@ describe('useIndicators()', () => {
expect(hookResult.result.current).toMatchInlineSnapshot(`
Object {
"dataUpdatedAt": 0,
"handleRefresh": [Function],
"indicatorCount": 0,
"indicators": Array [],
"isFetching": false,
@ -122,6 +121,11 @@ describe('useIndicators()', () => {
50,
],
},
"query": Object {
"id": "indicatorsTable",
"loading": false,
"refetch": [Function],
},
}
`);
});

View file

@ -26,8 +26,6 @@ export interface UseIndicatorsParams {
}
export interface UseIndicatorsValue {
handleRefresh: () => void;
/**
* Array of {@link Indicator} ready to render inside the IndicatorTable component
*/
@ -48,8 +46,12 @@ export interface UseIndicatorsValue {
isFetching: boolean;
dataUpdatedAt: number;
query: { refetch: VoidFunction; id: string; loading: boolean };
}
const QUERY_ID = 'indicatorsTable';
export const useIndicators = ({
filters,
filterQuery,
@ -98,7 +100,7 @@ export const useIndicators = ({
const { isLoading, isFetching, data, refetch, dataUpdatedAt } = useQuery(
[
'indicatorsTable',
QUERY_ID,
{
timeRange,
filterQuery,
@ -119,10 +121,10 @@ export const useIndicators = ({
}
);
const handleRefresh = useCallback(() => {
onChangePage(0);
refetch();
}, [onChangePage, refetch]);
const query = useMemo(
() => ({ refetch, id: QUERY_ID, loading: isLoading }),
[isLoading, refetch]
);
return {
indicators: data?.indicators || [],
@ -132,7 +134,7 @@ export const useIndicators = ({
onChangeItemsPerPage,
isLoading,
isFetching,
handleRefresh,
dataUpdatedAt,
query,
};
};

View file

@ -30,6 +30,11 @@ describe('<IndicatorsPage />', () => {
series: [],
selectedField: '',
onFieldChange: () => {},
query: {
id: 'chart',
loading: false,
refetch: stub,
},
});
(useIndicators as jest.MockedFunction<typeof useIndicators>).mockReturnValue({
@ -40,8 +45,12 @@ describe('<IndicatorsPage />', () => {
pagination: { pageIndex: 0, pageSize: 10, pageSizeOptions: [10] },
onChangeItemsPerPage: stub,
onChangePage: stub,
handleRefresh: stub,
dataUpdatedAt: Date.now(),
query: {
id: 'list',
loading: false,
refetch: stub,
},
});
(useFilters as jest.MockedFunction<typeof useFilters>).mockReturnValue({

View file

@ -16,8 +16,8 @@ import { FieldTypesProvider } from '../../../containers/field_types_provider';
import { InspectorProvider } from '../../../containers/inspector';
import { useColumnSettings } from '../components/table/hooks';
import { IndicatorsFilters } from '../containers/filters';
import { useSecurityContext } from '../../../hooks';
import { UpdateStatus } from '../../../components/update_status';
import { QueryBar } from '../../query_bar/query_bar';
const IndicatorsPageProviders: FC = ({ children }) => (
<IndicatorsFilters>
@ -43,6 +43,7 @@ const IndicatorsPageContent: VFC = () => {
isLoading: isLoadingIndicators,
isFetching: isFetchingIndicators,
dataUpdatedAt,
query: indicatorListQuery,
} = useIndicators({
filters,
filterQuery,
@ -57,14 +58,13 @@ const IndicatorsPageContent: VFC = () => {
onFieldChange,
isLoading: isLoadingAggregatedIndicators,
isFetching: isFetchingAggregatedIndicators,
query: indicatorChartQuery,
} = useAggregatedIndicators({
timeRange,
filters,
filterQuery,
});
const { SiemSearchBar } = useSecurityContext();
return (
<FieldTypesProvider>
<DefaultPageLayout
@ -72,7 +72,10 @@ const IndicatorsPageContent: VFC = () => {
subHeader={<UpdateStatus isUpdating={isFetchingIndicators} updatedAt={dataUpdatedAt} />}
>
<FiltersGlobal>
<SiemSearchBar indexPattern={indexPattern} id="global" />
<QueryBar
queries={[indicatorChartQuery, indicatorListQuery]}
indexPattern={indexPattern}
/>
</FiltersGlobal>
<IndicatorsBarChartWrapper

View file

@ -0,0 +1,31 @@
/*
* 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 React, { useEffect, VFC } from 'react';
import { useSecurityContext } from '../../hooks/use_security_context';
import { SecuritySolutionDataViewBase } from '../../types';
interface QueryBarProps {
indexPattern: SecuritySolutionDataViewBase;
queries: Array<{
id: string;
refetch: VoidFunction;
loading: boolean;
}>;
}
export const QueryBar: VFC<QueryBarProps> = ({ indexPattern, queries }) => {
const { SiemSearchBar, registerQuery, deregisterQuery } = useSecurityContext();
useEffect(() => {
queries.forEach(registerQuery);
return () => queries.forEach(deregisterQuery);
}, [queries, deregisterQuery, registerQuery]);
return <SiemSearchBar id="global" indexPattern={indexPattern} />;
};

View file

@ -32,7 +32,7 @@ const LazyIndicatorsPageWrapper = React.lazy(() => import('./containers/indicato
/**
* This is used here:
* x-pack/plugins/security_solution/public/threat_intelligence/pages/threat_intelligence.tsx
* x-pack/plugins/security_solution/public/threat_intelligence/routes.tsx
*/
export const createApp =
(services: Services) =>
@ -40,7 +40,7 @@ export const createApp =
({ securitySolutionContext }: AppProps) =>
(
<IntlProvider>
<ReduxStoreProvider store={securitySolutionContext.getSecuritySolutionStore}>
<ReduxStoreProvider store={securitySolutionContext.securitySolutionStore}>
<SecuritySolutionContext.Provider value={securitySolutionContext}>
<KibanaContextProvider services={services}>
<EnterpriseGuard>

View file

@ -99,7 +99,7 @@ export interface SecuritySolutionPluginContext {
/**
* Security Solution store
*/
getSecuritySolutionStore: Store;
securitySolutionStore: Store;
/**
* Pass UseInvestigateInTimeline functionality to TI plugin
*/
@ -114,4 +114,14 @@ export interface SecuritySolutionPluginContext {
useGlobalTime: () => TimeRange;
SiemSearchBar: VFC<any>;
/**
* Register query in security solution store for tracking and centralized refresh support
*/
registerQuery: (query: { id: string; loading: boolean; refetch: VoidFunction }) => void;
/**
* Deregister stale query
*/
deregisterQuery: (query: { id: string }) => void;
}