From 3096a72884b42cdf8bb25004014bf4edbb5f6db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georgiana-Andreea=20Onolea=C8=9B=C4=83?= Date: Fri, 3 Apr 2026 12:43:16 +0300 Subject: [PATCH] [ResponseOps][MaintenanceWindow] Add filter button of DSL filter throws error on create/edit MWs (#259517) Closes https://github.com/elastic/kibana/issues/259514 ## Summary Fix crash in FilterEditor when submitting a Query DSL filter with no index patterns available. getFilterFromQueryDsl accessed indexPatterns[0].id without guarding against an empty array. - fixed also: filter not being displayed after creation, two separate guards were preventing DSL filter pills from rendering in Maintenance Windows (where indexPatterns / dataViews is an empty array): - fixed also edit added filter: indexPattern?.getName() fails because getName() is a method on the DataView class, not on plain DataViewBase objects. Fixed by using optional call getName?.() with fallbacks to name and title. Related to: https://github.com/elastic/kibana/pull/256622 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine (cherry picked from commit f6c836958dab7169a84f7c2ec19e2e758f823353) --- .../public/filter_badge/filter_badge.tsx | 4 -- .../filter_editor/filter_editor.test.tsx | 46 +++++++++++++++++++ .../filter_editor/filter_editor.tsx | 6 ++- .../public/search_bar/search_bar.test.tsx | 34 ++++++++++++++ .../public/search_bar/search_bar.tsx | 22 +++------ 5 files changed, 90 insertions(+), 22 deletions(-) diff --git a/src/platform/plugins/shared/unified_search/public/filter_badge/filter_badge.tsx b/src/platform/plugins/shared/unified_search/public/filter_badge/filter_badge.tsx index 1ae7541dd9159..31207a9c17290 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_badge/filter_badge.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_badge/filter_badge.tsx @@ -38,10 +38,6 @@ export function FilterBadge({ }: FilterBadgeProps) { const { euiTheme } = useEuiTheme(); - if (!dataViews.length) { - return null; - } - const prefixText = filter.meta.negate ? ` ${strings.getNotLabel()}` : ''; const prefix = diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx index 4b271fb3d7503..492df07ea45ea 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.test.tsx @@ -12,6 +12,7 @@ import type { UseEuiTheme, EuiThemeComputed } from '@elastic/eui'; import type { TestBed } from '@kbn/test-jest-helpers'; import { registerTestBed } from '@kbn/test-jest-helpers'; import { coreMock } from '@kbn/core/public/mocks'; +import { FilterStateStore } from '@kbn/es-query'; import type { FilterEditorProps } from '.'; import { FilterEditor } from '.'; import { dataViewMockList } from '../../dataview_picker/mocks/dataview'; @@ -83,6 +84,51 @@ describe('', () => { expect(find('saveFilter').props().disabled).toBe(false); }); }); + describe('submitting query dsl with no index patterns', () => { + it('should create filter when no index patterns are available', async () => { + const onSubmit = jest.fn(); + const defaultProps: Omit = { + theme: { + euiTheme: {} as unknown as EuiThemeComputed<{}>, + colorMode: 'DARK', + modifications: [], + highContrastMode: false, + } as UseEuiTheme<{}>, + filter: { + meta: { + type: 'custom', + } as any, + $state: { + store: FilterStateStore.APP_STATE, + }, + }, + indexPatterns: [], + onCancel: jest.fn(), + onSubmit, + docLinks: coreMock.createStart().docLinks, + dataViews: dataMock.dataViews, + }; + const testBed: TestBed = await registerTestBed(FilterEditor, { defaultProps })(); + const { find } = testBed; + + find('customEditorInput').simulate('change', { + target: { value: '{ "wildcard": { "kibana.alert.rule.name": "test*" } }' }, + }); + + find('saveFilter').simulate('click'); + expect(onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + wildcard: { 'kibana.alert.rule.name': 'test*' }, + meta: expect.objectContaining({ + type: 'custom', + disabled: false, + negate: false, + }), + }) + ); + }); + }); + describe('handling data view fallback', () => { let testBed: TestBed; diff --git a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx index 032488802dbd2..0ae4063167435 100644 --- a/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx +++ b/src/platform/plugins/shared/unified_search/public/filter_bar/filter_editor/filter_editor.tsx @@ -354,7 +354,9 @@ class FilterEditorComponent extends Component { placeholder={strings.getSelectDataView()} options={this.state.indexPatterns} selectedOptions={selectedDataView ? [selectedDataView] : []} - getLabel={(indexPattern) => indexPattern?.getName()} + getLabel={(indexPattern) => + indexPattern?.getName?.() ?? indexPattern?.name ?? indexPattern?.title ?? '' + } onChange={this.onIndexPatternChange} isClearable={false} data-test-subj="filterIndexPatternsSelect" @@ -576,7 +578,7 @@ class FilterEditorComponent extends Component { return; } - const newIndex = index || this.state.indexPatterns[0].id!; + const newIndex = index || this.state.indexPatterns[0]?.id || this.state.indexPatterns[0]?.title; try { const body = JSON.parse(queryDsl); return buildCustomFilter(newIndex, body, disabled, negate, customLabel || null, $state.store); diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx index 3fd243b24bd50..57ee0ee8b4207 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.test.tsx @@ -166,6 +166,40 @@ describe('SearchBar', () => { }); }); + it('Should render filter bar when filters exist but no index patterns are provided', async () => { + const dslFilter = { + meta: { + key: 'query', + value: '{"wildcard":{"kibana.alert.rule.name":"example*"}}', + type: 'custom', + disabled: false, + negate: false, + alias: null, + }, + query: { + wildcard: { + 'kibana.alert.rule.name': 'example*', + }, + }, + }; + + render( + wrapSearchBarInContext({ + indexPatterns: [], + showDatePicker: false, + showQueryInput: true, + showFilterBar: true, + onFiltersUpdated: noop, + filters: [dslFilter], + }) + ); + + await waitFor(() => { + expect(screen.getByTestId('globalQueryBar')).toBeInTheDocument(); + expect(screen.getByTestId('filter-items-group')).toBeInTheDocument(); + }); + }); + it('Should NOT render filter bar, if disabled', async () => { render( wrapSearchBarInContext({ diff --git a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx index 8126a9a4bdf3e..7dc07aa997df3 100644 --- a/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx +++ b/src/platform/plugins/shared/unified_search/public/search_bar/search_bar.tsx @@ -7,7 +7,6 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { compact } from 'lodash'; import type { InjectedIntl } from '@kbn/i18n-react'; import { FormattedMessage, injectI18n } from '@kbn/i18n-react'; import classNames from 'classnames'; @@ -337,15 +336,6 @@ export class SearchBarUI ex this.renderSavedQueryManagement.clear(); } - private shouldRenderFilterBar() { - return ( - this.props.showFilterBar && - this.props.filters && - this.props.indexPatterns && - compact(this.props.indexPatterns).length > 0 - ); - } - /* * This Function is here to show the toggle in saved query form * in case you the date range (from/to) @@ -599,7 +589,7 @@ export class SearchBarUI ex }; private shouldShowDatePickerAsBadge() { - return this.shouldRenderFilterBar() && !this.props.showQueryInput; + return this.props.showFilterBar && !this.props.showQueryInput; } public render() { @@ -693,12 +683,12 @@ export class SearchBarUI ex ) : undefined; let filterBar; - if (this.shouldRenderFilterBar()) { + if (this.props.showFilterBar) { filterBar = this.shouldShowDatePickerAsBadge() ? ( ex ) : (