diff --git a/src/platform/packages/shared/response-ops/alerts-filters-form/components/alerts_solution_selector.tsx b/src/platform/packages/shared/response-ops/alerts-filters-form/components/alerts_solution_selector.tsx index f4a92d4638b38..0219a521b645c 100644 --- a/src/platform/packages/shared/response-ops/alerts-filters-form/components/alerts_solution_selector.tsx +++ b/src/platform/packages/shared/response-ops/alerts-filters-form/components/alerts_solution_selector.tsx @@ -71,6 +71,7 @@ export const AlertsSolutionSelector = forwardRef< { const toasts = getService('toasts'); const sampleData = getService('sampleData'); const rules = getService('rules'); - const es = getService('es'); - const config = getService('config'); - const retryTimeout = config.get('timeouts.try'); - // Failing: See https://github.com/elastic/kibana/issues/227748 - describe.skip('Embeddable alerts panel', () => { + describe('Embeddable alerts panel', () => { before(async () => { await sampleData.testResources.installAllKibanaSampleData(); @@ -59,18 +56,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { )!; const [stackRule, observabilityRule, securityRule] = await Promise.all([ - createEsQueryRule(sampleDataLogsDataView.id, 'stack'), - createEsQueryRule(sampleDataLogsDataView.id, 'observability'), - createSecurityRule(sampleDataLogsDataView.id), + createEsQueryRule(), + createCustomThresholdRule(sampleDataLogsDataView.id), + createSecurityRule(), ]); - await waitForRuleToBecomeActive(stackRule.id); - await waitForRuleToBecomeActive(observabilityRule.id); - await waitForRuleToBecomeActive(securityRule.id); - - await waitForAlertsToBeCreated(stackRule.id); - await waitForAlertsToBeCreated(observabilityRule.id); - await waitForAlertsToBeCreated(securityRule.id); + await waitForRuleToExecute(stackRule.id); + await waitForRuleToExecute(observabilityRule.id); + await waitForRuleToExecute(securityRule.id); await pageObjects.dashboard.gotoDashboardURL(); }); @@ -89,14 +82,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should ask for confirmation before resetting filters when switching solution', async () => { + await pageObjects.dashboard.gotoDashboardURL(); + await dashboardAddPanel.clickEditorMenuButton(); + await dashboardAddPanel.clickAddNewPanelFromUIActionLink('Alerts'); + + await testSubjects.click(SOLUTION_SELECTOR_SUBJ); + await find.clickByCssSelector(`[data-test-subj=${SOLUTION_SELECTOR_SUBJ}] button`); await find.clickByCssSelector(`button#observability`); + await find.clickByCssSelector(`[data-test-subj=${FILTERS_FORM_ITEM_SUBJ}] button`); await find.clickByCssSelector(`button#ruleTags`); await testSubjects.click('comboBoxToggleListButton'); const options = await comboBox.getOptions(RULE_TAGS_FILTER_SUBJ); await options[0].click(); - await testSubjects.click(SOLUTION_SELECTOR_SUBJ); + await find.clickByCssSelector(`[data-test-subj=${SOLUTION_SELECTOR_SUBJ}] button`); await find.clickByCssSelector(`button#security`); expect(await find.byButtonText('Switch solution')).to.be.ok(); @@ -120,6 +120,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { if (solution === 'stack' || solution === 'observability') { await testSubjects.missingOrFail(SOLUTION_SELECTOR_SUBJ); } + await find.clickByCssSelector(`[data-test-subj=${FILTERS_FORM_ITEM_SUBJ}] button`); await find.clickByCssSelector(`button#ruleTags`); await testSubjects.click('comboBoxToggleListButton'); @@ -150,7 +151,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await dashboardAddPanel.clickEditorMenuButton(); await dashboardAddPanel.clickAddNewPanelFromUIActionLink('Alerts'); await testSubjects.existOrFail(SOLUTION_SELECTOR_SUBJ); + await testSubjects.click(SOLUTION_SELECTOR_SUBJ); + await find.clickByCssSelector(`[data-test-subj=${SOLUTION_SELECTOR_SUBJ}] button`); await find.clickByCssSelector(`button#observability`); + await testSubjects.click(SAVE_CONFIG_BUTTON_SUBJ); await retry.try(() => testSubjects.exists(DASHBOARD_PANEL_TEST_SUBJ)); const featureCells = await find.allByCssSelector( @@ -232,34 +236,32 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); }); - const createEsQueryRule = async (index: string, solution: 'stack' | 'observability') => { - const name = `${solution}-rule`; + const createEsQueryRule = async () => { + const name = 'stack-rule'; const createdRule = await rules.api.createRule({ name, - ruleTypeId: `.es-query`, - schedule: { interval: '5s' }, - consumer: solution === 'stack' ? 'stackAlerts' : 'logs', + schedule: { + interval: '5s', + }, + consumer: 'stackAlerts', + ruleTypeId: '.es-query', + actions: [], tags: [name], params: { - searchConfiguration: { - query: { - query: '', - language: 'kuery', - }, - index, - }, - timeField: 'timestamp', - searchType: 'searchSource', + searchType: 'esQuery', timeWindowSize: 5, - timeWindowUnit: 'h', - threshold: [-1], + timeWindowUnit: 'd', + threshold: [0], thresholdComparator: '>', - size: 1, + size: 100, + esQuery: '{\n "query":{\n "match_all" : {}\n }\n }', aggType: 'count', groupBy: 'all', termSize: 5, excludeHitsFromPreviousRun: false, sourceFields: [], + index: ['kibana_sample_data_logs'], + timeField: '@timestamp', }, }); @@ -268,39 +270,68 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdRule; }; - const createSecurityRule = async (index: string) => { + const createCustomThresholdRule = async (dataView: string) => { + const name = 'observability-rule'; + const createdRule = await rules.api.createRule({ + name, + schedule: { + interval: '5s', + }, + consumer: 'logs', + ruleTypeId: 'observability.rules.custom_threshold', + actions: [], + tags: [name], + params: { + criteria: [ + { + comparator: '>', + metrics: [ + { + name: 'A', + aggType: 'count', + }, + ], + threshold: [0], + timeSize: 1, + timeUnit: 'd', + }, + ], + alertOnNoData: false, + alertOnGroupDisappear: false, + searchConfiguration: { + query: { + query: '', + language: 'kuery', + }, + index: dataView, + }, + }, + }); + + objectRemover.add(createdRule.id, 'rule', 'alerting'); + + return createdRule; + }; + + const createSecurityRule = async () => { + const name = 'security-rule'; + const { body: createdRule } = await supertest .post(`/api/detection_engine/rules`) .set('kbn-xsrf', 'foo') .send({ + name, + description: 'Spammy query rule', + enabled: true, + risk_score: 1, + rule_id: 'rule-1', + severity: 'low', type: 'query', - filters: [], - language: 'kuery', query: '_id: *', - required_fields: [], - data_view_id: index, - author: [], - false_positives: [], - references: [], - risk_score: 21, - risk_score_mapping: [], - severity: 'low', - severity_mapping: [], - threat: [], - max_signals: 100, - name: 'security-rule', - description: 'security-rule', - tags: ['security-rule'], - setup: '', - license: '', - interval: '5s', - from: 'now-10m', - to: 'now', - actions: [], - enabled: true, - meta: { - kibana_siem_app_url: 'http://localhost:5601/app/security', - }, + index: ['kibana_sample_data_logs'], + from: 'now-1y', + interval: '1m', + tags: [name], }) .expect(200); @@ -309,43 +340,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { return createdRule; }; - const waitForAlertsToBeCreated = async (ruleId: string) => { - return await retry.tryForTime(retryTimeout, async () => { - const response = await es.search({ - index: '.alerts*', - query: { - bool: { - filter: [ - { - term: { - 'kibana.alert.rule.uuid': ruleId, - }, - }, - ], - }, - }, + const waitForRuleToExecute = async (ruleId: string) => { + await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: 'default', + type: 'alert', + id: ruleId, + provider: 'alerting', + actions: new Map([['execute', { gte: 1 }]]), }); - - if (response.hits.hits.length === 0) { - throw new Error(`No hits found for index .alerts* and ruleId ${ruleId}`); - } - - return response; - }); - }; - - const waitForRuleToBecomeActive = async (ruleId: string) => { - return await retry.tryForTime(retryTimeout, async () => { - const rule = await rules.api.getRule(ruleId); - - const { execution_status: executionStatus } = rule || {}; - const { status } = executionStatus || {}; - - if (status === 'active' || status === 'ok') { - return executionStatus?.status; - } - - throw new Error(`waitForStatus(active|ok): got ${status}`); }); }; diff --git a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/kibana.jsonc b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/kibana.jsonc index 3d65e2b045586..61588b8fa5f7f 100644 --- a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/kibana.jsonc +++ b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/kibana.jsonc @@ -9,7 +9,8 @@ "requiredPlugins": [ "alerting", "triggersActionsUi", - "features" + "features", + "eventLog", ] } } diff --git a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts index e473cf7af2503..cbddef91de069 100644 --- a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts +++ b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/plugin.ts @@ -9,11 +9,14 @@ import { Plugin, CoreSetup } from '@kbn/core/server'; import { AlertingServerSetup, RuleType, RuleTypeParams } from '@kbn/alerting-plugin/server'; import { FeaturesPluginSetup } from '@kbn/features-plugin/server'; import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common'; +import type { IEventLogClientService } from '@kbn/event-log-plugin/server'; +import { registerRoutes } from './routes'; // this plugin's dependendencies export interface AlertingExampleDeps { alerting: AlertingServerSetup; features: FeaturesPluginSetup; + eventLog: IEventLogClientService; } export const noopAlertType: RuleType<{}, {}, {}, {}, {}, 'default'> = { @@ -106,11 +109,16 @@ export const failingAlertType: RuleType { - public setup(core: CoreSetup, { alerting, features }: AlertingExampleDeps) { +export class AlertingFixturePlugin + implements Plugin +{ + public setup(core: CoreSetup, { alerting, features }: AlertingExampleDeps) { alerting.registerType(noopAlertType); alerting.registerType(alwaysFiringAlertType); alerting.registerType(failingAlertType); + + registerRoutes(core); + features.registerKibanaFeature({ id: 'alerting_fixture', name: 'alerting_fixture', diff --git a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/routes.ts b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/routes.ts new file mode 100644 index 0000000000000..c98338cc493a0 --- /dev/null +++ b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/server/routes.ts @@ -0,0 +1,60 @@ +/* + * 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 type { + CoreSetup, + RequestHandlerContext, + KibanaRequest, + KibanaResponseFactory, + IKibanaResponse, +} from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { queryOptionsSchema } from '@kbn/event-log-plugin/server/event_log_client'; +import type { AlertingExampleDeps } from './plugin'; + +export const registerRoutes = (core: CoreSetup) => { + const router = core.http.createRouter(); + + router.get( + { + path: '/_test/event_log/{type}/{id}/_find', + security: { + authz: { + enabled: false, + reason: 'This route is opted out from authorization', + }, + }, + validate: { + params: schema.object({ + type: schema.string(), + id: schema.string(), + }), + query: queryOptionsSchema, + }, + }, + async ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> => { + const [, { eventLog }] = await core.getStartServices(); + const eventLogClient = eventLog.getClient(req); + const { + params: { id, type }, + query, + } = req; + + try { + return res.ok({ + body: await eventLogClient.findEventsBySavedObjectIds(type, [id], query), + }); + } catch (err) { + return res.notFound(); + } + } + ); +}; diff --git a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/tsconfig.json b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/tsconfig.json index fd7ae8469be9f..a6fcaa55d1241 100644 --- a/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/tsconfig.json +++ b/x-pack/platform/test/functional_with_es_ssl/plugins/alerts/tsconfig.json @@ -17,5 +17,7 @@ "@kbn/triggers-actions-ui-plugin", "@kbn/shared-ux-router", "@kbn/react-kibana-context-render", + "@kbn/event-log-plugin", + "@kbn/config-schema", ] }