-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Security Solution] Add Search Bar to Security D&R and EA Dashboards (#…
…156832) More details on the issue: elastic/security-team#6504 ## TODO - [x] Unit tests - [ ] Cypress tests (follow-up PR) ## Summary * Add global search bar and filter to EA and D&R pages. * Create `useGlobalFilterQuery` hook to simplify adding global search bar filters to a page * Filter alert column in risk table by time range   <img width="1402" alt="Screenshot 2023-05-08 at 13 27 54" src="https://user-images.githubusercontent.com/1490444/236812677-e6021d99-4be1-44d7-8449-26f9330d8b78.png"> ### Tooltips explaining that some pages are not affected by the KQL search bar (Last minute addition) <img width="747" alt="Screenshot 2023-05-08 at 17 57 32" src="https://user-images.githubusercontent.com/1490444/236871990-3ebd60fa-ea45-4f98-a8d9-5813ac2b10de.png"> <img width="1512" alt="Screenshot 2023-05-08 at 17 57 37" src="https://user-images.githubusercontent.com/1490444/236871998-94969be6-b194-4d19-b83e-12f9b96eda1b.png"> <img width="1512" alt="Screenshot 2023-05-08 at 17 57 51" src="https://user-images.githubusercontent.com/1490444/236872002-5255f799-f30b-44f1-bd90-8f19037b6915.png"> ### Glossary * **EA:** Entity Analytics * **D&R:** Detection & Response ### Checklist - [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 (cherry picked from commit 7fd9ca6)
- Loading branch information
Showing
29 changed files
with
512 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* | ||
* 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 { TestProviders } from '../mock'; | ||
import { useGlobalFilterQuery } from './use_global_filter_query'; | ||
import type { Filter, Query } from '@kbn/es-query'; | ||
|
||
const DEFAULT_QUERY: Query = { query: '', language: 'kuery' }; | ||
|
||
const mockGlobalFiltersQuerySelector = jest.fn(); | ||
const mockGlobalQuerySelector = jest.fn(); | ||
const mockUseInvalidFilterQuery = jest.fn(); | ||
|
||
jest.mock('../store', () => { | ||
const original = jest.requireActual('../store'); | ||
return { | ||
...original, | ||
inputsSelectors: { | ||
...original.inputsSelectors, | ||
globalFiltersQuerySelector: () => mockGlobalFiltersQuerySelector, | ||
globalQuerySelector: () => mockGlobalQuerySelector, | ||
}, | ||
}; | ||
}); | ||
|
||
jest.mock('./use_invalid_filter_query', () => ({ | ||
useInvalidFilterQuery: (...args: unknown[]) => mockUseInvalidFilterQuery(...args), | ||
})); | ||
|
||
describe('useGlobalFilterQuery', () => { | ||
beforeEach(() => { | ||
mockGlobalFiltersQuerySelector.mockReturnValue([]); | ||
mockGlobalQuerySelector.mockReturnValue(DEFAULT_QUERY); | ||
}); | ||
|
||
it('returns filterQuery', () => { | ||
const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); | ||
|
||
expect(result.current.filterQuery).toEqual({ | ||
bool: { must: [], filter: [], should: [], must_not: [] }, | ||
}); | ||
}); | ||
|
||
it('filters by KQL search', () => { | ||
mockGlobalQuerySelector.mockReturnValue({ query: 'test: 123', language: 'kuery' }); | ||
const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); | ||
|
||
expect(result.current.filterQuery).toEqual({ | ||
bool: { | ||
must: [], | ||
filter: [ | ||
{ | ||
bool: { | ||
minimum_should_match: 1, | ||
should: [ | ||
{ | ||
match: { | ||
test: '123', | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
], | ||
should: [], | ||
must_not: [], | ||
}, | ||
}); | ||
}); | ||
|
||
it('filters by global filters', () => { | ||
const query = { | ||
match_phrase: { | ||
test: '1234', | ||
}, | ||
}; | ||
const globalFilter: Filter[] = [ | ||
{ | ||
meta: { | ||
disabled: false, | ||
}, | ||
query, | ||
}, | ||
]; | ||
mockGlobalFiltersQuerySelector.mockReturnValue(globalFilter); | ||
const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); | ||
|
||
expect(result.current.filterQuery).toEqual({ | ||
bool: { | ||
must: [], | ||
filter: [query], | ||
should: [], | ||
must_not: [], | ||
}, | ||
}); | ||
}); | ||
|
||
it('filters by extra filter', () => { | ||
const query = { | ||
match_phrase: { | ||
test: '12345', | ||
}, | ||
}; | ||
const extraFilter: Filter = { | ||
meta: { | ||
disabled: false, | ||
}, | ||
query, | ||
}; | ||
|
||
const { result } = renderHook(() => useGlobalFilterQuery({ extraFilter }), { | ||
wrapper: TestProviders, | ||
}); | ||
|
||
expect(result.current.filterQuery).toEqual({ | ||
bool: { | ||
must: [], | ||
filter: [query], | ||
should: [], | ||
must_not: [], | ||
}, | ||
}); | ||
}); | ||
|
||
it('displays the KQL error when query is invalid', () => { | ||
mockGlobalQuerySelector.mockReturnValue({ query: ': :', language: 'kuery' }); | ||
const { result } = renderHook(() => useGlobalFilterQuery(), { wrapper: TestProviders }); | ||
|
||
expect(result.current.filterQuery).toEqual(undefined); | ||
expect(mockUseInvalidFilterQuery).toHaveBeenLastCalledWith( | ||
expect.objectContaining({ | ||
kqlError: expect.anything(), | ||
}) | ||
); | ||
}); | ||
}); |
78 changes: 78 additions & 0 deletions
78
x-pack/plugins/security_solution/public/common/hooks/use_global_filter_query.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/* | ||
* 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 { useMemo } from 'react'; | ||
import { getEsQueryConfig } from '@kbn/data-plugin/common'; | ||
import type { DataViewBase, EsQueryConfig, Filter, Query } from '@kbn/es-query'; | ||
import { buildEsQuery } from '@kbn/es-query'; | ||
import { useGlobalTime } from '../containers/use_global_time'; | ||
import { useKibana } from '../lib/kibana'; | ||
import { inputsSelectors } from '../store'; | ||
import { useDeepEqualSelector } from './use_selector'; | ||
import { useInvalidFilterQuery } from './use_invalid_filter_query'; | ||
import type { ESBoolQuery } from '../../../common/typed_json'; | ||
|
||
interface GlobalFilterQueryProps { | ||
extraFilter?: Filter; | ||
dataView?: DataViewBase; | ||
} | ||
|
||
/** | ||
* It builds a global filterQuery from KQL search bar and global filters. | ||
* It also validates the query and shows a warning if it's invalid. | ||
*/ | ||
export const useGlobalFilterQuery = ({ extraFilter, dataView }: GlobalFilterQueryProps = {}) => { | ||
const { from, to } = useGlobalTime(); | ||
const getGlobalFiltersQuerySelector = useMemo( | ||
() => inputsSelectors.globalFiltersQuerySelector(), | ||
[] | ||
); | ||
const getGlobalQuerySelector = useMemo(() => inputsSelectors.globalQuerySelector(), []); | ||
const query = useDeepEqualSelector(getGlobalQuerySelector); | ||
const globalFilters = useDeepEqualSelector(getGlobalFiltersQuerySelector); | ||
const { uiSettings } = useKibana().services; | ||
|
||
const filters = useMemo(() => { | ||
const enabledFilters = globalFilters.filter((f) => f.meta.disabled === false); | ||
|
||
return extraFilter ? [...enabledFilters, extraFilter] : enabledFilters; | ||
}, [extraFilter, globalFilters]); | ||
|
||
const { filterQuery, kqlError } = useMemo( | ||
() => buildQueryOrError(query, filters, getEsQueryConfig(uiSettings), dataView), | ||
[dataView, query, filters, uiSettings] | ||
); | ||
|
||
const filterQueryStringified = useMemo( | ||
() => (filterQuery ? JSON.stringify(filterQuery) : undefined), | ||
[filterQuery] | ||
); | ||
|
||
useInvalidFilterQuery({ | ||
id: 'GlobalFilterQuery', // It prevents displaying multiple times the same error popup | ||
filterQuery: filterQueryStringified, | ||
kqlError, | ||
query, | ||
startDate: from, | ||
endDate: to, | ||
}); | ||
|
||
return { filterQuery }; | ||
}; | ||
|
||
const buildQueryOrError = ( | ||
query: Query, | ||
filters: Filter[], | ||
config: EsQueryConfig, | ||
dataView?: DataViewBase | ||
): { filterQuery?: ESBoolQuery; kqlError?: Error } => { | ||
try { | ||
return { filterQuery: buildEsQuery(dataView, [query], filters, config) }; | ||
} catch (kqlError) { | ||
return { kqlError }; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.