Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Observability] add custom threshold functional test #184602

Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0870aa5
observability - add custom threshold functional test
dominiqueclarke May 31, 2024
3b3a32e
adjust tests
dominiqueclarke Jun 3, 2024
51d15ff
adjust tests
dominiqueclarke Jun 3, 2024
8668126
Merge branch 'chore/observability-custom-threshold-functional-test' o…
dominiqueclarke Jun 3, 2024
6d6cf00
adjust tests
dominiqueclarke Jun 4, 2024
5d82bc3
Merge branch 'main' of https://github.com/elastic/kibana into chore/o…
dominiqueclarke Jun 6, 2024
caf50c4
adjust test
dominiqueclarke Jun 7, 2024
e054ec5
Merge branch 'main' of https://github.com/elastic/kibana into chore/o…
dominiqueclarke Jul 17, 2024
bf49166
update selector and data
dominiqueclarke Jul 17, 2024
34d5482
Merge branch 'main' into chore/observability-custom-threshold-functio…
dominiqueclarke Jul 19, 2024
64f1f58
Merge branch 'main' into chore/observability-custom-threshold-functio…
dominiqueclarke Jul 19, 2024
80fd847
adjust expected value
dominiqueclarke Jul 22, 2024
154258d
Merge branch 'main' into chore/observability-custom-threshold-functio…
dominiqueclarke Jul 22, 2024
edb29db
Fix setting equation
maryam-saeidi Jul 22, 2024
dda84b9
Merge branch 'main' into chore/observability-custom-threshold-functio…
maryam-saeidi Jul 22, 2024
345caa2
Merge branch 'main' into chore/observability-custom-threshold-functio…
dominiqueclarke Jul 30, 2024
7e2ecf1
Merge branch 'main' into chore/observability-custom-threshold-functio…
maryam-saeidi Sep 12, 2024
d638063
Merge branch 'main' into chore/observability-custom-threshold-functio…
maryam-saeidi Sep 13, 2024
3be7c53
Create data view using createDataView helper
maryam-saeidi Sep 13, 2024
58eb831
Fix flakiness
maryam-saeidi Sep 16, 2024
9450b4a
Merge branch 'main' into chore/observability-custom-threshold-functio…
maryam-saeidi Sep 16, 2024
a77fea8
Fix type and flakiness
maryam-saeidi Sep 16, 2024
99f6d24
Merge branch 'main' into chore/observability-custom-threshold-functio…
maryam-saeidi Sep 16, 2024
1dd8fc8
Another attempt for fixing flakiness
maryam-saeidi Sep 17, 2024
0778b1a
Merge branch 'main' into chore/observability-custom-threshold-functio…
maryam-saeidi Sep 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export function MetricRowWithAgg({
}
>
<EuiExpression
data-test-subj="aggregationName"
data-test-subj={`aggregationName${name}`}
description={aggregationTypes[aggType].text}
value={aggType === Aggregators.COUNT ? filter || DEFAULT_COUNT_FILTER_TITLE : field}
isActive={aggTypePopoverOpen}
Expand Down Expand Up @@ -228,6 +228,7 @@ export function MetricRowWithAgg({
options={fieldOptions}
selectedOptions={field ? [{ label: field }] : []}
onChange={handleFieldChange}
data-test-subj="aggregationField"
/>
</EuiFormRow>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const ExpressionRow: React.FC<ExpressionRowProps> = (props) => {
<EuiFlexItem>
<EuiFormRow label={LABEL_LABEL} fullWidth helpText={LABEL_HELP_MESSAGE}>
<EuiFieldText
data-test-subj="thresholdRuleCustomEquationEditorFieldText"
data-test-subj="thresholdRuleCustomEquationEditorFieldTextLabel"
compressed
fullWidth
value={label}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -145,21 +145,22 @@ 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(1);
expect(wrapper.find('input[data-test-subj="alertThresholdInput1"]').length).toEqual(1);

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', () => {
Expand All @@ -174,9 +175,9 @@ describe('threshold expression', () => {
onChangeSelectedThresholdComparator={onChangeSelectedThresholdComparator}
/>
);
expect(wrapper.find('[data-test-subj="alertThresholdInput"]')).toMatchInlineSnapshot(`
expect(wrapper.find('[data-test-subj="alertThresholdInput0"]')).toMatchInlineSnapshot(`
<EuiFieldNumber
data-test-subj="alertThresholdInput"
data-test-subj="alertThresholdInput0"
isInvalid={false}
min={0}
onChange={[Function]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ export const ThresholdExpression = ({
error={errors[`threshold${i}`]}
>
<EuiFieldNumber
data-test-subj="alertThresholdInput"
data-test-subj={`alertThresholdInput${i}`}
min={0}
value={!threshold || threshold[i] === undefined ? '' : threshold[i]}
isInvalid={Number(errors[`threshold${i}`]?.length) > 0 || isNil(threshold[i])}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,24 @@ 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();
Comment on lines +52 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
const category = await categories.findByCssSelector(`.euiFacetButton[title="Observability"]`);
await category.click();
await category.clickByCssSelector(`.euiFacetButton[title="Observability"]`);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the following error when using clickByCssSelector:

fail: ObservabilityApp Custom threshold rule shows the custom threshold rule in the observability section
       │      TypeError: categories.clickByCssSelector is not a function

};

const clickOnCustomThresholdRule = async () => {
await testSubjects.click('observability.rules.custom_threshold-SelectOption');
};

return {
getManageRulesPageHref,
clickCreateRuleButton,
clickRuleStatusDropDownMenu,
clickDisableFromDropDownMenu,
clickLogsTab,
clickOnRuleInEventLogs,
clickOnObservabilityCategory,
clickOnCustomThresholdRule,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
return ruleName === alertName;
});
await testSubjects.click('thresholdPopover');
await testSubjects.setValue('alertThresholdInput', '1');
await testSubjects.setValue('alertThresholdInput0', '1');

await testSubjects.click('forLastExpression');
await testSubjects.setValue('timeWindowSizeNumber', '30');
Expand Down Expand Up @@ -461,7 +461,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await filterBar.addFilter({ field: 'message.keyword', operation: 'is', value: 'msg-1' });

await testSubjects.click('thresholdPopover');
await testSubjects.setValue('alertThresholdInput', '1');
await testSubjects.setValue('alertThresholdInput0', '1');
await testSubjects.click('saveEditedRuleButton');
await PageObjects.header.waitUntilLoadingHasFinished();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
/*
* 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 { Key } from 'selenium-webdriver';
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');
const find = getService('find');

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/infra/metrics_and_logs');
// create two data views
maryam-saeidi marked this conversation as resolved.
Show resolved Hide resolved
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/infra/metrics_and_logs');
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');
maryam-saeidi marked this conversation as resolved.
Show resolved Hide resolved
await observability.alerts.rulesPage.clickOnCustomThresholdRule();
});

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🫶

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('o11ySearchField', '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(
'thresholdRuleCustomEquationEditorFieldTextLabel',
'test equation'
);
});

it('can set time range', async () => {
// set time range
await testSubjects.click('forLastExpression');
await new Promise((r) => setTimeout(r, 1000));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this setTimeout do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was having trouble with this expression being filled. Waiting a moment before attempting to interact with the element resolved the issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it mean find.byCssSelector only checks it once? I remember we had some utilities to retry checking an element, maybe using those would make this logic more resilient.

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 () => {
const { body: rules } = await supertest
.post('/internal/alerting/rules/_find')
.set('kbn-xsrf', 'true')
.send({
page: 1,
per_page: 10,
filter: `{
"type": "function",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this filter do?

I wonder if we need to filter or if we can get all the rules, assuming there should be only one since any test that adds a rule needs to clean it up afterward as well.

"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'],
});
expect(rules.data.length).toEqual(1);
expect(rules.data[0]).toEqual(
expect.objectContaining({
name: 'test custom threshold rule',
tags: ['tag1'],
params: expect.objectContaining({
alertOnGroupDisappear: false,
alertOnNoData: false,
criteria: [
{
comparator: '>',
label: 'test equation',
equation: 'A - B',
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: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I'm not sure how to test is the data view configuration. I didn't see anywhere in the rule definition that directly referenced the data view? Or is this index id below someone a uuid referencing the data view?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this searchConfiguration is passed to the executor a bit differently but I didn't find a way to see how we fetch it in the edit flyout, I will check more and will comment here if I find out something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about creating a data view similar to this and validate the related data view id is correct?

Btw, the value of the optional filter should be saved in the query.query. Can you please also add the expected filter there?
image

// index: 'ef4deffb-805f-4aed-ad80-f481ee47d40b',
// query: {
// language: 'kuery',
// query: '',
// },
// },
}),
})
);
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await nameInput1.click();

await testSubjects.click('thresholdPopover');
await testSubjects.setValue('alertThresholdInput', '420000');
await testSubjects.setValue('alertThresholdInput0', '420000');
await testSubjects.click('forLastExpression');
await testSubjects.setValue('timeWindowSizeNumber', '24');
await testSubjects.setValue('timeWindowUnitSelect', 'hours');
Expand Down
Loading