diff --git a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.test.tsx b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.test.tsx new file mode 100644 index 0000000000000..a7ae24e88dd71 --- /dev/null +++ b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.test.tsx @@ -0,0 +1,171 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { Filter, FilterStateStore } from '@kbn/es-query'; +import { ToastsStart } from '@kbn/core-notifications-browser'; +import { useLoadRuleTypesQuery, useRuleAADFields, useAlertsDataView } from '../common/hooks'; +import { AlertsSearchBar } from '.'; +import { HttpStart } from '@kbn/core-http-browser'; + +const mockDataPlugin = dataPluginMock.createStartContract(); +jest.mock('@kbn/kibana-utils-plugin/public'); +jest.mock('../common/hooks'); + +jest.mocked(useAlertsDataView).mockReturnValue({ + isLoading: false, + dataView: { + title: '.alerts-*', + fields: [ + { + name: 'event.action', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, +}); + +jest.mocked(useLoadRuleTypesQuery).mockReturnValue({ + ruleTypesState: { + isInitialLoad: false, + data: new Map(), + isLoading: false, + error: null, + }, + authorizedToReadAnyRules: false, + hasAnyAuthorizedRuleType: false, + authorizedRuleTypes: [], + authorizedToCreateAnyRules: false, + isSuccess: false, +}); + +jest.mocked(useRuleAADFields).mockReturnValue({ + aadFields: [], + loading: false, +}); + +const unifiedSearchBarMock = jest.fn().mockImplementation((props) => ( + +)); + +const toastsMock = { toasts: { addWarning: jest.fn() } } as unknown as ToastsStart; +const httpMock = { + post: jest.fn(), +} as unknown as HttpStart; + +describe('AlertsSearchBar', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + render( + + ); + expect(await screen.findByTestId('querySubmitButton')).toBeInTheDocument(); + }); + + it('calls onQuerySubmit correctly', async () => { + const onQuerySubmitMock = jest.fn(); + + render( + + ); + + fireEvent.click(await screen.findByTestId('querySubmitButton')); + + await waitFor(() => { + expect(onQuerySubmitMock).toHaveBeenCalled(); + }); + }); + + it('calls onFiltersUpdated correctly', async () => { + const onFiltersUpdatedMock = jest.fn(); + const filters: Filter[] = [ + { + meta: { + negate: false, + alias: null, + disabled: false, + type: 'custom', + key: 'query', + }, + query: { bool: { filter: [{ term: { 'kibana.alert.rule.consumer': 'stackAlerts' } }] } }, + $state: { store: FilterStateStore.APP_STATE }, + }, + ]; + + const newUnifiedSearchBarMock = jest.fn().mockImplementation((props) => ( + + )); + + render( + + ); + + fireEvent.click(await screen.findByTestId('filtersSubmitButton')); + + await waitFor(() => { + expect(onFiltersUpdatedMock).toHaveBeenCalledWith(filters); + expect(mockDataPlugin.query.filterManager.setFilters).toHaveBeenCalledWith(filters); + }); + }); +}); diff --git a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx index efda77df2c406..00822d013e0d2 100644 --- a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx +++ b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/index.tsx @@ -41,14 +41,14 @@ export const AlertsSearchBar = ({ http, toasts, unifiedSearchBar, - dataViewsService, + dataService, }: AlertsSearchBarProps) => { const [queryLanguage, setQueryLanguage] = useState('kuery'); const { dataView } = useAlertsDataView({ featureIds, http, toasts, - dataViewsService, + dataViewsService: dataService.dataViews, }); const { aadFields, loading: fieldsLoading } = useRuleAADFields({ ruleTypeId, @@ -119,7 +119,10 @@ export const AlertsSearchBar = ({ displayStyle: 'inPage', showFilterBar, onQuerySubmit: onSearchQuerySubmit, - onFiltersUpdated, + onFiltersUpdated: (newFilters) => { + dataService.query.filterManager.setFilters(newFilters); + onFiltersUpdated?.(newFilters); + }, onRefresh, showDatePicker, showQueryInput: true, diff --git a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts index 8875ef62947a8..f8566b2ec692c 100644 --- a/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts +++ b/packages/kbn-alerts-ui-shared/src/alerts_search_bar/types.ts @@ -11,7 +11,7 @@ import type { Filter } from '@kbn/es-query'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; import type { ToastsStart, HttpStart } from '@kbn/core/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; -import type { DataViewsContract } from '@kbn/data-views-plugin/common'; +import { DataPublicPluginStart } from '@kbn/data-plugin/public'; export type QueryLanguageType = 'lucene' | 'kuery'; @@ -41,5 +41,5 @@ export interface AlertsSearchBarProps { http: HttpStart; toasts: ToastsStart; unifiedSearchBar: UnifiedSearchPublicPluginStart['ui']['SearchBar']; - dataViewsService: DataViewsContract; + dataService: DataPublicPluginStart; } diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx index d3ef68206cfa4..9b7502add4045 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.test.tsx @@ -9,12 +9,12 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { httpServiceMock } from '@kbn/core/public/mocks'; -import { RuleActionsAlertsFilter } from './rule_actions_alerts_filter'; -import type { AlertsSearchBarProps } from '../../alerts_search_bar'; import { FilterStateStore } from '@kbn/es-query'; +import { AlertsSearchBarProps, AlertsSearchBar } from '../../alerts_search_bar'; import { getAction } from '../../common/test_utils/actions_test_utils'; -import userEvent from '@testing-library/user-event'; +import { RuleActionsAlertsFilter } from './rule_actions_alerts_filter'; const http = httpServiceMock.createStartContract(); @@ -23,43 +23,7 @@ jest.mock('../hooks', () => ({ })); jest.mock('../../alerts_search_bar', () => ({ - AlertsSearchBar: ({ onFiltersUpdated, onQueryChange, onQuerySubmit }: AlertsSearchBarProps) => ( -
- AlertsSearchBar - - - -
- ), + AlertsSearchBar: jest.fn(), })); const { useRuleFormState } = jest.requireMock('../hooks'); @@ -79,7 +43,7 @@ describe('ruleActionsAlertsFilter', () => { SearchBar: {}, }, }, - dataViews: {}, + data: {}, }, formData: { actions: [], @@ -93,6 +57,46 @@ describe('ruleActionsAlertsFilter', () => { }); }); + (AlertsSearchBar as jest.Mock).mockImplementation( + ({ onFiltersUpdated, onQueryChange, onQuerySubmit }: AlertsSearchBarProps) => ( +
+ AlertsSearchBar + + + +
+ ) + ); + afterEach(() => { jest.clearAllMocks(); }); @@ -166,18 +170,86 @@ describe('ruleActionsAlertsFilter', () => { await userEvent.click(screen.getByText('Update Filter')); expect(mockOnChange).toHaveBeenLastCalledWith({ - filters: [{ $state: { store: 'appState' }, meta: {} }], + filters: [{ $state: { store: 'appState' }, meta: {}, query: {} }], kql: 'test', }); await userEvent.click(screen.getByText('Update Query')); expect(mockOnChange).toHaveBeenLastCalledWith({ - filters: [{ $state: { store: 'appState' }, meta: {} }], + filters: [{ $state: { store: 'appState' }, meta: {}, query: {} }], kql: 'onQueryChange', }); await userEvent.click(screen.getByText('Submit Query')); expect(mockOnChange).toHaveBeenLastCalledWith({ - filters: [{ $state: { store: 'appState' }, meta: {} }], + filters: [{ $state: { store: 'appState' }, meta: {}, query: {} }], kql: 'onQuerySubmit', }); }); + + test('renders filters correctly', async () => { + const filters = [ + { + meta: { + negate: false, + alias: null, + disabled: false, + type: 'custom', + key: 'query', + }, + query: { bool: { filter: [{ term: { 'kibana.alert.rule.consumer': 'stackAlerts' } }] } }, + $state: { store: FilterStateStore.APP_STATE }, + }, + ]; + + (AlertsSearchBar as jest.Mock).mockImplementation( + ({ onFiltersUpdated, onQueryChange, onQuerySubmit }: AlertsSearchBarProps) => ( +
+ AlertsSearchBar + + + +
+ ) + ); + + render( + + ); + + await userEvent.click(screen.getByTestId('alertsFilterQueryToggle')); + + expect(mockOnChange).toHaveBeenLastCalledWith(undefined); + + await userEvent.click(screen.getByText('Update Filter')); + expect(mockOnChange).toHaveBeenLastCalledWith({ filters, kql: '' }); + }); }); diff --git a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx index 646d1b359969b..791c1ce0491f2 100644 --- a/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx +++ b/packages/kbn-alerts-ui-shared/src/rule_form/rule_actions/rule_actions_alerts_filter.tsx @@ -31,7 +31,7 @@ export interface RuleActionsAlertsFilterProps { http: RuleFormPlugins['http']; notifications: RuleFormPlugins['notifications']; unifiedSearch: RuleFormPlugins['unifiedSearch']; - dataViews: RuleFormPlugins['dataViews']; + data: RuleFormPlugins['data']; }; } @@ -48,7 +48,7 @@ export const RuleActionsAlertsFilter = ({ http, notifications: { toasts }, unifiedSearch, - dataViews, + data, } = propsPlugins || plugins; const [query, setQuery] = useState(action.alertsFilter?.query ?? DEFAULT_QUERY); @@ -84,7 +84,19 @@ export const RuleActionsAlertsFilter = ({ ); const onFiltersUpdated = useCallback( - (filters: Filter[]) => updateQuery({ filters }), + (filters: Filter[]) => { + const updatedFilters = filters.map((filter) => { + const { $state, meta, ...rest } = filter; + return { + $state, + meta, + query: filter?.query ? { ...filter.query } : { ...rest }, + }; + }); + + // Wrapping filters in query object here to avoid schema validation failure + updateQuery({ filters: updatedFilters }); + }, [updateQuery] ); @@ -108,7 +120,7 @@ export const RuleActionsAlertsFilter = ({ http={http} toasts={toasts} unifiedSearchBar={unifiedSearch.ui.SearchBar} - dataViewsService={dataViews} + dataService={data} appName={appName} featureIds={featureIds} ruleTypeId={ruleTypeId} diff --git a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx index 6783289963f6c..7f3c25eeaf55d 100644 --- a/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx +++ b/x-pack/plugins/alerting/public/pages/maintenance_windows/components/create_maintenance_windows_form.tsx @@ -117,6 +117,17 @@ export const CreateMaintenanceWindowForm = React.memo { + return filtersToTransform.map((filter) => { + const { $state, meta, ...rest } = filter; + return { + $state, + meta, + query: filter?.query ? { ...filter.query } : { ...rest }, + }; + }); + }; + const scopedQueryPayload = useMemo(() => { if (!isScopedQueryEnabled) { return null; @@ -124,9 +135,13 @@ export const CreateMaintenanceWindowForm = React.memo diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts index 97c9ba686435a..df1dce08f684a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.test.ts @@ -11,9 +11,10 @@ import { isValidUrl, getConnectorWithInvalidatedFields, getRuleWithInvalidatedFields, + validateActionFilterQuery, } from './value_validators'; import { v4 as uuidv4 } from 'uuid'; -import { Rule, IErrorObject, UserConfiguredActionConnector } from '../../types'; +import { Rule, IErrorObject, UserConfiguredActionConnector, RuleUiAction } from '../../types'; describe('throwIfAbsent', () => { test('throws if value is absent', () => { @@ -441,3 +442,70 @@ describe('getRuleWithInvalidatedFields', () => { expect((rule.actions[1].params as any).incident.field.name).toEqual('myIncident'); }); }); + +describe('validateActionFilterQuery', () => { + test('does not return an error when kql query exists', () => { + const actionItem: RuleUiAction = { + actionTypeId: 'test', + group: 'qwer', + id: '123', + params: { + incident: { + field: { + name: 'myIncident', + }, + }, + }, + alertsFilter: { query: { kql: 'id: *', filters: [] } }, + }; + + expect(validateActionFilterQuery(actionItem)).toBe(null); + }); + + test('does not return an error when filter query exists', () => { + const actionItem = { + actionTypeId: 'test', + group: 'qwer', + id: '123', + params: { + incident: { + field: { + name: 'myIncident', + }, + }, + }, + alertsFilter: { + query: { + kql: undefined, + filters: [ + { + $state: { store: 'state' }, + meta: { key: 'test' }, + query: { exists: { field: '_id' } }, + }, + ], + }, + }, + }; + + expect(validateActionFilterQuery(actionItem)).toBe(null); + }); + + test('returns an error no kql and no filters', () => { + const actionItem = { + actionTypeId: 'test', + group: 'qwer', + id: '123', + params: { + incident: { + field: { + name: 'myIncident', + }, + }, + }, + alertsFilter: { query: { kql: '', filters: [] } }, + }; + + expect(validateActionFilterQuery(actionItem)).toBe('A custom query is required.'); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index 9176d9d54ef3a..9af97b713dff3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -157,7 +157,7 @@ export const ActionTypeForm = ({ http, notifications, unifiedSearch, - dataViews, + data, } = useKibana().services; const { euiTheme } = useEuiTheme(); const [isOpen, setIsOpen] = useState(true); @@ -513,7 +513,7 @@ export const ActionTypeForm = ({ plugins={{ http, unifiedSearch, - dataViews, + data, notifications, }} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.test.tsx new file mode 100644 index 0000000000000..61fceacead341 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.test.tsx @@ -0,0 +1,190 @@ +/* + * 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 from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { useAlertsDataView } from '@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'; +import { Filter, FilterStateStore } from '@kbn/es-query'; +import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { NotificationsStart } from '@kbn/core-notifications-browser'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { useLoadRuleTypesQuery } from '../../hooks/use_load_rule_types_query'; +import { useRuleAADFields } from '../../hooks/use_rule_aad_fields'; +import { AlertsSearchBar } from './alerts_search_bar'; + +const mockDataPlugin = dataPluginMock.createStartContract(); +jest.mock('@kbn/kibana-utils-plugin/public'); +jest.mock('../../hooks/use_load_rule_types_query'); +jest.mock('../../hooks/use_rule_aad_fields'); +jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_alerts_data_view'); +jest.mock('@kbn/kibana-react-plugin/public', () => ({ + ...jest.requireActual('@kbn/kibana-react-plugin/public'), + useKibana: jest.fn(), +})); + +jest.mocked(useAlertsDataView).mockReturnValue({ + isLoading: false, + dataView: { + title: '.alerts-*', + fields: [ + { + name: 'event.action', + type: 'string', + aggregatable: true, + searchable: true, + }, + ], + }, +}); + +jest.mocked(useLoadRuleTypesQuery).mockReturnValue({ + ruleTypesState: { + initialLoad: false, + data: new Map(), + isLoading: false, + error: undefined, + }, + authorizedToReadAnyRules: false, + hasAnyAuthorizedRuleType: false, + authorizedRuleTypes: [], + authorizedToCreateAnyRules: false, + isSuccess: false, +}); + +jest.mocked(useRuleAADFields).mockReturnValue({ + aadFields: [], + loading: false, +}); + +const mockUseKibana = useKibana as jest.Mock; + +describe('AlertsSearchBar', () => { + beforeEach(() => { + mockUseKibana.mockReturnValue({ + services: { + data: mockDataPlugin, + unifiedSearch: { + ui: { + SearchBar: jest.fn().mockImplementation((props) => ( + + )), + }, + }, + notifications: { toasts: { addWarning: jest.fn() } } as unknown as NotificationsStart, + }, + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + render( + + ); + expect(await screen.findByTestId('querySubmitButton')).toBeInTheDocument(); + }); + + it('calls onQuerySubmit correctly', async () => { + const onQuerySubmitMock = jest.fn(); + + render( + + ); + + fireEvent.click(await screen.findByTestId('querySubmitButton')); + + await waitFor(() => { + expect(onQuerySubmitMock).toHaveBeenCalled(); + }); + }); + + it('calls onFiltersUpdated correctly', async () => { + const onFiltersUpdatedMock = jest.fn(); + const filters: Filter[] = [ + { + meta: { + negate: false, + alias: null, + disabled: false, + type: 'custom', + key: 'query', + }, + query: { bool: { filter: [{ term: { 'kibana.alert.rule.consumer': 'stackAlerts' } }] } }, + $state: { store: FilterStateStore.APP_STATE }, + }, + ]; + + mockUseKibana.mockReturnValue({ + services: { + data: mockDataPlugin, + unifiedSearch: { + ui: { + SearchBar: jest.fn().mockImplementation((props) => ( + + )), + }, + }, + notifications: { toasts: { addWarning: jest.fn() } } as unknown as NotificationsStart, + }, + }); + + render( + + ); + + fireEvent.click(await screen.findByTestId('filtersSubmitButton')); + + await waitFor(() => { + expect(onFiltersUpdatedMock).toHaveBeenCalledWith(filters); + expect(mockDataPlugin.query.filterManager.setFilters).toHaveBeenCalledWith(filters); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx index 3896e5d0e938a..5e2880e65231c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_search_bar/alerts_search_bar.tsx @@ -53,6 +53,7 @@ export function AlertsSearchBar({ unifiedSearch: { ui: { SearchBar }, }, + data: dataService, } = useKibana().services; const [queryLanguage, setQueryLanguage] = useState('kuery'); @@ -183,7 +184,11 @@ export function AlertsSearchBar({ displayStyle="inPage" showFilterBar={showFilterBar} onQuerySubmit={onSearchQuerySubmit} - onFiltersUpdated={onFiltersUpdated} + onFiltersUpdated={(newFilters) => { + const mappedFilters = structuredClone(newFilters); + dataService.query.filterManager.setFilters(mappedFilters); + onFiltersUpdated?.(mappedFilters); + }} onRefresh={onRefresh} showDatePicker={showDatePicker} showQueryInput={true} diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts index bf4a81ea4772d..3c39bd235bf97 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts @@ -25,6 +25,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const toasts = getService('toasts'); const esClient = getService('es'); const apmSynthtraceKibanaClient = getService('apmSynthtraceKibanaClient'); + const filterBar = getService('filterBar'); + const esArchiver = getService('esArchiver'); async function getAlertsByName(name: string) { const { @@ -95,6 +97,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('create alert', function () { let apmSynthtraceEsClient: ApmSynthtraceEsClient; before(async () => { + await esArchiver.load( + 'test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword' + ); const version = (await apmSynthtraceKibanaClient.installApmPackage()).version; apmSynthtraceEsClient = await getApmSynthtraceEsClient({ client: esClient, @@ -130,7 +135,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return Promise.all([apmSynthtraceEsClient.index(events)]); }); - after(() => apmSynthtraceEsClient.clean()); + after(async () => { + await apmSynthtraceEsClient.clean(); + await esArchiver.unload( + 'test/api_integration/fixtures/es_archiver/index_patterns/constant_keyword' + ); + }); beforeEach(async () => { await pageObjects.common.navigateToApp('triggersActions'); @@ -344,6 +354,57 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); }); + it('should create an alert with DSL filter for conditional action', async () => { + const alertName = generateUniqueKey(); + await rules.common.defineIndexThresholdAlert(alertName); + + // filterKuery validation + await testSubjects.setValue('filterKuery', 'group:'); + const filterKueryInput = await testSubjects.find('filterKuery'); + expect(await filterKueryInput.elementHasClass('euiFieldSearch-isInvalid')).to.eql(true); + await testSubjects.setValue('filterKuery', 'group: group-0'); + expect(await filterKueryInput.elementHasClass('euiFieldSearch-isInvalid')).to.eql(false); + + await testSubjects.click('.slack-alerting-ActionTypeSelectOption'); + await testSubjects.click('addNewActionConnectorButton-.slack'); + const slackConnectorName = generateUniqueKey(); + await testSubjects.setValue('nameInput', slackConnectorName); + await testSubjects.setValue('slackWebhookUrlInput', 'https://test.com'); + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + const createdConnectorToastTitle = await toasts.getTitleAndDismiss(); + expect(createdConnectorToastTitle).to.eql(`Created '${slackConnectorName}'`); + await testSubjects.click('notifyWhenSelect'); + await testSubjects.click('onThrottleInterval'); + await testSubjects.setValue('throttleInput', '10'); + + await testSubjects.click('alertsFilterQueryToggle'); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + const filter = `{ + "bool": { + "filter": [{ "term": { "kibana.alert.rule.consumer": "*" } }] + } + }`; + await filterBar.addDslFilter(filter, true); + + await testSubjects.click('saveRuleButton'); + + const toastTitle = await toasts.getTitleAndDismiss(); + expect(toastTitle).to.eql(`Created rule "${alertName}"`); + + await testSubjects.click('editActionHoverButton'); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await testSubjects.scrollIntoView('globalQueryBar'); + + await filterBar.hasFilter('query', filter, true); + + // clean up created alert + const alertsToDelete = await getAlertsByName(alertName); + await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); + }); + it('should create an alert with actions in multiple groups', async () => { const alertName = generateUniqueKey(); await defineAlwaysFiringAlert(alertName); @@ -542,5 +603,34 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await deleteConnectorByName('webhook-test'); }); + + it('should add filter', async () => { + const ruleName = generateUniqueKey(); + await defineAlwaysFiringAlert(ruleName); + + await testSubjects.click('saveRuleButton'); + await testSubjects.existOrFail('confirmRuleSaveModal'); + await testSubjects.click('confirmRuleSaveModal > confirmModalConfirmButton'); + await testSubjects.missingOrFail('confirmRuleSaveModal'); + + const toastTitle = await toasts.getTitleAndDismiss(); + expect(toastTitle).to.eql(`Created rule "${ruleName}"`); + + await testSubjects.click('triggersActionsAlerts'); + + const filter = `{ + "bool": { + "filter": [{ "term": { "kibana.alert.rule.name": "${ruleName}" } }] + } + }`; + + await filterBar.addDslFilter(filter, true); + + await filterBar.hasFilter('query', filter, true); + + // clean up created alert + const alertsToDelete = await getAlertsByName(ruleName); + await deleteAlerts(alertsToDelete.map((alertItem: { id: string }) => alertItem.id)); + }); }); };