[Alerting] Saved object remover for all e2e triggers_actions_ui tests (#86837)

* wip

* Using object remover for alerts list

* wip - using supertest instead of axios

* wip - using supertest instead of axios

* Removing custom services in favor of supertest

* Fixing test

* Fixing test

* Fixing types check

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
ymao1 2021-01-04 09:59:41 -05:00 committed by GitHub
parent c8a349e713
commit b22210d7e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 319 additions and 571 deletions

View file

@ -4,13 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
import uuid from 'uuid';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
function generateUniqueKey() {
return uuid.v4().replace(/-/g, '');
}
import { generateUniqueKey } from '../../lib/get_test_data';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');

View file

@ -4,67 +4,50 @@
* you may not use this file except in compliance with the Elastic License.
*/
import uuid from 'uuid';
import { times } from 'lodash';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
function generateUniqueKey() {
return uuid.v4().replace(/-/g, '');
}
import { ObjectRemover } from '../../lib/object_remover';
import { generateUniqueKey, getTestAlertData, getTestActionData } from '../../lib/get_test_data';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const alerting = getService('alerting');
const testSubjects = getService('testSubjects');
const find = getService('find');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
const supertest = getService('supertest');
const retry = getService('retry');
const objectRemover = new ObjectRemover(supertest);
async function deleteAlerts(alertIds: string[]) {
alertIds.forEach(async (alertId: string) => {
await supertest.delete(`/api/alerts/alert/${alertId}`).set('kbn-xsrf', 'foo').expect(204, '');
async function createAlertManualCleanup(overwrites: Record<string, any> = {}) {
const { body: createdAlert } = await supertest
.post(`/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.send(getTestAlertData(overwrites))
.expect(200);
return createdAlert;
}
async function createFailingAlert() {
return await createAlert({
alertTypeId: 'test.failing',
schedule: { interval: '30s' },
});
}
async function createAlert(overwrites: Record<string, any> = {}) {
const { body: createdAlert } = await supertest
.post(`/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.send({
enabled: true,
name: generateUniqueKey(),
tags: ['foo', 'bar'],
alertTypeId: 'test.noop',
consumer: 'alerts',
schedule: { interval: '1m' },
throttle: '1m',
actions: [],
params: {},
...overwrites,
})
.expect(200);
const createdAlert = await createAlertManualCleanup(overwrites);
objectRemover.add(createdAlert.id, 'alert', 'alerts');
return createdAlert;
}
async function createFailingAlert(overwrites: Record<string, any> = {}) {
const { body: createdAlert } = await supertest
.post(`/api/alerts/alert`)
async function createAction(overwrites: Record<string, any> = {}) {
const { body: createdAction } = await supertest
.post(`/api/actions/action`)
.set('kbn-xsrf', 'foo')
.send({
enabled: true,
name: generateUniqueKey(),
tags: ['foo', 'bar'],
alertTypeId: 'test.failing',
consumer: 'alerts',
schedule: { interval: '30s' },
throttle: '1m',
actions: [],
params: {},
...overwrites,
})
.send(getTestActionData(overwrites))
.expect(200);
return createdAlert;
objectRemover.add(createdAction.id, 'action', 'actions');
return createdAction;
}
async function refreshAlertsList() {
@ -77,11 +60,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.click('alertsTab');
});
afterEach(async () => {
await objectRemover.removeAll();
});
it('should display alerts in alphabetical order', async () => {
const uniqueKey = generateUniqueKey();
const a = await createAlert({ name: 'b', tags: [uniqueKey] });
const b = await createAlert({ name: 'c', tags: [uniqueKey] });
const c = await createAlert({ name: 'a', tags: [uniqueKey] });
await createAlert({ name: 'b', tags: [uniqueKey] });
await createAlert({ name: 'c', tags: [uniqueKey] });
await createAlert({ name: 'a', tags: [uniqueKey] });
await refreshAlertsList();
await pageObjects.triggersActionsUI.searchAlerts(uniqueKey);
@ -90,8 +77,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(searchResults[0].name).to.eql('a');
expect(searchResults[1].name).to.eql('b');
expect(searchResults[2].name).to.eql('c');
await deleteAlerts([a.id, b.id, c.id]);
});
it('should search for alert', async () => {
@ -108,7 +93,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
interval: '1m',
},
]);
await deleteAlerts([createdAlert.id]);
});
it('should search for tags', async () => {
@ -125,16 +109,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
interval: '1m',
},
]);
await deleteAlerts([createdAlert.id]);
});
it('should display an empty list when search did not return any alerts', async () => {
const createdAlert = await createAlert();
await createAlert();
await refreshAlertsList();
await pageObjects.triggersActionsUI.searchAlerts(`An Alert That For Sure Doesn't Exist!`);
expect(await pageObjects.triggersActionsUI.isAlertsListDisplayed()).to.eql(true);
await deleteAlerts([createdAlert.id]);
});
it('should disable single alert', async () => {
@ -153,7 +135,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const disableSwitchAfterDisable = await testSubjects.find('disableSwitch');
const isChecked = await disableSwitchAfterDisable.getAttribute('aria-checked');
expect(isChecked).to.eql('true');
await deleteAlerts([createdAlert.id]);
});
it('should re-enable single alert', async () => {
@ -178,7 +159,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const disableSwitchAfterReEnable = await testSubjects.find('disableSwitch');
const isChecked = await disableSwitchAfterReEnable.getAttribute('aria-checked');
expect(isChecked).to.eql('false');
await deleteAlerts([createdAlert.id]);
});
it('should mute single alert', async () => {
@ -197,7 +177,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const muteSwitchAfterMute = await testSubjects.find('muteSwitch');
const isChecked = await muteSwitchAfterMute.getAttribute('aria-checked');
expect(isChecked).to.eql('true');
await deleteAlerts([createdAlert.id]);
});
it('should unmute single alert', async () => {
@ -222,12 +201,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const muteSwitchAfterUnmute = await testSubjects.find('muteSwitch');
const isChecked = await muteSwitchAfterUnmute.getAttribute('aria-checked');
expect(isChecked).to.eql('false');
await deleteAlerts([createdAlert.id]);
});
it('should delete single alert', async () => {
const firstAlert = await createAlert();
const secondAlert = await createAlert();
await createAlert();
const secondAlert = await createAlertManualCleanup();
await refreshAlertsList();
await pageObjects.triggersActionsUI.searchAlerts(secondAlert.name);
@ -246,7 +224,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.triggersActionsUI.searchAlerts(secondAlert.name);
const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getAlertsList();
expect(searchResultsAfterDelete.length).to.eql(0);
await deleteAlerts([firstAlert.id]);
});
it('should mute all selection', async () => {
@ -270,7 +247,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const muteSwitch = await testSubjects.find('muteSwitch');
const isChecked = await muteSwitch.getAttribute('aria-checked');
expect(isChecked).to.eql('true');
await deleteAlerts([createdAlert.id]);
});
it('should unmute all selection', async () => {
@ -296,7 +272,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const muteSwitch = await testSubjects.find('muteSwitch');
const isChecked = await muteSwitch.getAttribute('aria-checked');
expect(isChecked).to.eql('false');
await deleteAlerts([createdAlert.id]);
});
it('should disable all selection', async () => {
@ -320,7 +295,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const disableSwitch = await testSubjects.find('disableSwitch');
const isChecked = await disableSwitch.getAttribute('aria-checked');
expect(isChecked).to.eql('true');
await deleteAlerts([createdAlert.id]);
});
it('should enable all selection', async () => {
@ -346,14 +320,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const disableSwitch = await testSubjects.find('disableSwitch');
const isChecked = await disableSwitch.getAttribute('aria-checked');
expect(isChecked).to.eql('false');
await deleteAlerts([createdAlert.id]);
});
it.skip('should delete all selection', async () => {
const namePrefix = generateUniqueKey();
let count = 0;
const createdAlertsFirstPage = await Promise.all(
times(2, () => createAlert({ name: `${namePrefix}-0${count++}` }))
times(2, () => createAlertManualCleanup({ name: `${namePrefix}-0${count++}` }))
);
await refreshAlertsList();
await pageObjects.triggersActionsUI.searchAlerts(namePrefix);
@ -380,13 +353,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('should filter alerts by the status', async () => {
const createdAlert = await createAlert();
const failinfAlert = await createFailingAlert();
await createAlert();
const failingAlert = await createFailingAlert();
// initialy alert get Pending status, so we need to retry refresh list logic to get the post execution statuses
await retry.try(async () => {
await refreshAlertsList();
const refreshResults = await pageObjects.triggersActionsUI.getAlertsListWithStatus();
expect(refreshResults.map((item) => item.status).sort()).to.eql(['Error', 'Ok']);
expect(refreshResults.map((item: any) => item.status).sort()).to.eql(['Error', 'Ok']);
});
await testSubjects.click('alertStatusFilterButton');
await testSubjects.click('alertStatuserrorFilerOption'); // select Error status filter
@ -394,7 +367,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const filterErrorOnlyResults = await pageObjects.triggersActionsUI.getAlertsListWithStatus();
expect(filterErrorOnlyResults).to.eql([
{
name: failinfAlert.name,
name: failingAlert.name,
tagsText: 'foo, bar',
alertType: 'Test: Failing',
interval: '30s',
@ -402,8 +375,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
},
]);
});
await deleteAlerts([createdAlert.id, failinfAlert.id]);
});
it('should display total alerts by status and error banner only when exists alerts with status error', async () => {
@ -427,7 +398,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
);
expect(alertsErrorBannerWhenNoErrors).to.have.length(0);
const failingAlert = await createFailingAlert();
await createFailingAlert();
await retry.try(async () => {
await refreshAlertsList();
const alertsErrorBannerExistErrors = await find.allByCssSelector(
@ -449,13 +420,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
expect(await testSubjects.getVisibleText('totalErrorAlertsCount')).to.be('Error: 1');
expect(await testSubjects.getVisibleText('totalPendingAlertsCount')).to.be('Pending: 0');
expect(await testSubjects.getVisibleText('totalUnknownAlertsCount')).to.be('Unknown: 0');
await deleteAlerts([createdAlert.id, failingAlert.id]);
});
it('should filter alerts by the alert type', async () => {
const noopAlert = await createAlert();
const failinfAlert = await createFailingAlert();
await createAlert();
const failingAlert = await createFailingAlert();
await refreshAlertsList();
await testSubjects.click('alertTypeFilterButton');
expect(await (await testSubjects.find('alertType0Group')).getVisibleText()).to.eql('Alerts');
@ -465,27 +434,18 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const filterFailingAlertOnlyResults = await pageObjects.triggersActionsUI.getAlertsList();
expect(filterFailingAlertOnlyResults).to.eql([
{
name: failinfAlert.name,
name: failingAlert.name,
tagsText: 'foo, bar',
alertType: 'Test: Failing',
interval: '30s',
},
]);
});
await deleteAlerts([noopAlert.id, failinfAlert.id]);
});
it('should filter alerts by the action type', async () => {
const noopAlert = await createAlert();
const action = await alerting.actions.createAction({
name: `slack-${Date.now()}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
});
await createAlert();
const action = await createAction();
const noopAlertWithAction = await createAlert({
actions: [
{
@ -511,8 +471,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
},
]);
});
await deleteAlerts([noopAlertWithAction.id, noopAlert.id]);
});
});
};

View file

@ -4,35 +4,35 @@
* you may not use this file except in compliance with the Elastic License.
*/
import uuid from 'uuid';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
function generateUniqueKey() {
return uuid.v4().replace(/-/g, '');
}
import { ObjectRemover } from '../../lib/object_remover';
import { generateUniqueKey, getTestActionData } from '../../lib/get_test_data';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const alerting = getService('alerting');
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
const find = getService('find');
const retry = getService('retry');
const comboBox = getService('comboBox');
const supertest = getService('supertest');
describe('Connectors', function () {
before(async () => {
await alerting.actions.createAction({
name: `slack-${Date.now()}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
});
const objectRemover = new ObjectRemover(supertest);
before(async () => {
const { body: createdAction } = await supertest
.post(`/api/actions/action`)
.set('kbn-xsrf', 'foo')
.send(getTestActionData())
.expect(200);
await pageObjects.common.navigateToApp('triggersActions');
await testSubjects.click('connectorsTab');
objectRemover.add(createdAction.id, 'action', 'actions');
});
after(async () => {
await objectRemover.removeAll();
});
it('should create a connector', async () => {

View file

@ -9,53 +9,103 @@ import uuid from 'uuid';
import { omit, mapValues, range, flatten } from 'lodash';
import moment from 'moment';
import { FtrProviderContext } from '../../ftr_provider_context';
import { ObjectRemover } from '../../lib/object_remover';
import { alwaysFiringAlertType } from '../../fixtures/plugins/alerts/server/plugin';
import { getTestAlertData, getTestActionData } from '../../lib/get_test_data';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header', 'alertDetailsUI']);
const browser = getService('browser');
const log = getService('log');
const alerting = getService('alerting');
const retry = getService('retry');
const find = getService('find');
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
async function createAction(overwrites: Record<string, any> = {}) {
const { body: createdAction } = await supertest
.post(`/api/actions/action`)
.set('kbn-xsrf', 'foo')
.send(getTestActionData(overwrites))
.expect(200);
objectRemover.add(createdAction.id, 'action', 'actions');
return createdAction;
}
async function createAlert(overwrites: Record<string, any> = {}) {
const { body: createdAlert } = await supertest
.post(`/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.send(getTestAlertData(overwrites))
.expect(200);
objectRemover.add(createdAlert.id, 'alert', 'alerts');
return createdAlert;
}
async function createAlwaysFiringAlert(overwrites: Record<string, any> = {}) {
const { body: createdAlert } = await supertest
.post(`/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.send(
getTestAlertData({
alertTypeId: 'test.always-firing',
...overwrites,
})
)
.expect(200);
objectRemover.add(createdAlert.id, 'alert', 'alerts');
return createdAlert;
}
async function createActions(testRunUuid: string) {
return await Promise.all([
createAction({ name: `slack-${testRunUuid}-${0}` }),
createAction({ name: `slack-${testRunUuid}-${1}` }),
]);
}
async function createAlertWithActionsAndParams(
testRunUuid: string,
params: Record<string, any> = {}
) {
const actions = await createActions(testRunUuid);
return await createAlwaysFiringAlert({
name: `test-alert-${testRunUuid}`,
actions: actions.map((action) => ({
id: action.id,
group: 'default',
params: {
message: 'from alert 1s',
level: 'warn',
},
})),
params,
});
}
async function getAlertInstanceSummary(alertId: string) {
const { body: summary } = await supertest
.get(`/api/alerts/alert/${alertId}/_instance_summary`)
.expect(200);
return summary;
}
async function muteAlertInstance(alertId: string, alertInstanceId: string) {
const { body: response } = await supertest
.post(`/api/alerts/alert/${alertId}/alert_instance/${alertInstanceId}/_mute`)
.set('kbn-xsrf', 'foo')
.expect(204);
return response;
}
describe('Alert Details', function () {
describe('Header', function () {
const testRunUuid = uuid.v4();
before(async () => {
await pageObjects.common.navigateToApp('triggersActions');
const actions = await Promise.all([
alerting.actions.createAction({
name: `slack-${testRunUuid}-${0}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
}),
alerting.actions.createAction({
name: `slack-${testRunUuid}-${1}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
}),
]);
const alert = await alerting.alerts.createAlwaysFiringWithActions(
`test-alert-${testRunUuid}`,
actions.map((action) => ({
id: action.id,
group: 'default',
params: {
message: 'from alert 1s',
level: 'warn',
},
}))
);
const alert = await createAlertWithActionsAndParams(testRunUuid);
// refresh to see alert
await browser.refresh();
@ -69,6 +119,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
});
after(async () => {
await objectRemover.removeAll();
});
it('renders the alert details', async () => {
const headingText = await pageObjects.alertDetailsUI.getHeadingText();
expect(headingText).to.be(`test-alert-${testRunUuid}`);
@ -154,33 +208,41 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
describe('Edit alert button', function () {
const testRunUuid = uuid.v4();
const alertName = uuid.v4();
const updatedAlertName = `Changed Alert Name ${alertName}`;
it('should open edit alert flyout', async () => {
await pageObjects.common.navigateToApp('triggersActions');
const params = {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000, 5000],
index: ['.kibana_1'],
timeField: 'alert',
};
const alert = await alerting.alerts.createAlertWithActions(
testRunUuid,
'.index-threshold',
params,
[
before(async () => {
await createAlwaysFiringAlert({
name: alertName,
alertTypeId: '.index-threshold',
params: {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000, 5000],
index: ['.kibana_1'],
timeField: 'alert',
},
actions: [
{
group: 'threshold met',
id: 'my-server-log',
params: { level: 'info', message: ' {{context.message}}' },
},
]
);
],
});
});
after(async () => {
await objectRemover.removeAll();
});
it('should open edit alert flyout', async () => {
await pageObjects.common.navigateToApp('triggersActions');
// refresh to see alert
await browser.refresh();
@ -190,13 +252,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.existOrFail('alertsList');
// click on first alert
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alertName);
const editButton = await testSubjects.find('openEditAlertFlyoutButton');
await editButton.click();
expect(await testSubjects.exists('hasActionsDisabled')).to.eql(false);
const updatedAlertName = `Changed Alert Name ${uuid.v4()}`;
await testSubjects.setValue('alertNameInput', updatedAlertName, {
clearWithKeyboard: true,
});
@ -212,22 +273,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('should reset alert when canceling an edit', async () => {
await pageObjects.common.navigateToApp('triggersActions');
const params = {
aggType: 'count',
termSize: 5,
thresholdComparator: '>',
timeWindowSize: 5,
timeWindowUnit: 'm',
groupBy: 'all',
threshold: [1000, 5000],
index: ['.kibana_1'],
timeField: 'alert',
};
const alert = await alerting.alerts.createAlertWithActions(
testRunUuid,
'.index-threshold',
params
);
// refresh to see alert
await browser.refresh();
@ -237,13 +283,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.existOrFail('alertsList');
// click on first alert
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(updatedAlertName);
const editButton = await testSubjects.find('openEditAlertFlyoutButton');
await editButton.click();
const updatedAlertName = `Changed Alert Name ${uuid.v4()}`;
await testSubjects.setValue('alertNameInput', updatedAlertName, {
await testSubjects.setValue('alertNameInput', uuid.v4(), {
clearWithKeyboard: true,
});
@ -254,23 +299,29 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const nameInputAfterCancel = await testSubjects.find('alertNameInput');
const textAfterCancel = await nameInputAfterCancel.getAttribute('value');
expect(textAfterCancel).to.eql(alert.name);
expect(textAfterCancel).to.eql(updatedAlertName);
});
});
describe('View In App', function () {
const testRunUuid = uuid.v4();
const alertName = uuid.v4();
beforeEach(async () => {
await pageObjects.common.navigateToApp('triggersActions');
});
after(async () => {
await objectRemover.removeAll();
});
it('renders the alert details view in app button', async () => {
const alert = await alerting.alerts.createNoOp(`test-alert-${testRunUuid}`);
const alert = await createAlert({
name: alertName,
consumer: 'alerting_fixture',
});
// refresh to see alert
await browser.refresh();
await pageObjects.header.waitUntilLoadingHasFinished();
// Verify content
@ -287,14 +338,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('renders a disabled alert details view in app button', async () => {
const alert = await alerting.alerts.createAlwaysFiringWithActions(
`test-alert-disabled-nav`,
[]
);
const alert = await createAlwaysFiringAlert({
name: `test-alert-disabled-nav`,
});
// refresh to see alert
await browser.refresh();
await pageObjects.header.waitUntilLoadingHasFinished();
// Verify content
@ -314,44 +363,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
before(async () => {
await pageObjects.common.navigateToApp('triggersActions');
const actions = await Promise.all([
alerting.actions.createAction({
name: `slack-${testRunUuid}-${0}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
}),
alerting.actions.createAction({
name: `slack-${testRunUuid}-${1}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
}),
]);
const instances = [{ id: 'us-central' }, { id: 'us-east' }, { id: 'us-west' }];
alert = await alerting.alerts.createAlwaysFiringWithActions(
`test-alert-${testRunUuid}`,
actions.map((action) => ({
id: action.id,
group: 'default',
params: {
message: 'from alert 1s',
level: 'warn',
},
})),
{
instances,
}
);
alert = await createAlertWithActionsAndParams(testRunUuid, {
instances,
});
// refresh to see alert
await browser.refresh();
await pageObjects.header.waitUntilLoadingHasFinished();
// Verify content
@ -362,13 +380,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// await first run to complete so we have an initial state
await retry.try(async () => {
const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary(
alert.id
);
const { instances: alertInstances } = await getAlertInstanceSummary(alert.id);
expect(Object.keys(alertInstances).length).to.eql(instances.length);
});
});
after(async () => {
await objectRemover.removeAll();
});
it('renders the active alert instances', async () => {
// refresh to ensure Api call and UI are looking at freshest output
await browser.refresh();
@ -384,8 +404,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
(actionGroup: { id: string; name: string }) => actionGroup.id === actionGroupId
)?.name;
const summary = await alerting.alerts.getAlertInstanceSummary(alert.id);
const dateOnAllInstancesFromApiResponse = mapValues(
const summary = await getAlertInstanceSummary(alert.id);
const dateOnAllInstancesFromApiResponse: Record<string, string> = mapValues(
summary.instances,
(instance) => instance.activeStartDate
);
@ -404,7 +424,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
.join(', ')}`
);
const instancesList = await pageObjects.alertDetailsUI.getAlertInstancesList();
const instancesList: any[] = await pageObjects.alertDetailsUI.getAlertInstancesList();
expect(instancesList.map((instance) => omit(instance, 'duration'))).to.eql([
{
instance: 'us-central',
@ -474,12 +494,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('renders the muted inactive alert instances', async () => {
// mute an alert instance that doesn't exist
await alerting.alerts.muteAlertInstance(alert.id, 'eu-east');
await muteAlertInstance(alert.id, 'eu-east');
// refresh to see alert
await browser.refresh();
const instancesList = await pageObjects.alertDetailsUI.getAlertInstancesList();
const instancesList: any[] = await pageObjects.alertDetailsUI.getAlertInstancesList();
expect(
instancesList.filter((alertInstance) => alertInstance.instance === 'eu-east')
).to.eql([
@ -545,25 +565,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
before(async () => {
await pageObjects.common.navigateToApp('triggersActions');
const actions = await Promise.all([
alerting.actions.createAction({
name: `slack-${testRunUuid}-${0}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
}),
alerting.actions.createAction({
name: `slack-${testRunUuid}-${1}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
}),
]);
const instances = flatten(
range(10).map((index) => [
{ id: `us-central-${index}` },
@ -571,26 +572,13 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
{ id: `us-west-${index}` },
])
);
alert = await alerting.alerts.createAlwaysFiringWithActions(
`test-alert-${testRunUuid}`,
actions.map((action) => ({
id: action.id,
group: 'default',
params: {
message: 'from alert 1s',
level: 'warn',
},
})),
{
instances,
}
);
alert = await createAlertWithActionsAndParams(testRunUuid, {
instances,
});
// await first run to complete so we have an initial state
await retry.try(async () => {
const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary(
alert.id
);
const { instances: alertInstances } = await getAlertInstanceSummary(alert.id);
expect(Object.keys(alertInstances).length).to.eql(instances.length);
});
@ -606,14 +594,16 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
});
after(async () => {
await objectRemover.removeAll();
});
const PAGE_SIZE = 10;
it('renders the first page', async () => {
// Verify content
await testSubjects.existOrFail('alertInstancesList');
const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary(
alert.id
);
const { instances: alertInstances } = await getAlertInstanceSummary(alert.id);
const items = await pageObjects.alertDetailsUI.getAlertInstancesList();
expect(items.length).to.eql(PAGE_SIZE);
@ -626,9 +616,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// Verify content
await testSubjects.existOrFail('alertInstancesList');
const { instances: alertInstances } = await alerting.alerts.getAlertInstanceSummary(
alert.id
);
const { instances: alertInstances } = await getAlertInstanceSummary(alert.id);
await pageObjects.alertDetailsUI.clickPaginationNextPage();

View file

@ -6,19 +6,26 @@
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
import { ObjectRemover } from '../../lib/object_remover';
import { getTestAlertData, getTestActionData } from '../../lib/get_test_data';
export default ({ getPageObjects, getService }: FtrProviderContext) => {
const testSubjects = getService('testSubjects');
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
const log = getService('log');
const browser = getService('browser');
const alerting = getService('alerting');
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
describe('Home page', function () {
before(async () => {
await pageObjects.common.navigateToApp('triggersActions');
});
after(async () => {
await objectRemover.removeAll();
});
it('Loads the app', async () => {
await log.debug('Checking for section heading to say Triggers and Actions.');
@ -58,26 +65,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('navigates to an alert details page', async () => {
const action = await alerting.actions.createAction({
name: `Slack-${Date.now()}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
});
const { body: createdAction } = await supertest
.post(`/api/actions/action`)
.set('kbn-xsrf', 'foo')
.send(getTestActionData())
.expect(200);
objectRemover.add(createdAction.id, 'action', 'actions');
const alert = await alerting.alerts.createAlwaysFiringWithAction(
`test-alert-${Date.now()}`,
{
id: action.id,
group: 'default',
params: {
message: 'from alert 1s',
level: 'warn',
},
}
);
const { body: createdAlert } = await supertest
.post(`/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.send(getTestAlertData())
.expect(200);
objectRemover.add(createdAlert.id, 'alert', 'alerts');
// refresh to see alert
await browser.refresh();
@ -88,12 +88,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.existOrFail('alertsList');
// click on first alert
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(createdAlert.name);
// Verify url
expect(await browser.getCurrentUrl()).to.contain(`/alert/${alert.id}`);
await alerting.alerts.deleteAlert(alert.id);
expect(await browser.getCurrentUrl()).to.contain(`/alert/${createdAlert.id}`);
});
});
});

View file

@ -7,7 +7,6 @@
import { resolve, join } from 'path';
import { CA_CERT_PATH } from '@kbn/dev-utils';
import { FtrConfigProviderContext } from '@kbn/test/types/ftr';
import { services } from './services';
import { pageObjects } from './page_objects';
// .server-log is specifically not enabled
@ -39,7 +38,6 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const returnedObject = {
...xpackFunctionalConfig.getAll(),
servers,
services,
pageObjects,
// list paths to the files that contain your plugins tests
testFiles: [

View file

@ -5,8 +5,7 @@
*/
import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
import { services } from '../functional/services';
import { pageObjects } from './page_objects';
import { services } from './services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, typeof pageObjects>;

View file

@ -0,0 +1,39 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import uuid from 'uuid';
export function generateUniqueKey() {
return uuid.v4().replace(/-/g, '');
}
export function getTestAlertData(overwrites = {}) {
return {
enabled: true,
name: generateUniqueKey(),
tags: ['foo', 'bar'],
alertTypeId: 'test.noop',
consumer: 'alerts',
schedule: { interval: '1m' },
throttle: '1m',
notifyWhen: 'onThrottleInterval',
actions: [],
params: {},
...overwrites,
};
}
export function getTestActionData(overwrites = {}) {
return {
name: `slack-${Date.now()}`,
actionTypeId: '.slack',
config: {},
secrets: {
webhookUrl: 'https://test',
},
...overwrites,
};
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
interface ObjectToRemove {
id: string;
type: string;
plugin: string;
}
export class ObjectRemover {
private readonly supertest: any;
private objectsToRemove: ObjectToRemove[] = [];
constructor(supertest: any) {
this.supertest = supertest;
}
add(id: ObjectToRemove['id'], type: ObjectToRemove['type'], plugin: ObjectToRemove['plugin']) {
this.objectsToRemove.push({ id, type, plugin });
}
async removeAll() {
await Promise.all(
this.objectsToRemove.map(({ id, type, plugin }) => {
return this.supertest
.delete(`/api/${plugin}/${type}/${id}`)
.set('kbn-xsrf', 'foo')
.expect(204);
})
);
this.objectsToRemove = [];
}
}

View file

@ -32,7 +32,7 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) {
const $ = await table.parseDomContent();
return $.findTestSubjects('alert-instance-row')
.toArray()
.map((row) => {
.map((row: CheerioElement) => {
return {
instance: $(row)
.findTestSubject('alertInstancesTableCell-instance')
@ -86,7 +86,7 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) {
$.findTestSubjects('alert-instance-row')
.toArray()
.filter(
(row) =>
(row: CheerioElement) =>
$(row)
.findTestSubject('alertInstancesTableCell-instance')
.find('.euiTableCellContent')

View file

@ -78,7 +78,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
const $ = await table.parseDomContent();
return $.findTestSubjects('connectors-row')
.toArray()
.map((row) => {
.map((row: CheerioElement) => {
return {
name: $(row)
.findTestSubject('connectorsTableCell-name')
@ -96,7 +96,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
const $ = await table.parseDomContent();
return $.findTestSubjects('alert-row')
.toArray()
.map((row) => {
.map((row: CheerioElement) => {
return getRowItemData(row, $);
});
},
@ -105,7 +105,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
const $ = await table.parseDomContent();
return $.findTestSubjects('alert-row')
.toArray()
.map((row) => {
.map((row: CheerioElement) => {
const rowItem = getRowItemData(row, $);
return {
...rowItem,

View file

@ -1,49 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import axios, { AxiosInstance } from 'axios';
import util from 'util';
import { ToolingLog } from '@kbn/dev-utils';
export class Actions {
private log: ToolingLog;
private axios: AxiosInstance;
constructor(url: string, log: ToolingLog) {
this.log = log;
this.axios = axios.create({
headers: { 'kbn-xsrf': 'x-pack/ftr/services/alerting/actions' },
baseURL: url,
maxRedirects: 0,
validateStatus: () => true, // we do our own validation below and throw better error messages
});
}
public async createAction(actionParams: {
name: string;
actionTypeId: string;
config: Record<string, any>;
secrets: Record<string, any>;
}) {
this.log.debug(`creating action ${actionParams.name}`);
const {
data: action,
status: actionStatus,
statusText: actionStatusText,
} = await this.axios.post(`/api/actions/action`, actionParams);
if (actionStatus !== 200) {
throw new Error(
`Expected status code of 200, received ${actionStatus} ${actionStatusText}: ${util.inspect(
action
)}`
);
}
this.log.debug(`created action ${action.id}`);
return action;
}
}

View file

@ -1,180 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import axios, { AxiosInstance } from 'axios';
import util from 'util';
import { ToolingLog } from '@kbn/dev-utils';
export interface AlertInstanceSummary {
status: string;
muted: boolean;
enabled: boolean;
lastRun?: string;
errorMessage?: string;
instances: Record<string, AlertInstanceStatus>;
}
export interface AlertInstanceStatus {
status: string;
muted: boolean;
actionGroupId: string;
activeStartDate?: string;
}
export class Alerts {
private log: ToolingLog;
private axios: AxiosInstance;
constructor(url: string, log: ToolingLog) {
this.log = log;
this.axios = axios.create({
headers: { 'kbn-xsrf': 'x-pack/ftr/services/alerting/alerts' },
baseURL: url,
maxRedirects: 0,
validateStatus: () => true, // we do our own validation below and throw better error messages
});
}
public async createAlertWithActions(
name: string,
alertTypeId: string,
params?: Record<string, any>,
actions?: Array<{
id: string;
group: string;
params: Record<string, any>;
}>,
tags?: string[],
consumer?: string,
schedule?: Record<string, any>,
throttle?: string
) {
this.log.debug(`creating alert ${name}`);
const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, {
enabled: true,
name,
tags,
alertTypeId,
consumer: consumer ?? 'alerts',
schedule: schedule ?? { interval: '1m' },
throttle: throttle ?? '1m',
actions: actions ?? [],
params: params ?? {},
});
if (status !== 200) {
throw new Error(
`Expected status code of 200, received ${status} ${statusText}: ${util.inspect(alert)}`
);
}
this.log.debug(`created alert ${alert.id}`);
return alert;
}
public async createNoOp(name: string) {
this.log.debug(`creating alert ${name}`);
const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, {
enabled: true,
name,
tags: ['foo'],
alertTypeId: 'test.noop',
consumer: 'alerting_fixture',
schedule: { interval: '1m' },
throttle: '1m',
actions: [],
params: {},
});
if (status !== 200) {
throw new Error(
`Expected status code of 200, received ${status} ${statusText}: ${util.inspect(alert)}`
);
}
this.log.debug(`created alert ${alert.id}`);
return alert;
}
public async createAlwaysFiringWithActions(
name: string,
actions: Array<{
id: string;
group: string;
params: Record<string, any>;
}>,
params: Record<string, any> = {}
) {
this.log.debug(`creating alert ${name}`);
const { data: alert, status, statusText } = await this.axios.post(`/api/alerts/alert`, {
enabled: true,
name,
tags: ['foo'],
alertTypeId: 'test.always-firing',
consumer: 'alerts',
schedule: { interval: '1m' },
throttle: '1m',
actions,
params,
});
if (status !== 200) {
throw new Error(
`Expected status code of 200, received ${status} ${statusText}: ${util.inspect(alert)}`
);
}
this.log.debug(`created alert ${alert.id}`);
return alert;
}
public async createAlwaysFiringWithAction(
name: string,
action: {
id: string;
group: string;
params: Record<string, any>;
}
) {
return this.createAlwaysFiringWithActions(name, [action]);
}
public async deleteAlert(id: string) {
this.log.debug(`deleting alert ${id}`);
const { data: alert, status, statusText } = await this.axios.delete(`/api/alerts/alert/${id}`);
if (status !== 204) {
throw new Error(
`Expected status code of 204, received ${status} ${statusText}: ${util.inspect(alert)}`
);
}
this.log.debug(`deleted alert ${alert.id}`);
}
public async getAlertInstanceSummary(id: string): Promise<AlertInstanceSummary> {
this.log.debug(`getting alert ${id} state`);
const { data } = await this.axios.get(`/api/alerts/alert/${id}/_instance_summary`);
return data;
}
public async muteAlertInstance(id: string, instanceId: string) {
this.log.debug(`muting instance ${instanceId} under alert ${id}`);
const { data: alert, status, statusText } = await this.axios.post(
`/api/alerts/alert/${id}/alert_instance/${instanceId}/_mute`
);
if (status !== 204) {
throw new Error(
`Expected status code of 204, received ${status} ${statusText}: ${util.inspect(alert)}`
);
}
this.log.debug(`muted alert instance ${instanceId}`);
}
}

View file

@ -1,22 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { format as formatUrl } from 'url';
import { Alerts } from './alerts';
import { Actions } from './actions';
import { FtrProviderContext } from '../../ftr_provider_context';
export function AlertsServiceProvider({ getService }: FtrProviderContext) {
const log = getService('log');
const config = getService('config');
const url = formatUrl(config.get('servers.kibana'));
return new (class AlertingService {
actions = new Actions(url, log);
alerts = new Alerts(url, log);
})();
}

View file

@ -1,13 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { services as xpackFunctionalServices } from '../../functional/services';
import { AlertsServiceProvider } from './alerting';
export const services = {
...xpackFunctionalServices,
alerting: AlertsServiceProvider,
};