diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx index 5da07f8add864..5600d231df19a 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/filter_group.test.tsx @@ -95,7 +95,11 @@ const getStoreWithCustomState = (newState: typeof state = state) => { return createStore(newState, SUB_PLUGINS_REDUCER, kibanaObservable, storage); }; -const TestComponent: FC> = (props) => ( +const TestComponent: FC< + ComponentProps & { + filterGroupProps?: Partial>; + } +> = (props) => ( > = (props) => ( chainingSystem="HIERARCHICAL" onFilterChange={onFilterChangeMock} onInit={onInitMock} + {...props.filterGroupProps} /> ); @@ -522,6 +527,36 @@ describe(' Filter Group Component ', () => { expect(screen.queryByTestId(TEST_IDS.SAVE_CHANGE_POPOVER)).toBeVisible(); }); }); + it('should update controlGroup with new filters and queries when valid query is supplied', async () => { + const validQuery = { query: { language: 'kuery', query: '' } }; + // pass an invalid query + render(); + + await waitFor(() => { + expect(controlGroupMock.updateInput).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + filters: undefined, + query: validQuery.query, + }) + ); + }); + }); + + it('should not update controlGroup with new filters and queries when invalid query is supplied', async () => { + const invalidQuery = { query: { language: 'kuery', query: '\\' } }; + // pass an invalid query + render(); + + await waitFor(() => { + expect(controlGroupMock.updateInput).toHaveBeenCalledWith( + expect.objectContaining({ + filters: [], + query: undefined, + }) + ); + }); + }); }); describe('Filter Changed Banner', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx b/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx index a4c61fb9c98a6..b821ffc6ca227 100644 --- a/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/filter_group/index.tsx @@ -46,6 +46,7 @@ import { FilterGroupContext } from './filter_group_context'; import { NUM_OF_CONTROLS } from './config'; import { TEST_IDS } from './constants'; import { URL_PARAM_ARRAY_EXCEPTION_MSG } from './translations'; +import { convertToBuildEsQuery } from '../../lib/kuery'; const FilterWrapper = styled.div.attrs((props) => ({ className: props.className, @@ -149,14 +150,41 @@ const FilterGroupComponent = (props: PropsWithChildren) => { return cleanup; }, []); + const { filters: validatedFilters, query: validatedQuery } = useMemo(() => { + const [_, kqlError] = convertToBuildEsQuery({ + config: {}, + queries: query ? [query] : [], + filters: filters ?? [], + indexPattern: { fields: [], title: '' }, + }); + + // we only need to handle kqlError because control group can handle Lucene error + if (kqlError) { + /* + * Based on the behaviour from other components, + * ignore all filters and queries if there is some error + * in the input filters and queries + * + * */ + return { + filters: [], + query: undefined, + }; + } + return { + filters, + query, + }; + }, [filters, query]); + useEffect(() => { controlGroup?.updateInput({ + filters: validatedFilters, + query: validatedQuery, timeRange, - filters, - query, chainingSystem, }); - }, [timeRange, filters, query, chainingSystem, controlGroup]); + }, [timeRange, chainingSystem, controlGroup, validatedQuery, validatedFilters]); const handleInputUpdates = useCallback( (newInput: ControlGroupInput) => { @@ -171,7 +199,7 @@ const FilterGroupComponent = (props: PropsWithChildren) => { [setControlGroupInputUpdates, getStoredControlInput, isViewMode, setHasPendingChanges] ); - const handleFilterUpdates = useCallback( + const handleOutputFilterUpdates = useCallback( ({ filters: newFilters }: ControlGroupOutput) => { if (isEqual(currentFiltersRef.current, newFilters)) return; if (onFilterChange) onFilterChange(newFilters ?? []); @@ -181,8 +209,8 @@ const FilterGroupComponent = (props: PropsWithChildren) => { ); const debouncedFilterUpdates = useMemo( - () => debounce(handleFilterUpdates, 500), - [handleFilterUpdates] + () => debounce(handleOutputFilterUpdates, 500), + [handleOutputFilterUpdates] ); useEffect(() => {