Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions x-pack/test/functional/services/rules/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import type { RuleResponse } from '@kbn/alerting-plugin/common/routes/rule/response';
import { FtrProviderContext } from '../../ftr_provider_context';

export function RulesAPIServiceProvider({ getService }: FtrProviderContext) {
Expand All @@ -20,6 +21,7 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) {
ruleTypeId,
schedule,
actions = [],
tags = [],
}: {
consumer: string;
name: string;
Expand All @@ -28,6 +30,7 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) {
ruleTypeId: string;
schedule: Record<string, unknown>;
actions?: any[];
tags?: string[];
}) {
log.debug(`Create basic rule...`);
const { body: createdRule } = await kbnSupertest
Expand All @@ -41,6 +44,7 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) {
rule_type_id: ruleTypeId,
schedule,
actions,
tags,
})
.expect(200);
return createdRule;
Expand All @@ -67,5 +71,11 @@ export function RulesAPIServiceProvider({ getService }: FtrProviderContext) {
await this.deleteRule(rule.id);
}
},

async getRule(id: string): Promise<RuleResponse> {
log.debug(`Getting rule with id ${id}...`);
const { body: rule } = await kbnSupertest.get(`/api/alerting/rule/${id}`).expect(200);
return rule;
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
'dashboard',
'timePicker',
]);
const browser = getService('browser');

const security = getService('security');
const retry = getService('retry');
const find = getService('find');
Expand All @@ -42,153 +42,40 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const comboBox = getService('comboBox');
const dashboardAddPanel = getService('dashboardAddPanel');
const toasts = getService('toasts');
const sampleData = getService('sampleData');
const rules = getService('rules');
const es = getService('es');
const config = getService('config');
const retryTimeout = config.get('timeouts.try');

const createEsQueryRule = async (index: string, solution: 'stack' | 'observability') => {
const name = `${solution}-rule`;
const { body: createdRule } = await supertest
.post(`/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send({
name,
rule_type_id: `.es-query`,
enabled: true,
schedule: { interval: '5s' },
consumer: solution === 'stack' ? 'stackAlerts' : 'logs',
tags: [name],
params: {
searchConfiguration: {
query: {
query: '',
language: 'kuery',
},
index,
},
timeField: 'timestamp',
searchType: 'searchSource',
timeWindowSize: 5,
timeWindowUnit: 'h',
threshold: [-1],
thresholdComparator: '>',
size: 1,
aggType: 'count',
groupBy: 'all',
termSize: 5,
excludeHitsFromPreviousRun: false,
sourceFields: [],
},
})
.expect(200);
objectRemover.add(createdRule.id, 'rule', 'alerting');
return createdRule;
};

const createSecurityRule = async (index: string) => {
const { body: createdRule } = await supertest
.post(`/api/detection_engine/rules`)
.set('kbn-xsrf', 'foo')
.send({
type: 'query',
filters: [],
language: 'kuery',
query: '_id: *',
required_fields: [],
data_view_id: index,
author: [],
false_positives: [],
references: [],
risk_score: 21,
risk_score_mapping: [],
severity: 'low',
severity_mapping: [],
threat: [],
max_signals: 100,
name: 'security-rule',
description: 'security-rule',
tags: ['security-rule'],
setup: '',
license: '',
interval: '5s',
from: 'now-10m',
to: 'now',
actions: [],
enabled: true,
meta: {
kibana_siem_app_url: 'http://localhost:5601/app/security',
},
})
.expect(200);
objectRemover.add(createdRule.id, 'rule', 'alerting');
return createdRule;
};

const getSampleWebLogsDataView = async () => {
const { body } = await supertest
.post(`/api/content_management/rpc/search`)
.set('kbn-xsrf', 'foo')
.send({
contentTypeId: 'index-pattern',
query: { limit: 10 },
version: 1,
})
.expect(200);
return body.result.result.hits.find(
(dataView: { attributes: { title: string } }) =>
dataView.attributes.title === 'kibana_sample_data_logs'
);
};
describe('Embeddable alerts panel', () => {
before(async () => {
await sampleData.testResources.installAllKibanaSampleData();

const getRuleSummary = async (ruleId: string) => {
const { body: summary } = await supertest
.get(`/internal/alerting/rule/${encodeURIComponent(ruleId)}/_alert_summary`)
.expect(200);
return summary;
};
const dataViews = await getDataViews();
const sampleDataLogsDataView = dataViews.find(
(dataView) => dataView.title === 'kibana_sample_data_logs'
)!;

// Failing: See https://github.com/elastic/kibana/issues/220807
describe.skip('Embeddable alerts panel', () => {
before(async () => {
await pageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await pageObjects.home.addSampleDataSet('logs');
const dataView = await getSampleWebLogsDataView();
const [stackRule, observabilityRule, securityRule] = await Promise.all([
createEsQueryRule(dataView.id, 'stack'),
createEsQueryRule(dataView.id, 'observability'),
createSecurityRule(dataView.id),
createEsQueryRule(sampleDataLogsDataView.id, 'stack'),
createEsQueryRule(sampleDataLogsDataView.id, 'observability'),
createSecurityRule(sampleDataLogsDataView.id),
]);

// Refresh to see the created rules
await browser.refresh();
await pageObjects.header.waitUntilLoadingHasFinished();
await waitForRuleToBecomeActive(stackRule.id);
await waitForRuleToBecomeActive(observabilityRule.id);
await waitForRuleToBecomeActive(securityRule.id);

// Wait for all rules to have created alerts
const rulesAlerted: Record<string, boolean> = {
[stackRule.id]: false,
[observabilityRule.id]: false,
[securityRule.id]: false,
};
await retry.try(async () => {
const rulesWithoutAlerts = Object.entries(rulesAlerted)
.filter(([_, alerted]) => !alerted)
.map(([ruleId]) => ruleId);
await Promise.all(
rulesWithoutAlerts.map(async (ruleId) => {
const summary = await getRuleSummary(ruleId);
rulesAlerted[ruleId] = Object.keys(summary.alerts).length > 0;
})
);
expect(Object.values(rulesAlerted).every((hasAlerts) => hasAlerts)).to.be(true);
});
await waitForAlertsToBeCreated(stackRule.id);
await waitForAlertsToBeCreated(observabilityRule.id);
await waitForAlertsToBeCreated(securityRule.id);

await pageObjects.dashboard.gotoDashboardURL();
});

after(async () => {
await pageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', {
useActualUrl: true,
});
await pageObjects.home.addSampleDataSet('logs');
await sampleData.testResources.removeAllKibanaSampleData();
await objectRemover.removeAll();
});

Expand Down Expand Up @@ -336,11 +223,134 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await find.clickByCssSelector(
'[data-test-subj=customizePanel] [data-test-subj=superDatePickerToggleQuickMenuButton]'
);
await testSubjects.click('superDatePickerCommonlyUsed_Last_24 hours');
await testSubjects.click('superDatePickerCommonlyUsed_sample_data range');
await testSubjects.click('saveCustomizePanelButton');
await retry.try(async () =>
expect((await testSubjects.findAll('alertsTableEmptyState')).length).to.equal(1)
);
});
});

const createEsQueryRule = async (index: string, solution: 'stack' | 'observability') => {
const name = `${solution}-rule`;
const createdRule = await rules.api.createRule({
name,
ruleTypeId: `.es-query`,
schedule: { interval: '5s' },
consumer: solution === 'stack' ? 'stackAlerts' : 'logs',
tags: [name],
params: {
searchConfiguration: {
query: {
query: '',
language: 'kuery',
},
index,
},
timeField: 'timestamp',
searchType: 'searchSource',
timeWindowSize: 5,
timeWindowUnit: 'h',
threshold: [-1],
thresholdComparator: '>',
size: 1,
aggType: 'count',
groupBy: 'all',
termSize: 5,
excludeHitsFromPreviousRun: false,
sourceFields: [],
},
});

objectRemover.add(createdRule.id, 'rule', 'alerting');

return createdRule;
};

const createSecurityRule = async (index: string) => {
const { body: createdRule } = await supertest
.post(`/api/detection_engine/rules`)
.set('kbn-xsrf', 'foo')
.send({
type: 'query',
filters: [],
language: 'kuery',
query: '_id: *',
required_fields: [],
data_view_id: index,
author: [],
false_positives: [],
references: [],
risk_score: 21,
risk_score_mapping: [],
severity: 'low',
severity_mapping: [],
threat: [],
max_signals: 100,
name: 'security-rule',
description: 'security-rule',
tags: ['security-rule'],
setup: '',
license: '',
interval: '5s',
from: 'now-10m',
to: 'now',
actions: [],
enabled: true,
meta: {
kibana_siem_app_url: 'http://localhost:5601/app/security',
},
})
.expect(200);

objectRemover.add(createdRule.id, 'rule', 'alerting');

return createdRule;
};

const waitForAlertsToBeCreated = async (ruleId: string) => {
return await retry.tryForTime(retryTimeout, async () => {
const response = await es.search({
index: '.alerts*',
query: {
bool: {
filter: [
{
term: {
'kibana.alert.rule.uuid': ruleId,
},
},
],
},
},
});

if (response.hits.hits.length === 0) {
throw new Error(`No hits found for index .alerts* and ruleId ${ruleId}`);
}

return response;
});
};

const waitForRuleToBecomeActive = async (ruleId: string) => {
return await retry.tryForTime(retryTimeout, async () => {
const rule = await rules.api.getRule(ruleId);

const { execution_status: executionStatus } = rule || {};
const { status } = executionStatus || {};

if (status === 'active' || status === 'ok') {
return executionStatus?.status;
}

throw new Error(`waitForStatus(active|ok): got ${status}`);
});
};

const getDataViews = async (): Promise<Array<{ id: string; title: string }>> => {
const response = await supertest.get('/api/data_views').expect(200);

return response.body.data_view;
};
};