From 0870aa5a34ac9d13ff34b11932b46d7135395390 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Fri, 31 May 2024 12:38:17 -0400 Subject: [PATCH 01/12] observability - add custom threshold functional test --- .../custom_equation/metric_row_with_agg.tsx | 3 +- .../common/expression_items/threshold.tsx | 2 +- .../observability/alerts/rules_page.ts | 116 ++++++++++++++++++ .../apps/observability/index.ts | 1 + .../pages/alerts/custom_threshold.ts | 112 +++++++++++++++++ 5 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx index a00a086ef5528..bde22fc45feac 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/custom_equation/metric_row_with_agg.tsx @@ -135,7 +135,7 @@ export function MetricRowWithAgg({ } > )} diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index 5e24ced438a68..6a61b5350cd86 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -154,7 +154,7 @@ export const ThresholdExpression = ({ error={errors[`threshold${i}`]} > 0 || isNil(threshold[i])} diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index 226d257ed918b..84ab6cb80ff41 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { Key } from 'selenium-webdriver'; import { FtrProviderContext } from '../../../ftr_provider_context'; export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderContext) { @@ -30,6 +31,118 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont await find.clickByButtonText('metric-threshold'); }; + const clickOnObservabilityCategory = async () => { + const categories = await testSubjects.find('ruleTypeModal'); + const category = await categories.findByCssSelector(`.euiFacetButton[title="Observability"]`); + await category.click(); + }; + + const clickOnCustomThresholdRule = async () => { + await testSubjects.click('observability.rules.custom_threshold-SelectOption'); + }; + + const fillCustomThresholdRule = async (name: string) => { + await testSubjects.setValue('ruleNameInput', name); + await testSubjects.setValue('comboBoxSearchInput', 'tag1'); + + // select data view + await testSubjects.click('selectDataViewExpression'); + await testSubjects.setValue('indexPattern-switcher--input', 'metricbeat-*'); + const dataViewExpression = await find.byCssSelector( + '[data-test-subj="indexPattern-switcher--input"]' + ); + await dataViewExpression.pressKeys(Key.ENTER); + + // select aggregation + await testSubjects.click('aggregationNameA'); + await testSubjects.click('aggregationTypeSelect'); + // assert all options are available + await find.byCssSelector('option[value="avg"]'); + await find.byCssSelector('option[value="min"]'); + await find.byCssSelector('option[value="max"]'); + await find.byCssSelector('option[value="sum"]'); + await find.byCssSelector('option[value="count"]'); + await find.byCssSelector('option[value="cardinality"]'); + await find.byCssSelector('option[value="p99"]'); + await find.byCssSelector('option[value="p95"]'); + await find.byCssSelector('option[value="rate"]'); + + // set first aggregation + await find.clickByCssSelector(`option[value="avg"]`); + const input1 = await find.byCssSelector('[data-test-subj="aggregationField"] input'); + await input1.type('metricset.rtt'); + await testSubjects.click('o11yClosablePopoverTitleButton'); + + // set second aggregation + await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); + await testSubjects.click('aggregationNameB'); + await testSubjects.setValue('ruleKqlFilterSearchField', 'service.name : "opbeans-node"'); + await testSubjects.click('o11yClosablePopoverTitleButton'); + + // set custom equation + // await testSubjects.click('customEquation'); + // const customEquationField = await find.byCssSelector( + // '[data-test-subj="thresholdRuleCustomEquationEditorFieldText"]' + // ); + // await customEquationField.click(); + // await customEquationField.type('A - B'); + // await testSubjects.click('o11yClosablePopoverTitleButton'); + + // set threshold + await testSubjects.click('thresholdPopover'); + await testSubjects.click('comparatorOptionsComboBox'); + // assert all options are available + await find.byCssSelector('option[value=">="]'); + await find.byCssSelector('option[value="<="]'); + await find.byCssSelector('option[value=">"]'); + await find.byCssSelector('option[value="<"]'); + await find.byCssSelector('option[value="between"]'); + await find.byCssSelector('option[value="notBetween"]'); + // select an option + await find.clickByCssSelector(`option[value="notBetween"]`); + const thresholdField1 = await find.byCssSelector('[data-test-subj="alertThresholdInput0"]'); + await thresholdField1.click(); + await new Promise((r) => setTimeout(r, 1000)); + await thresholdField1.pressKeys(Key.BACK_SPACE); + await new Promise((r) => setTimeout(r, 1000)); + await thresholdField1.pressKeys(Key.BACK_SPACE); + await new Promise((r) => setTimeout(r, 1000)); + await thresholdField1.pressKeys(Key.BACK_SPACE); + await thresholdField1.type('200'); + const thresholdField2 = await find.byCssSelector('[data-test-subj="alertThresholdInput1"]'); + thresholdField2.type('250'); + await find.clickByCssSelector('[aria-label="Close"]'); + + // set equation label + await testSubjects.setValue('thresholdRuleCustomEquationEditorFieldText', 'test equation'); + + // set time range + await testSubjects.click('forLastExpression'); + await new Promise((r) => setTimeout(r, 1000)); + const timeRangeField = await find.byCssSelector('[data-test-subj="timeWindowSizeNumber"]'); + await timeRangeField.click(); + await new Promise((r) => setTimeout(r, 1000)); + await timeRangeField.pressKeys(Key.BACK_SPACE); + await timeRangeField.type('2'); + // assert all options are available + await testSubjects.click('timeWindowUnitSelect'); + await find.byCssSelector('option[value="s"]'); + await find.byCssSelector('option[value="m"]'); + await find.byCssSelector('option[value="h"]'); + await find.byCssSelector('option[value="d"]'); + // select an option + await new Promise((r) => setTimeout(r, 3000)); + await find.clickByCssSelector('[data-test-subj="timeWindowUnitSelect"] option[value="d"]'); + await find.clickByCssSelector('[aria-label="Close"]'); + const groupByField = await find.byCssSelector( + '[data-test-subj="thresholdRuleMetricsExplorer-groupBy"] [data-test-subj="comboBoxSearchInput"]' + ); + await groupByField.type('docker.container.name'); + await testSubjects.click('saveRuleButton'); + await testSubjects.click('confirmModalConfirmButton'); + await find.byCssSelector('button[title="test custom threshold rule"]'); + }; + return { getManageRulesPageHref, clickCreateRuleButton, @@ -37,5 +150,8 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont clickDisableFromDropDownMenu, clickLogsTab, clickOnRuleInEventLogs, + clickOnObservabilityCategory, + clickOnCustomThresholdRule, + fillCustomThresholdRule, }; } diff --git a/x-pack/test/observability_functional/apps/observability/index.ts b/x-pack/test/observability_functional/apps/observability/index.ts index 67c00ef846a2c..b46b2ef07845f 100644 --- a/x-pack/test/observability_functional/apps/observability/index.ts +++ b/x-pack/test/observability_functional/apps/observability/index.ts @@ -17,6 +17,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./pages/alerts/rule_stats')); loadTestFile(require.resolve('./pages/alerts/state_synchronization')); loadTestFile(require.resolve('./pages/alerts/table_storage')); + loadTestFile(require.resolve('./pages/alerts/custom_threshold')); loadTestFile(require.resolve('./pages/cases/case_details')); loadTestFile(require.resolve('./pages/overview/alert_table')); loadTestFile(require.resolve('./exploratory_view')); diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts new file mode 100644 index 0000000000000..740c94031018b --- /dev/null +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -0,0 +1,112 @@ +/* + * 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 expect from 'expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const supertest = getService('supertest'); + + describe('Custom threshold rule', function () { + this.tags('includeFirefox'); + + const observability = getService('observability'); + const dataView1 = 'filebeat-*'; + const dataView2 = 'metricbeat-*'; + const timeFieldName = '@timestamp'; + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + // create two data views + await supertest + .post(`/api/saved_objects/index-pattern`) + .set('kbn-xsrf', 'true') + .send({ attributes: { title: dataView1, timeFieldName } }); + await supertest + .post(`/api/saved_objects/index-pattern`) + .set('kbn-xsrf', 'true') + .send({ attributes: { title: dataView2, timeFieldName } }); + await observability.alerts.common.navigateToRulesPage(); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + it('is filtered to only show "all" alerts by default', async () => { + await observability.alerts.rulesPage.clickCreateRuleButton(); + await observability.alerts.rulesPage.clickOnObservabilityCategory(); + await testSubjects.existOrFail('observability.rules.custom_threshold-SelectOption'); + }); + + it('can create a custom threshold rule', async () => { + await observability.alerts.rulesPage.clickOnCustomThresholdRule(); + await observability.alerts.rulesPage.fillCustomThresholdRule('test custom threshold rule'); + }); + + it('saved the rule correctly', async () => { + const { body: rules } = await supertest + .post('/internal/alerting/rules/_find') + .set('kbn-xsrf', 'true') + .send({ + page: 1, + per_page: 10, + filter: + '{"type":"function","function":"or","arguments":[{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":".es-query","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.ml.anomaly_detection_alert","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.uptime.alerts.tlsCertificate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.uptime.alerts.monitorStatus","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.uptime.alerts.durationAnomaly","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.synthetics.alerts.monitorStatus","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.synthetics.alerts.tls","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"slo.rules.burnRate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"metrics.alert.threshold","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"metrics.alert.inventory.threshold","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"observability.rules.custom_threshold","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"logs.alert.document.count","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.error_rate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.transaction_error_rate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.transaction_duration","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.anomaly","isQuoted":false}]}]}', + sort_field: 'name', + sort_order: 'asc', + filter_consumers: ['apm', 'infrastructure', 'logs', 'uptime', 'slo', 'observability'], + }); + expect(rules.data.length).toEqual(1); + expect(rules.data[0]).toEqual( + expect.objectContaining({ + name: 'test custom threshold rule', + tags: ['tag1'], + params: expect.objectContaining({ + alertOnGroupDisappear: true, + alertOnNoData: true, + criteria: [ + { + comparator: '>', + label: 'test equation', + metrics: [ + { + aggType: 'avg', + field: 'metricset.rtt', + name: 'A', + }, + { + aggType: 'count', + filter: 'service.name : "opbeans-node"', + name: 'B', + }, + ], + threshold: [200, 250], + timeSize: 2, + timeUnit: 'd', + }, + ], + groupBy: ['docker.container.name'], + // searchConfiguration: { + // index: 'ef4deffb-805f-4aed-ad80-f481ee47d40b', + // query: { + // language: 'kuery', + // query: '', + // }, + // }, + }), + }) + ); + }); + }); +}; From 3b3a32e167de829b03a15c716fb29059bfa03b2d Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Mon, 3 Jun 2024 11:09:16 -0400 Subject: [PATCH 02/12] adjust tests --- .../common/expression_items/threshold.test.tsx | 14 +++++++------- .../discover/search_source_alert.ts | 2 +- .../stack_alerting/index_threshold_rule.ts | 2 +- .../discover/search_source_alert.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx index 02ee05c125db6..ffa62c44ca38d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx @@ -89,10 +89,10 @@ describe('threshold expression', () => { wrapper.find('[data-test-subj="thresholdPopover"]').last().simulate('click'); expect(wrapper.find('[data-test-subj="comparatorOptionsComboBox"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alertThresholdInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alertThresholdInput0"]').exists()).toBeTruthy(); wrapper - .find('[data-test-subj="alertThresholdInput"]') + .find('[data-test-subj="alertThresholdInput0"]') .last() .simulate('change', { target: { value: 1000 } }); expect(onChangeSelectedThreshold).toHaveBeenCalled(); @@ -145,21 +145,21 @@ describe('threshold expression', () => { wrapper.find('[data-test-subj="thresholdPopover"]').last().simulate('click'); expect(wrapper.find('[data-test-subj="comparatorOptionsComboBox"]').exists()).toBeTruthy(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput"]').length).toEqual(1); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(1); wrapper .find('[data-test-subj="comparatorOptionsComboBox"]') .last() .simulate('change', { target: { value: 'between' } }); wrapper.update(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput"]').length).toEqual(2); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(2); wrapper .find('[data-test-subj="comparatorOptionsComboBox"]') .last() .simulate('change', { target: { value: '<' } }); wrapper.update(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput"]').length).toEqual(1); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(1); }); it('is valid when the threshold value is 0', () => { @@ -174,9 +174,9 @@ describe('threshold expression', () => { onChangeSelectedThresholdComparator={onChangeSelectedThresholdComparator} /> ); - expect(wrapper.find('[data-test-subj="alertThresholdInput"]')).toMatchInlineSnapshot(` + expect(wrapper.find('[data-test-subj="alertThresholdInput0"]')).toMatchInlineSnapshot(` Date: Mon, 3 Jun 2024 11:09:16 -0400 Subject: [PATCH 03/12] adjust tests --- .../common/expression_items/threshold.test.tsx | 14 +++++++------- .../discover/search_source_alert.ts | 4 ++-- .../stack_alerting/index_threshold_rule.ts | 2 +- .../discover/search_source_alert.ts | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx index 02ee05c125db6..ffa62c44ca38d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx @@ -89,10 +89,10 @@ describe('threshold expression', () => { wrapper.find('[data-test-subj="thresholdPopover"]').last().simulate('click'); expect(wrapper.find('[data-test-subj="comparatorOptionsComboBox"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="alertThresholdInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="alertThresholdInput0"]').exists()).toBeTruthy(); wrapper - .find('[data-test-subj="alertThresholdInput"]') + .find('[data-test-subj="alertThresholdInput0"]') .last() .simulate('change', { target: { value: 1000 } }); expect(onChangeSelectedThreshold).toHaveBeenCalled(); @@ -145,21 +145,21 @@ describe('threshold expression', () => { wrapper.find('[data-test-subj="thresholdPopover"]').last().simulate('click'); expect(wrapper.find('[data-test-subj="comparatorOptionsComboBox"]').exists()).toBeTruthy(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput"]').length).toEqual(1); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(1); wrapper .find('[data-test-subj="comparatorOptionsComboBox"]') .last() .simulate('change', { target: { value: 'between' } }); wrapper.update(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput"]').length).toEqual(2); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(2); wrapper .find('[data-test-subj="comparatorOptionsComboBox"]') .last() .simulate('change', { target: { value: '<' } }); wrapper.update(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput"]').length).toEqual(1); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(1); }); it('is valid when the threshold value is 0', () => { @@ -174,9 +174,9 @@ describe('threshold expression', () => { onChangeSelectedThresholdComparator={onChangeSelectedThresholdComparator} /> ); - expect(wrapper.find('[data-test-subj="alertThresholdInput"]')).toMatchInlineSnapshot(` + expect(wrapper.find('[data-test-subj="alertThresholdInput0"]')).toMatchInlineSnapshot(` Date: Tue, 4 Jun 2024 10:21:13 -0400 Subject: [PATCH 04/12] adjust tests --- .../public/common/expression_items/threshold.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx index ffa62c44ca38d..123ce6bcfb00e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.test.tsx @@ -152,7 +152,8 @@ describe('threshold expression', () => { .last() .simulate('change', { target: { value: 'between' } }); wrapper.update(); - expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(2); + expect(wrapper.find('input[data-test-subj="alertThresholdInput0"]').length).toEqual(1); + expect(wrapper.find('input[data-test-subj="alertThresholdInput1"]').length).toEqual(1); wrapper .find('[data-test-subj="comparatorOptionsComboBox"]') From caf50c416967f320c457122a9b488f2219f7a01e Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Fri, 7 Jun 2024 11:21:27 -0400 Subject: [PATCH 05/12] adjust test --- .../observability/alerts/rules_page.ts | 104 ------------ .../pages/alerts/custom_threshold.ts | 152 +++++++++++++++++- 2 files changed, 145 insertions(+), 111 deletions(-) diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index 84ab6cb80ff41..2f84687350b8b 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Key } from 'selenium-webdriver'; import { FtrProviderContext } from '../../../ftr_provider_context'; export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderContext) { @@ -41,108 +40,6 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont await testSubjects.click('observability.rules.custom_threshold-SelectOption'); }; - const fillCustomThresholdRule = async (name: string) => { - await testSubjects.setValue('ruleNameInput', name); - await testSubjects.setValue('comboBoxSearchInput', 'tag1'); - - // select data view - await testSubjects.click('selectDataViewExpression'); - await testSubjects.setValue('indexPattern-switcher--input', 'metricbeat-*'); - const dataViewExpression = await find.byCssSelector( - '[data-test-subj="indexPattern-switcher--input"]' - ); - await dataViewExpression.pressKeys(Key.ENTER); - - // select aggregation - await testSubjects.click('aggregationNameA'); - await testSubjects.click('aggregationTypeSelect'); - // assert all options are available - await find.byCssSelector('option[value="avg"]'); - await find.byCssSelector('option[value="min"]'); - await find.byCssSelector('option[value="max"]'); - await find.byCssSelector('option[value="sum"]'); - await find.byCssSelector('option[value="count"]'); - await find.byCssSelector('option[value="cardinality"]'); - await find.byCssSelector('option[value="p99"]'); - await find.byCssSelector('option[value="p95"]'); - await find.byCssSelector('option[value="rate"]'); - - // set first aggregation - await find.clickByCssSelector(`option[value="avg"]`); - const input1 = await find.byCssSelector('[data-test-subj="aggregationField"] input'); - await input1.type('metricset.rtt'); - await testSubjects.click('o11yClosablePopoverTitleButton'); - - // set second aggregation - await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); - await testSubjects.click('aggregationNameB'); - await testSubjects.setValue('ruleKqlFilterSearchField', 'service.name : "opbeans-node"'); - await testSubjects.click('o11yClosablePopoverTitleButton'); - - // set custom equation - // await testSubjects.click('customEquation'); - // const customEquationField = await find.byCssSelector( - // '[data-test-subj="thresholdRuleCustomEquationEditorFieldText"]' - // ); - // await customEquationField.click(); - // await customEquationField.type('A - B'); - // await testSubjects.click('o11yClosablePopoverTitleButton'); - - // set threshold - await testSubjects.click('thresholdPopover'); - await testSubjects.click('comparatorOptionsComboBox'); - // assert all options are available - await find.byCssSelector('option[value=">="]'); - await find.byCssSelector('option[value="<="]'); - await find.byCssSelector('option[value=">"]'); - await find.byCssSelector('option[value="<"]'); - await find.byCssSelector('option[value="between"]'); - await find.byCssSelector('option[value="notBetween"]'); - // select an option - await find.clickByCssSelector(`option[value="notBetween"]`); - const thresholdField1 = await find.byCssSelector('[data-test-subj="alertThresholdInput0"]'); - await thresholdField1.click(); - await new Promise((r) => setTimeout(r, 1000)); - await thresholdField1.pressKeys(Key.BACK_SPACE); - await new Promise((r) => setTimeout(r, 1000)); - await thresholdField1.pressKeys(Key.BACK_SPACE); - await new Promise((r) => setTimeout(r, 1000)); - await thresholdField1.pressKeys(Key.BACK_SPACE); - await thresholdField1.type('200'); - const thresholdField2 = await find.byCssSelector('[data-test-subj="alertThresholdInput1"]'); - thresholdField2.type('250'); - await find.clickByCssSelector('[aria-label="Close"]'); - - // set equation label - await testSubjects.setValue('thresholdRuleCustomEquationEditorFieldText', 'test equation'); - - // set time range - await testSubjects.click('forLastExpression'); - await new Promise((r) => setTimeout(r, 1000)); - const timeRangeField = await find.byCssSelector('[data-test-subj="timeWindowSizeNumber"]'); - await timeRangeField.click(); - await new Promise((r) => setTimeout(r, 1000)); - await timeRangeField.pressKeys(Key.BACK_SPACE); - await timeRangeField.type('2'); - // assert all options are available - await testSubjects.click('timeWindowUnitSelect'); - await find.byCssSelector('option[value="s"]'); - await find.byCssSelector('option[value="m"]'); - await find.byCssSelector('option[value="h"]'); - await find.byCssSelector('option[value="d"]'); - // select an option - await new Promise((r) => setTimeout(r, 3000)); - await find.clickByCssSelector('[data-test-subj="timeWindowUnitSelect"] option[value="d"]'); - await find.clickByCssSelector('[aria-label="Close"]'); - const groupByField = await find.byCssSelector( - '[data-test-subj="thresholdRuleMetricsExplorer-groupBy"] [data-test-subj="comboBoxSearchInput"]' - ); - await groupByField.type('docker.container.name'); - await testSubjects.click('saveRuleButton'); - await testSubjects.click('confirmModalConfirmButton'); - await find.byCssSelector('button[title="test custom threshold rule"]'); - }; - return { getManageRulesPageHref, clickCreateRuleButton, @@ -152,6 +49,5 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont clickOnRuleInEventLogs, clickOnObservabilityCategory, clickOnCustomThresholdRule, - fillCustomThresholdRule, }; } diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 740c94031018b..7bee83c323381 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { Key } from 'selenium-webdriver'; import expect from 'expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -13,6 +13,7 @@ export default ({ getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); const supertest = getService('supertest'); + const find = getService('find'); describe('Custom threshold rule', function () { this.tags('includeFirefox'); @@ -43,15 +44,132 @@ export default ({ getService }: FtrProviderContext) => { await kibanaServer.savedObjects.cleanStandardList(); }); - it('is filtered to only show "all" alerts by default', async () => { + it('shows the custom threshold rule in the observability section', async () => { await observability.alerts.rulesPage.clickCreateRuleButton(); await observability.alerts.rulesPage.clickOnObservabilityCategory(); await testSubjects.existOrFail('observability.rules.custom_threshold-SelectOption'); + await observability.alerts.rulesPage.clickOnCustomThresholdRule(); }); - it('can create a custom threshold rule', async () => { - await observability.alerts.rulesPage.clickOnCustomThresholdRule(); - await observability.alerts.rulesPage.fillCustomThresholdRule('test custom threshold rule'); + it('can add name and tags', async () => { + await testSubjects.setValue('ruleNameInput', 'test custom threshold rule'); + await testSubjects.setValue('comboBoxSearchInput', 'tag1'); + }); + + it('can add data view', async () => { + // select data view + await testSubjects.click('selectDataViewExpression'); + await testSubjects.setValue('indexPattern-switcher--input', 'metricbeat-*'); + const dataViewExpression = await find.byCssSelector( + '[data-test-subj="indexPattern-switcher--input"]' + ); + await dataViewExpression.pressKeys(Key.ENTER); + }); + + it('can select aggregation', async () => { + // select aggregation + await testSubjects.click('aggregationNameA'); + await testSubjects.click('aggregationTypeSelect'); + // assert all options are available + await find.byCssSelector('option[value="avg"]'); + await find.byCssSelector('option[value="min"]'); + await find.byCssSelector('option[value="max"]'); + await find.byCssSelector('option[value="sum"]'); + await find.byCssSelector('option[value="count"]'); + await find.byCssSelector('option[value="cardinality"]'); + await find.byCssSelector('option[value="p99"]'); + await find.byCssSelector('option[value="p95"]'); + await find.byCssSelector('option[value="rate"]'); + + // set first aggregation + await find.clickByCssSelector(`option[value="avg"]`); + const input1 = await find.byCssSelector('[data-test-subj="aggregationField"] input'); + await input1.type('metricset.rtt'); + await testSubjects.click('o11yClosablePopoverTitleButton'); + + // set second aggregation + await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); + await testSubjects.click('aggregationNameB'); + await testSubjects.setValue('ruleKqlFilterSearchField', 'service.name : "opbeans-node"'); + await testSubjects.click('o11yClosablePopoverTitleButton'); + }); + + // it('can set custom equation', async () => { + // set custom equation + // await testSubjects.click('customEquation'); + // const customEquationField = await find.byCssSelector( + // '[data-test-subj="thresholdRuleCustomEquationEditorFieldText"]' + // ); + // await customEquationField.click(); + // await customEquationField.type('A - B'); + // await testSubjects.click('o11yClosablePopoverTitleButton'); + // }); + + it('can set threshold', async () => { + // set threshold + await testSubjects.click('thresholdPopover'); + await testSubjects.click('comparatorOptionsComboBox'); + // assert all options are available + await find.byCssSelector('option[value=">="]'); + await find.byCssSelector('option[value="<="]'); + await find.byCssSelector('option[value=">"]'); + await find.byCssSelector('option[value="<"]'); + await find.byCssSelector('option[value="between"]'); + await find.byCssSelector('option[value="notBetween"]'); + // select an option + await find.clickByCssSelector(`option[value="notBetween"]`); + const thresholdField1 = await find.byCssSelector('[data-test-subj="alertThresholdInput0"]'); + await thresholdField1.click(); + await new Promise((r) => setTimeout(r, 1000)); + await thresholdField1.pressKeys(Key.BACK_SPACE); + await new Promise((r) => setTimeout(r, 1000)); + await thresholdField1.pressKeys(Key.BACK_SPACE); + await new Promise((r) => setTimeout(r, 1000)); + await thresholdField1.pressKeys(Key.BACK_SPACE); + await thresholdField1.type('200'); + const thresholdField2 = await find.byCssSelector('[data-test-subj="alertThresholdInput1"]'); + thresholdField2.type('250'); + await find.clickByCssSelector('[aria-label="Close"]'); + }); + + it('can set equation label', async () => { + // set equation label + await testSubjects.setValue('thresholdRuleCustomEquationEditorFieldText', 'test equation'); + }); + + it('can set time range', async () => { + // set time range + await testSubjects.click('forLastExpression'); + await new Promise((r) => setTimeout(r, 1000)); + const timeRangeField = await find.byCssSelector('[data-test-subj="timeWindowSizeNumber"]'); + await timeRangeField.click(); + await new Promise((r) => setTimeout(r, 1000)); + await timeRangeField.pressKeys(Key.BACK_SPACE); + await timeRangeField.type('2'); + // assert all options are available + await testSubjects.click('timeWindowUnitSelect'); + await find.byCssSelector('option[value="s"]'); + await find.byCssSelector('option[value="m"]'); + await find.byCssSelector('option[value="h"]'); + await find.byCssSelector('option[value="d"]'); + // select an option + await new Promise((r) => setTimeout(r, 3000)); + await find.clickByCssSelector('[data-test-subj="timeWindowUnitSelect"] option[value="d"]'); + await find.clickByCssSelector('[aria-label="Close"]'); + }); + + it('can set groupby', async () => { + // set group by + const groupByField = await find.byCssSelector( + '[data-test-subj="thresholdRuleMetricsExplorer-groupBy"] [data-test-subj="comboBoxSearchInput"]' + ); + await groupByField.type('docker.container.name'); + }); + + it('can save the rule', async () => { + await testSubjects.click('saveRuleButton'); + await testSubjects.click('confirmModalConfirmButton'); + await find.byCssSelector('button[title="test custom threshold rule"]'); }); it('saved the rule correctly', async () => { @@ -61,8 +179,28 @@ export default ({ getService }: FtrProviderContext) => { .send({ page: 1, per_page: 10, - filter: - '{"type":"function","function":"or","arguments":[{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":".es-query","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.ml.anomaly_detection_alert","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.uptime.alerts.tlsCertificate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.uptime.alerts.monitorStatus","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.uptime.alerts.durationAnomaly","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.synthetics.alerts.monitorStatus","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"xpack.synthetics.alerts.tls","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"slo.rules.burnRate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"metrics.alert.threshold","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"metrics.alert.inventory.threshold","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"observability.rules.custom_threshold","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"logs.alert.document.count","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.error_rate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.transaction_error_rate","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.transaction_duration","isQuoted":false}]},{"type":"function","function":"is","arguments":[{"type":"literal","value":"alert.attributes.alertTypeId","isQuoted":false},{"type":"literal","value":"apm.anomaly","isQuoted":false}]}]}', + filter: `{ + "type": "function", + "function": "or", + "arguments": [ + { + "type": "function", + "function": "is", + "arguments": [ + { + "type": "literal", + "value": "alert.attributes.alertTypeId", + "isQuoted": false + }, + { + "type": "literal", + "value": "observability.rules.custom_threshold", + "isQuoted": false + } + ] + } + ] + }`, sort_field: 'name', sort_order: 'asc', filter_consumers: ['apm', 'infrastructure', 'logs', 'uptime', 'slo', 'observability'], From bf4916631db809360cee1156e79801fb78973a17 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Tue, 16 Jul 2024 22:20:19 -0400 Subject: [PATCH 06/12] update selector and data --- .../apps/observability/pages/alerts/custom_threshold.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 7bee83c323381..46f436a446b38 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -24,7 +24,6 @@ export default ({ getService }: FtrProviderContext) => { const timeFieldName = '@timestamp'; before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); // create two data views await supertest @@ -39,7 +38,6 @@ export default ({ getService }: FtrProviderContext) => { }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); await kibanaServer.savedObjects.cleanStandardList(); }); @@ -90,7 +88,7 @@ export default ({ getService }: FtrProviderContext) => { // set second aggregation await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); await testSubjects.click('aggregationNameB'); - await testSubjects.setValue('ruleKqlFilterSearchField', 'service.name : "opbeans-node"'); + await testSubjects.setValue('o11ySearchField', 'service.name : "opbeans-node"'); await testSubjects.click('o11yClosablePopoverTitleButton'); }); From 80fd84794289cf3343912324e3b283df43e18852 Mon Sep 17 00:00:00 2001 From: Dominique Belcher Date: Sun, 21 Jul 2024 22:21:39 -0400 Subject: [PATCH 07/12] adjust expected value --- .../apps/observability/pages/alerts/custom_threshold.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 46f436a446b38..537cae7e3d9f4 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -209,8 +209,8 @@ export default ({ getService }: FtrProviderContext) => { name: 'test custom threshold rule', tags: ['tag1'], params: expect.objectContaining({ - alertOnGroupDisappear: true, - alertOnNoData: true, + alertOnGroupDisappear: false, + alertOnNoData: false, criteria: [ { comparator: '>', From edb29db202a16fc4472ec06fe415769948540096 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Mon, 22 Jul 2024 17:42:55 +0200 Subject: [PATCH 08/12] Fix setting equation --- .../components/expression_row.tsx | 2 +- .../pages/alerts/custom_threshold.ts | 26 +++++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/expression_row.tsx b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/expression_row.tsx index 0d4fe7ecafbb8..ee9033f72e8e5 100644 --- a/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/expression_row.tsx +++ b/x-pack/plugins/observability_solution/observability/public/components/custom_threshold/components/expression_row.tsx @@ -165,7 +165,7 @@ export const ExpressionRow: React.FC = (props) => { { await testSubjects.click('o11yClosablePopoverTitleButton'); }); - // it('can set custom equation', async () => { - // set custom equation - // await testSubjects.click('customEquation'); - // const customEquationField = await find.byCssSelector( - // '[data-test-subj="thresholdRuleCustomEquationEditorFieldText"]' - // ); - // await customEquationField.click(); - // await customEquationField.type('A - B'); - // await testSubjects.click('o11yClosablePopoverTitleButton'); - // }); + it('can set custom equation', async () => { + // set custom equation + await testSubjects.click('customEquation'); + const customEquationField = await find.byCssSelector( + '[data-test-subj="thresholdRuleCustomEquationEditorFieldText"]' + ); + await customEquationField.click(); + await customEquationField.type('A - B'); + await testSubjects.click('o11yClosablePopoverTitleButton'); + }); it('can set threshold', async () => { // set threshold @@ -132,7 +132,10 @@ export default ({ getService }: FtrProviderContext) => { it('can set equation label', async () => { // set equation label - await testSubjects.setValue('thresholdRuleCustomEquationEditorFieldText', 'test equation'); + await testSubjects.setValue( + 'thresholdRuleCustomEquationEditorFieldTextLabel', + 'test equation' + ); }); it('can set time range', async () => { @@ -215,6 +218,7 @@ export default ({ getService }: FtrProviderContext) => { { comparator: '>', label: 'test equation', + equation: 'A - B', metrics: [ { aggType: 'avg', From 3be7c53a1bffb535051db0abae3fbe6cb0282a3e Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Fri, 13 Sep 2024 13:35:21 +0200 Subject: [PATCH 09/12] Create data view using createDataView helper --- .../services/observability/alerts/common.ts | 67 +++++++++++++++ .../observability/alerts/rules_page.ts | 4 +- .../pages/alerts/custom_threshold.ts | 86 +++++++------------ 3 files changed, 102 insertions(+), 55 deletions(-) diff --git a/x-pack/test/functional/services/observability/alerts/common.ts b/x-pack/test/functional/services/observability/alerts/common.ts index dcac33b952e3e..7f327664f1b71 100644 --- a/x-pack/test/functional/services/observability/alerts/common.ts +++ b/x-pack/test/functional/services/observability/alerts/common.ts @@ -6,9 +6,11 @@ */ import expect from '@kbn/expect'; +import { ToolingLog } from '@kbn/tooling-log'; import { chunk } from 'lodash'; import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, AlertStatus } from '@kbn/rule-data-utils'; import { WebElementWrapper } from '@kbn/ftr-common-functional-ui-services'; +import { Agent as SuperTestAgent } from 'supertest'; import { FtrProviderContext } from '../../../ftr_provider_context'; // Based on the x-pack/test/functional/es_archives/observability/alerts archive. @@ -314,6 +316,69 @@ export function ObservabilityAlertsCommonProvider({ return value; }); + // Data view + const createDataView = async ({ + supertest, + id, + name, + title, + logger, + }: { + supertest: SuperTestAgent; + id: string; + name: string; + title: string; + logger: ToolingLog; + }) => { + const { body } = await supertest + .post(`/api/content_management/rpc/create`) + .set('kbn-xsrf', 'foo') + .send({ + contentTypeId: 'index-pattern', + data: { + fieldAttrs: '{}', + title, + timeFieldName: '@timestamp', + sourceFilters: '[]', + fields: '[]', + fieldFormatMap: '{}', + typeMeta: '{}', + runtimeFieldMap: '{}', + name, + }, + options: { id }, + version: 1, + }) + .expect(200); + + logger.debug(`Created data view: ${JSON.stringify(body)}`); + return body; + }; + + const deleteDataView = async ({ + supertest, + id, + logger, + }: { + supertest: SuperTestAgent; + id: string; + logger: ToolingLog; + }) => { + const { body } = await supertest + .post(`/api/content_management/rpc/delete`) + .set('kbn-xsrf', 'foo') + .send({ + contentTypeId: 'index-pattern', + id, + options: { force: true }, + version: 1, + }) + .expect(200); + + logger.debug(`Deleted data view id: ${id}`); + return body; + }; + return { getQueryBar, clearQueryBar, @@ -357,5 +422,7 @@ export function ObservabilityAlertsCommonProvider({ navigateToRulesLogsPage, navigateToRuleDetailsByRuleId, navigateToAlertDetails, + createDataView, + deleteDataView, }; } diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index 6457abba22c7d..2a170bc84b4d0 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -7,6 +7,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; const METRIC_THRESHOLD_RULE_TYPE_SELECTOR = 'metrics.alert.threshold-SelectOption'; +const CUSTOM_THRESHOLD_RULE_TYPE_SELECTOR = 'observability.rules.custom_threshold-SelectOption'; export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); @@ -50,7 +51,8 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont }; const clickOnCustomThresholdRule = async () => { - await testSubjects.click('observability.rules.custom_threshold-SelectOption'); + await testSubjects.existOrFail(CUSTOM_THRESHOLD_RULE_TYPE_SELECTOR); + await testSubjects.click(CUSTOM_THRESHOLD_RULE_TYPE_SELECTOR); }; return { diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index b07d89c468d7b..1392a5659d4ac 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ + import { Key } from 'selenium-webdriver'; import expect from 'expect'; import { FtrProviderContext } from '../../../../ftr_provider_context'; @@ -14,38 +15,48 @@ export default ({ getService }: FtrProviderContext) => { const kibanaServer = getService('kibanaServer'); const supertest = getService('supertest'); const find = getService('find'); + const logger = getService('log'); describe('Custom threshold rule', function () { this.tags('includeFirefox'); const observability = getService('observability'); - const dataView1 = 'filebeat-*'; - const dataView2 = 'metricbeat-*'; - const timeFieldName = '@timestamp'; + const DATA_VIEW_1 = 'filebeat-*'; + const DATA_VIEW_1_ID = 'data-view-id_1'; + const DATA_VIEW_1_NAME = 'test-data-view-name_1'; + const DATA_VIEW_2 = 'metricbeat-*'; + const DATA_VIEW_2_ID = 'data-view-id_2'; + const DATA_VIEW_2_NAME = 'test-data-view-name_2'; before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); // create two data views - await supertest - .post(`/api/saved_objects/index-pattern`) - .set('kbn-xsrf', 'true') - .send({ attributes: { title: dataView1, timeFieldName } }); - await supertest - .post(`/api/saved_objects/index-pattern`) - .set('kbn-xsrf', 'true') - .send({ attributes: { title: dataView2, timeFieldName } }); + await observability.alerts.common.createDataView({ + supertest, + name: DATA_VIEW_1_NAME, + id: DATA_VIEW_1_ID, + title: DATA_VIEW_1, + logger, + }); + await observability.alerts.common.createDataView({ + supertest, + name: DATA_VIEW_2_NAME, + id: DATA_VIEW_2_ID, + title: DATA_VIEW_2, + logger, + }); await observability.alerts.common.navigateToRulesPage(); }); after(async () => { await esArchiver.unload('x-pack/test/functional/es_archives/infra/metrics_and_logs'); + // This also deletes the created data views await kibanaServer.savedObjects.cleanStandardList(); }); it('shows the custom threshold rule in the observability section', async () => { await observability.alerts.rulesPage.clickCreateRuleButton(); await observability.alerts.rulesPage.clickOnObservabilityCategory(); - await testSubjects.existOrFail('observability.rules.custom_threshold-SelectOption'); await observability.alerts.rulesPage.clickOnCustomThresholdRule(); }); @@ -57,7 +68,7 @@ export default ({ getService }: FtrProviderContext) => { it('can add data view', async () => { // select data view await testSubjects.click('selectDataViewExpression'); - await testSubjects.setValue('indexPattern-switcher--input', 'metricbeat-*'); + await testSubjects.setValue('indexPattern-switcher--input', 'test-data-view-name_2'); const dataViewExpression = await find.byCssSelector( '[data-test-subj="indexPattern-switcher--input"]' ); @@ -126,7 +137,7 @@ export default ({ getService }: FtrProviderContext) => { await thresholdField1.pressKeys(Key.BACK_SPACE); await thresholdField1.type('200'); const thresholdField2 = await find.byCssSelector('[data-test-subj="alertThresholdInput1"]'); - thresholdField2.type('250'); + await thresholdField2.type('250'); await find.clickByCssSelector('[aria-label="Close"]'); }); @@ -174,38 +185,8 @@ export default ({ getService }: FtrProviderContext) => { }); it('saved the rule correctly', async () => { - const { body: rules } = await supertest - .post('/internal/alerting/rules/_find') - .set('kbn-xsrf', 'true') - .send({ - page: 1, - per_page: 10, - filter: `{ - "type": "function", - "function": "or", - "arguments": [ - { - "type": "function", - "function": "is", - "arguments": [ - { - "type": "literal", - "value": "alert.attributes.alertTypeId", - "isQuoted": false - }, - { - "type": "literal", - "value": "observability.rules.custom_threshold", - "isQuoted": false - } - ] - } - ] - }`, - sort_field: 'name', - sort_order: 'asc', - filter_consumers: ['apm', 'infrastructure', 'logs', 'uptime', 'slo', 'observability'], - }); + const { body: rules } = await supertest.get('/internal/alerting/rules/_find'); + expect(rules.data.length).toEqual(1); expect(rules.data[0]).toEqual( expect.objectContaining({ @@ -216,7 +197,7 @@ export default ({ getService }: FtrProviderContext) => { alertOnNoData: false, criteria: [ { - comparator: '>', + comparator: 'notBetween', label: 'test equation', equation: 'A - B', metrics: [ @@ -237,13 +218,10 @@ export default ({ getService }: FtrProviderContext) => { }, ], groupBy: ['docker.container.name'], - // searchConfiguration: { - // index: 'ef4deffb-805f-4aed-ad80-f481ee47d40b', - // query: { - // language: 'kuery', - // query: '', - // }, - // }, + searchConfiguration: { + index: 'data-view-id_2', + query: { query: '', language: 'kuery' }, + }, }), }) ); From 58eb83167759d8f8d2819c577c0ee7f7ced031ec Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Mon, 16 Sep 2024 18:10:48 +0200 Subject: [PATCH 10/12] Fix flakiness --- .../components/closable_popover_title.tsx | 9 +++++++-- .../public/common/expression_items/threshold.tsx | 7 +++++-- .../services/observability/alerts/rules_page.ts | 1 + .../observability/pages/alerts/custom_threshold.ts | 13 +++++++++++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx index f6ebec92b4a57..e9d0dc580fa49 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx @@ -12,13 +12,18 @@ import { EuiPopoverTitle, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elas interface ClosablePopoverTitleProps { children: JSX.Element; onClose: () => void; + dataTestSubj: string; } -export const ClosablePopoverTitle = ({ children, onClose }: ClosablePopoverTitleProps) => { +export const ClosablePopoverTitle = ({ + children, + onClose, + dataTestSubj, +}: ClosablePopoverTitleProps) => { return ( - {children} + {children}
- setAlertThresholdPopoverOpen(false)}> - <>{comparators[comparator].text} + setAlertThresholdPopoverOpen(false)} + dataTestSubj="thresholdPopoverTitle" + > + {comparators[comparator].text} diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index 2a170bc84b4d0..2d320028649ea 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -19,6 +19,7 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont }; const clickCreateRuleButton = async () => { + await testSubjects.existOrFail('createRuleButton'); const createRuleButton = await testSubjects.find('createRuleButton'); return createRuleButton.click(); }; diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 1392a5659d4ac..4caac034bee8c 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -16,6 +16,7 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const find = getService('find'); const logger = getService('log'); + const retry = getService('retry'); describe('Custom threshold rule', function () { this.tags('includeFirefox'); @@ -73,6 +74,10 @@ export default ({ getService }: FtrProviderContext) => { '[data-test-subj="indexPattern-switcher--input"]' ); await dataViewExpression.pressKeys(Key.ENTER); + await retry.waitFor('data view selection to happen', async () => { + const dataViewSelector = await testSubjects.find('selectDataViewExpression'); + return (await dataViewSelector.getVisibleText()) === 'DATA VIEW\ntest-data-view-name_2'; + }); }); it('can select aggregation', async () => { @@ -112,6 +117,10 @@ export default ({ getService }: FtrProviderContext) => { await customEquationField.click(); await customEquationField.type('A - B'); await testSubjects.click('o11yClosablePopoverTitleButton'); + await retry.waitFor('custom equation update to happen', async () => { + const customEquation = await testSubjects.find('customEquation'); + return (await customEquation.getVisibleText()) === 'EQUATION\nA - B'; + }); }); it('can set threshold', async () => { @@ -139,6 +148,10 @@ export default ({ getService }: FtrProviderContext) => { const thresholdField2 = await find.byCssSelector('[data-test-subj="alertThresholdInput1"]'); await thresholdField2.type('250'); await find.clickByCssSelector('[aria-label="Close"]'); + await retry.waitFor('comparator selection to happen', async () => { + const customEquation = await testSubjects.find('thresholdPopover'); + return (await customEquation.getVisibleText()) === 'IS NOT BETWEEN\n200 AND 250'; + }); }); it('can set equation label', async () => { From a77fea8d2c05ab02a41e8608026a69cca09a38d1 Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Mon, 16 Sep 2024 22:57:01 +0200 Subject: [PATCH 11/12] Fix type and flakiness --- .../components/closable_popover_title.tsx | 2 +- .../public/common/expression_items/threshold.tsx | 2 +- .../services/observability/alerts/rules_page.ts | 2 +- .../apps/observability/pages/alerts/custom_threshold.ts | 8 ++++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx index e9d0dc580fa49..60145943a83c1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/components/closable_popover_title.tsx @@ -12,7 +12,7 @@ import { EuiPopoverTitle, EuiFlexGroup, EuiFlexItem, EuiButtonIcon } from '@elas interface ClosablePopoverTitleProps { children: JSX.Element; onClose: () => void; - dataTestSubj: string; + dataTestSubj?: string; } export const ClosablePopoverTitle = ({ diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx index a14700da21cfc..555c7a2905632 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/threshold.tsx @@ -124,7 +124,7 @@ export const ThresholdExpression = ({ onClose={() => setAlertThresholdPopoverOpen(false)} dataTestSubj="thresholdPopoverTitle" > - {comparators[comparator].text} + <>{comparators[comparator].text} diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index 2d320028649ea..99ef27baf50ff 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -21,7 +21,7 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont const clickCreateRuleButton = async () => { await testSubjects.existOrFail('createRuleButton'); const createRuleButton = await testSubjects.find('createRuleButton'); - return createRuleButton.click(); + return await createRuleButton.click(); }; const clickRuleStatusDropDownMenu = async () => testSubjects.click('statusDropdown'); diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 4caac034bee8c..5cbf2f4367c4e 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -100,12 +100,20 @@ export default ({ getService }: FtrProviderContext) => { const input1 = await find.byCssSelector('[data-test-subj="aggregationField"] input'); await input1.type('metricset.rtt'); await testSubjects.click('o11yClosablePopoverTitleButton'); + await retry.waitFor('first aggregation to happen', async () => { + const aggregationNameA = await testSubjects.find('aggregationNameA'); + return (await aggregationNameA.getVisibleText()) === 'AVERAGE\nmetricset.rtt'; + }); // set second aggregation await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); await testSubjects.click('aggregationNameB'); await testSubjects.setValue('o11ySearchField', 'service.name : "opbeans-node"'); await testSubjects.click('o11yClosablePopoverTitleButton'); + await retry.waitFor('first aggregation to happen', async () => { + const aggregationNameB = await testSubjects.find('aggregationNameB'); + return (await aggregationNameB.getVisibleText()) === 'COUNT\nservice.name : "opbeans-node"'; + }); }); it('can set custom equation', async () => { From 1dd8fc8aecf44723a6981671d6a35dd08518edeb Mon Sep 17 00:00:00 2001 From: Maryam Saeidi Date: Tue, 17 Sep 2024 16:36:45 +0200 Subject: [PATCH 12/12] Another attempt for fixing flakiness --- .../functional/services/observability/alerts/rules_page.ts | 2 ++ .../apps/observability/pages/alerts/custom_threshold.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/x-pack/test/functional/services/observability/alerts/rules_page.ts b/x-pack/test/functional/services/observability/alerts/rules_page.ts index 99ef27baf50ff..f5b16dc3914ab 100644 --- a/x-pack/test/functional/services/observability/alerts/rules_page.ts +++ b/x-pack/test/functional/services/observability/alerts/rules_page.ts @@ -35,6 +35,7 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont }; const clickOnInfrastructureCategory = async () => { + await testSubjects.existOrFail('ruleTypeModal'); const categories = await testSubjects.find('ruleTypeModal'); const category = await categories.findByCssSelector(`.euiFacetButton[title="Infrastructure"]`); await category.click(); @@ -46,6 +47,7 @@ export function ObservabilityAlertsRulesProvider({ getService }: FtrProviderCont }; const clickOnObservabilityCategory = async () => { + await testSubjects.existOrFail('ruleTypeModal'); const categories = await testSubjects.find('ruleTypeModal'); const category = await categories.findByCssSelector(`.euiFacetButton[title="Observability"]`); await category.click(); diff --git a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts index 5cbf2f4367c4e..38d308a17e7b0 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/alerts/custom_threshold.ts @@ -104,6 +104,7 @@ export default ({ getService }: FtrProviderContext) => { const aggregationNameA = await testSubjects.find('aggregationNameA'); return (await aggregationNameA.getVisibleText()) === 'AVERAGE\nmetricset.rtt'; }); + await new Promise((r) => setTimeout(r, 1000)); // set second aggregation await testSubjects.click('thresholdRuleCustomEquationEditorAddAggregationFieldButton'); @@ -114,6 +115,7 @@ export default ({ getService }: FtrProviderContext) => { const aggregationNameB = await testSubjects.find('aggregationNameB'); return (await aggregationNameB.getVisibleText()) === 'COUNT\nservice.name : "opbeans-node"'; }); + await new Promise((r) => setTimeout(r, 1000)); }); it('can set custom equation', async () => { @@ -129,6 +131,7 @@ export default ({ getService }: FtrProviderContext) => { const customEquation = await testSubjects.find('customEquation'); return (await customEquation.getVisibleText()) === 'EQUATION\nA - B'; }); + await new Promise((r) => setTimeout(r, 1000)); }); it('can set threshold', async () => {