[RAM] Add alert summary API (#146709)

## Summary

Resolve: https://github.com/elastic/kibana/issues/141487


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Xavier Mouligneau 2022-12-16 11:39:00 -05:00 committed by GitHub
parent e43ccd13c6
commit 3917bf54c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 614 additions and 7 deletions

View file

@ -20,6 +20,7 @@ const createAlertsClientMock = () => {
find: jest.fn(),
getFeatureIdsByRegistrationContexts: jest.fn(),
getBrowserFields: jest.fn(),
getAlertSummary: jest.fn(),
};
return mocked;
};

View file

@ -11,11 +11,16 @@ import { Filter, buildEsQuery, EsQueryConfig } from '@kbn/es-query';
import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils';
import {
AlertConsumers,
ALERT_TIME_RANGE,
ALERT_STATUS,
getEsQueryConfig,
getSafeSortIds,
isValidFeatureId,
STATUS_VALUES,
ValidFeatureId,
ALERT_STATUS_RECOVERED,
ALERT_END,
ALERT_STATUS_ACTIVE,
} from '@kbn/rule-data-utils';
import {
@ -32,6 +37,7 @@ import {
import { Logger, ElasticsearchClient, EcsEventOutcome } from '@kbn/core/server';
import { AuditLogger } from '@kbn/security-plugin/server';
import { IndexPatternsFetcher } from '@kbn/data-plugin/server';
import { isEmpty } from 'lodash';
import { BrowserFields } from '../../common';
import { alertAuditEvent, operationAlertAuditActionMap } from './audit_events';
import {
@ -92,6 +98,15 @@ interface GetAlertParams {
index?: string;
}
interface GetAlertSummaryParams {
id?: string;
gte: string;
lte: string;
featureIds: string[];
filter?: estypes.QueryDslQueryContainer[];
fixedInterval?: string;
}
interface SingleSearchAfterAndAudit {
id?: string | null | undefined;
query?: string | object | undefined;
@ -500,6 +515,115 @@ export class AlertsClient {
}
}
public async getAlertSummary({
gte,
lte,
featureIds,
filter,
fixedInterval = '1m',
}: GetAlertSummaryParams) {
try {
const indexToUse = await this.getAuthorizedAlertsIndices(featureIds);
if (isEmpty(indexToUse)) {
throw Boom.badRequest('no featureIds were provided for getting alert summary');
}
// first search for the alert by id, then use the alert info to check if user has access to it
const responseAlertSum = await this.singleSearchAfterAndAudit({
index: (indexToUse ?? []).join(),
operation: ReadOperations.Get,
aggs: {
active_alerts_bucket: {
date_histogram: {
field: ALERT_TIME_RANGE,
fixed_interval: fixedInterval,
hard_bounds: {
min: gte,
max: lte,
},
extended_bounds: {
min: gte,
max: lte,
},
min_doc_count: 0,
},
},
recovered_alerts: {
filter: {
term: {
[ALERT_STATUS]: ALERT_STATUS_RECOVERED,
},
},
aggs: {
container: {
date_histogram: {
field: ALERT_END,
fixed_interval: fixedInterval,
extended_bounds: {
min: gte,
max: lte,
},
min_doc_count: 0,
},
},
},
},
count: {
terms: { field: ALERT_STATUS },
},
},
query: {
bool: {
filter: [
{
range: {
[ALERT_TIME_RANGE]: {
gt: gte,
lt: lte,
},
},
},
...(filter ? filter : []),
],
},
},
size: 0,
});
let activeAlertCount = 0;
let recoveredAlertCount = 0;
(
(responseAlertSum.aggregations?.count as estypes.AggregationsMultiBucketAggregateBase)
.buckets as estypes.AggregationsStringTermsBucketKeys[]
).forEach((b) => {
if (b.key === ALERT_STATUS_ACTIVE) {
activeAlertCount = b.doc_count;
} else if (b.key === ALERT_STATUS_RECOVERED) {
recoveredAlertCount = b.doc_count;
}
});
return {
activeAlertCount,
recoveredAlertCount,
activeAlerts:
(
responseAlertSum.aggregations
?.active_alerts_bucket as estypes.AggregationsAutoDateHistogramAggregate
)?.buckets ?? [],
recoveredAlerts:
(
(responseAlertSum.aggregations?.recovered_alerts as estypes.AggregationsFilterAggregate)
?.container as estypes.AggregationsAutoDateHistogramAggregate
)?.buckets ?? [],
};
} catch (error) {
this.logger.error(`getAlertSummary threw an error: ${error}`);
throw error;
}
}
public async update<Params extends RuleTypeParams = never>({
id,
status,

View file

@ -0,0 +1,116 @@
/*
* 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 { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
import { getAlertSummaryRoute } from './get_alert_summary';
import { requestContextMock } from './__mocks__/request_context';
import { requestMock, serverMock } from './__mocks__/server';
describe('getAlertSummaryRoute', () => {
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
beforeEach(async () => {
server = serverMock.create();
({ clients, context } = requestContextMock.createTools());
clients.rac.getAlertSummary.mockResolvedValue({
activeAlertCount: 0,
recoveredAlertCount: 0,
activeAlerts: [],
recoveredAlerts: [],
});
getAlertSummaryRoute(server.router);
});
describe('request validation', () => {
test('rejects invalid query params', async () => {
await expect(
server.inject(
requestMock.create({
method: 'post',
path: `${BASE_RAC_ALERTS_API_PATH}/_alert_summary`,
body: { gte: 4, lte: 3, featureIds: ['logs'] },
}),
context
)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Request was rejected with message: 'Invalid value \\"4\\" supplied to \\"gte\\",Invalid value \\"3\\" supplied to \\"lte\\"'"`
);
});
test('validate gte/lte format', async () => {
const resp = await server.inject(
requestMock.create({
method: 'post',
path: `${BASE_RAC_ALERTS_API_PATH}/_alert_summary`,
body: {
gte: '2020-12-16T15:00:00.000Z',
lte: '2020-12-16',
featureIds: ['logs'],
},
}),
context
);
expect(resp.status).toEqual(400);
expect(resp.body).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"success": false,
},
"message": "gte and/or lte are not following the UTC format",
}
`);
});
test('validate fixed_interval ', async () => {
const resp = await server.inject(
requestMock.create({
method: 'post',
path: `${BASE_RAC_ALERTS_API_PATH}/_alert_summary`,
body: {
gte: '2020-12-16T15:00:00.000Z',
lte: '2020-12-16T16:00:00.000Z',
featureIds: ['logs'],
fixed_interval: 'xx',
},
}),
context
);
expect(resp.status).toEqual(400);
expect(resp.body).toMatchInlineSnapshot(`
Object {
"attributes": Object {
"success": false,
},
"message": "fixed_interval is not following the expected format 1m, 1h, 1d, 1w",
}
`);
});
test('rejects unknown query params', async () => {
await expect(
server.inject(
requestMock.create({
method: 'post',
path: `${BASE_RAC_ALERTS_API_PATH}/_alert_summary`,
body: {
gte: '2020-12-16T15:00:00.000Z',
lte: '2020-12-16T16:00:00.000Z',
featureIds: ['logs'],
boop: 'unknown',
},
}),
context
)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Request was rejected with message: 'invalid keys \\"boop\\"'"`
);
});
});
});

View file

@ -0,0 +1,97 @@
/*
* 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 Boom from '@hapi/boom';
import { IRouter } from '@kbn/core/server';
import * as t from 'io-ts';
import { transformError } from '@kbn/securitysolution-es-utils';
import moment from 'moment';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { RacRequestHandlerContext } from '../types';
import { BASE_RAC_ALERTS_API_PATH } from '../../common/constants';
import { buildRouteValidation } from './utils/route_validation';
export const getAlertSummaryRoute = (router: IRouter<RacRequestHandlerContext>) => {
router.post(
{
path: `${BASE_RAC_ALERTS_API_PATH}/_alert_summary`,
validate: {
body: buildRouteValidation(
t.intersection([
t.exact(
t.type({
gte: t.string,
lte: t.string,
featureIds: t.array(t.string),
})
),
t.exact(
t.partial({
fixed_interval: t.string,
filter: t.array(t.object),
})
),
])
),
},
options: {
tags: ['access:rac'],
},
},
async (context, request, response) => {
try {
const racContext = await context.rac;
const alertsClient = await racContext.getAlertsClient();
const { gte, lte, featureIds, filter, fixed_interval: fixedInterval } = request.body;
if (
!(
moment(gte, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true).isValid() &&
moment(lte, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true).isValid()
)
) {
throw Boom.badRequest('gte and/or lte are not following the UTC format');
}
if (fixedInterval && fixedInterval?.match(/^\d{1,2}['m','h','d','w']$/) == null) {
throw Boom.badRequest(
'fixed_interval is not following the expected format 1m, 1h, 1d, 1w'
);
}
const aggs = await alertsClient.getAlertSummary({
gte,
lte,
featureIds,
filter: filter as estypes.QueryDslQueryContainer[],
fixedInterval,
});
return response.ok({
body: aggs,
});
} catch (exc) {
const err = transformError(exc);
const contentType = {
'content-type': 'application/json',
};
const defaultedHeaders = {
...contentType,
};
return response.customError({
headers: defaultedHeaders,
statusCode: err.statusCode,
body: {
message: err.message,
attributes: {
success: false,
},
},
});
}
}
);
};

View file

@ -14,6 +14,7 @@ import { bulkUpdateAlertsRoute } from './bulk_update_alerts';
import { findAlertsByQueryRoute } from './find';
import { getFeatureIdsByRegistrationContexts } from './get_feature_ids_by_registration_contexts';
import { getBrowserFieldsByFeatureId } from './get_browser_fields_by_feature_id';
import { getAlertSummaryRoute } from './get_alert_summary';
export function defineRoutes(router: IRouter<RacRequestHandlerContext>) {
getAlertByIdRoute(router);
@ -23,4 +24,5 @@ export function defineRoutes(router: IRouter<RacRequestHandlerContext>) {
findAlertsByQueryRoute(router);
getFeatureIdsByRegistrationContexts(router);
getBrowserFieldsByFeatureId(router);
getAlertSummaryRoute(router);
}

View file

@ -130,6 +130,7 @@ describe(`feature_privilege_builder`, () => {
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAlertSummary",
]
`);
});
@ -179,6 +180,7 @@ describe(`feature_privilege_builder`, () => {
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAlertSummary",
]
`);
});
@ -276,6 +278,7 @@ describe(`feature_privilege_builder`, () => {
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAlertSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/update",
]
`);
@ -342,6 +345,7 @@ describe(`feature_privilege_builder`, () => {
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAlertSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/update",
]
`);
@ -446,10 +450,12 @@ describe(`feature_privilege_builder`, () => {
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/getAlertSummary",
"alerting:1.0.0-zeta1:alert-type/my-feature/alert/update",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/getAlertSummary",
]
`);
});
@ -521,10 +527,12 @@ describe(`feature_privilege_builder`, () => {
"alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/getAlertSummary",
"alerting:1.0.0-zeta1:another-alert-type/my-feature/alert/update",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/get",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/find",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/getAuthorizedAlertsIndices",
"alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/getAlertSummary",
]
`);
});

View file

@ -25,7 +25,7 @@ const readOperations: Record<AlertingEntity, string[]> = {
'find',
'getRuleExecutionKPI',
],
alert: ['get', 'find', 'getAuthorizedAlertsIndices'],
alert: ['get', 'find', 'getAuthorizedAlertsIndices', 'getAlertSummary'],
};
const writeOperations: Record<AlertingEntity, string[]> = {

View file

@ -10,6 +10,10 @@
"message": "hello world 1",
"kibana.alert.rule.consumer": "apm",
"kibana.alert.workflow_status": "open",
"kibana.alert.time_range": {
"gte": "2020-12-16T15:16:18.570Z"
},
"kibana.alert.status": "active",
"kibana.space_ids": ["space1", "space2"]
}
}
@ -26,7 +30,12 @@
"kibana.alert.rule.rule_type_id": "apm.error_rate",
"message": "hello world 1",
"kibana.alert.rule.consumer": "apm",
"kibana.alert.workflow_status": "open",
"kibana.alert.workflow_status": "recovered",
"kibana.alert.time_range": {
"gte": "2020-12-16T15:16:18.570Z",
"lte": "2020-12-16T15:16:19.570Z"
},
"kibana.alert.status": "recovered",
"kibana.space_ids": ["space1"]
}
}
@ -44,6 +53,7 @@
"message": "hello world 1",
"kibana.alert.rule.consumer": "apm",
"kibana.alert.workflow_status": "open",
"kibana.alert.status": "active",
"kibana.space_ids": ["space2"]
}
}
@ -114,3 +124,47 @@
}
}
}
{
"type": "doc",
"value": {
"index": ".alerts-observability.logs.alerts-default",
"id": "123456789XYZ",
"source": {
"event.kind": "signal",
"@timestamp": "2020-12-16T15:16:18.570Z",
"kibana.alert.rule.rule_type_id": "apm.error_rate",
"message": "hello world 1",
"kibana.alert.rule.consumer": "apm",
"kibana.alert.workflow_status": "open",
"kibana.alert.time_range": {
"gte": "2020-12-16T15:16:18.570Z"
},
"kibana.alert.status": "active",
"kibana.space_ids": ["space1", "space2"]
}
}
}
{
"type": "doc",
"value": {
"index": ".alerts-observability.logs.alerts-default",
"id": "space1alertLogs",
"source": {
"event.kind": "signal",
"@timestamp": "2020-12-16T15:16:18.570Z",
"kibana.alert.rule.rule_type_id": "apm.error_rate",
"message": "hello world 1",
"kibana.alert.rule.consumer": "apm",
"kibana.alert.workflow_status": "recovered",
"kibana.alert.time_range": {
"gte": "2020-12-16T15:16:18.570Z",
"lte": "2020-12-16T15:27:19.570Z"
},
"kibana.alert.end": "2020-12-16T15:27:19.570Z",
"kibana.alert.status": "recovered",
"kibana.space_ids": ["space1"]
}
}
}

View file

@ -16,6 +16,10 @@
"kibana.alert.rule.consumer": {
"type": "keyword",
"ignore_above": 256
},
"kibana.alert.time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
}
}
}
@ -45,3 +49,38 @@
}
}
}
{
"type": "index",
"value": {
"index": ".alerts-observability.logs.alerts-default",
"mappings": {
"properties": {
"message": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"kibana.alert.rule.consumer": {
"type": "keyword",
"ignore_above": 256
},
"kibana.alert.status": {
"type": "keyword",
"ignore_above": 256
},
"kibana.alert.time_range": {
"type": "date_range",
"format": "epoch_millis||strict_date_optional_time"
},
"kibana.alert.end": {
"type": "date"
}
}
}
}
}

View file

@ -109,6 +109,7 @@ export const observabilityOnlyAll: Role = {
{
feature: {
apm: ['all'],
logs: ['all'],
},
spaces: ['space1'],
},
@ -126,6 +127,7 @@ export const observabilityOnlyAllSpace2: Role = {
{
feature: {
apm: ['all'],
logs: ['all'],
},
spaces: ['space2'],
},
@ -143,6 +145,7 @@ export const observabilityOnlyRead: Role = {
{
feature: {
apm: ['read'],
logs: ['read'],
},
spaces: ['space1'],
},
@ -160,6 +163,7 @@ export const observabilityOnlyReadSpace2: Role = {
{
feature: {
apm: ['read'],
logs: ['read'],
},
spaces: ['space2'],
},
@ -214,6 +218,7 @@ export const observabilityOnlyAllSpacesAll: Role = {
{
feature: {
apm: ['all'],
logs: ['all'],
},
spaces: ['*'],
},
@ -260,6 +265,7 @@ export const observabilityOnlyAllSpacesAllWithReadESIndices: Role = {
{
feature: {
apm: ['all'],
logs: ['all'],
},
spaces: ['default', 'space1'],
},
@ -277,6 +283,7 @@ export const observabilityOnlyReadSpacesAll: Role = {
{
feature: {
apm: ['read'],
logs: ['read'],
},
spaces: ['*'],
},

View file

@ -0,0 +1,159 @@
/*
* 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 '@kbn/expect';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { getSpaceUrlPrefix } from '../../../common/lib/authentication/spaces';
import { superUser } from '../../../common/lib/authentication/users';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const supertestWithoutAuth = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
const TEST_URL = '/internal/rac/alerts';
const SPACE1 = 'space1';
const ALERT_SUMMARY_URL = `${TEST_URL}/_alert_summary`;
const LOGS_ALERT_ID = '123456789XYZ';
const LOGS_ALERT_ID2 = 'space1alertLogs';
describe('Alerts - GET - _alert_summary', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
});
it('Alert summary for all LOGS alerts with features', async () => {
const alertSummary = await supertestWithoutAuth
.post(`${getSpaceUrlPrefix(SPACE1)}${ALERT_SUMMARY_URL}`)
.auth(superUser.username, superUser.password)
.set('kbn-xsrf', 'true')
.send({
gte: '2020-12-16T15:00:00.000Z',
lte: '2020-12-16T16:00:00.000Z',
featureIds: ['logs'],
fixed_interval: '10m',
})
.expect(200);
expect(alertSummary.body).to.eql({
activeAlertCount: 1,
recoveredAlertCount: 1,
activeAlerts: [
{ key_as_string: '1608130800000', key: 1608130800000, doc_count: 0 },
{ key_as_string: '1608131400000', key: 1608131400000, doc_count: 2 },
{ key_as_string: '1608132000000', key: 1608132000000, doc_count: 2 },
{ key_as_string: '1608132600000', key: 1608132600000, doc_count: 1 },
{ key_as_string: '1608133200000', key: 1608133200000, doc_count: 1 },
{ key_as_string: '1608133800000', key: 1608133800000, doc_count: 1 },
{ key_as_string: '1608134400000', key: 1608134400000, doc_count: 1 },
],
recoveredAlerts: [
{ key_as_string: '2020-12-16T15:00:00.000Z', key: 1608130800000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:10:00.000Z', key: 1608131400000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:20:00.000Z', key: 1608132000000, doc_count: 1 },
{ key_as_string: '2020-12-16T15:30:00.000Z', key: 1608132600000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:40:00.000Z', key: 1608133200000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:50:00.000Z', key: 1608133800000, doc_count: 0 },
{ key_as_string: '2020-12-16T16:00:00.000Z', key: 1608134400000, doc_count: 0 },
],
});
});
it('Alert summary with a filter where the alert is recovered', async () => {
const alertSummary = await supertestWithoutAuth
.post(`${getSpaceUrlPrefix(SPACE1)}${ALERT_SUMMARY_URL}`)
.auth(superUser.username, superUser.password)
.set('kbn-xsrf', 'true')
.send({
gte: '2020-12-16T15:00:00.000Z',
lte: '2020-12-16T16:00:00.000Z',
filter: [
{
terms: {
_id: [LOGS_ALERT_ID2],
},
},
],
featureIds: ['logs'],
fixed_interval: '10m',
})
.expect(200);
expect(alertSummary.body).to.eql({
activeAlertCount: 0,
recoveredAlertCount: 1,
activeAlerts: [
{ key_as_string: '1608130800000', key: 1608130800000, doc_count: 0 },
{ key_as_string: '1608131400000', key: 1608131400000, doc_count: 1 },
{ key_as_string: '1608132000000', key: 1608132000000, doc_count: 1 },
{ key_as_string: '1608132600000', key: 1608132600000, doc_count: 0 },
{ key_as_string: '1608133200000', key: 1608133200000, doc_count: 0 },
{ key_as_string: '1608133800000', key: 1608133800000, doc_count: 0 },
{ key_as_string: '1608134400000', key: 1608134400000, doc_count: 0 },
],
recoveredAlerts: [
{ key_as_string: '2020-12-16T15:00:00.000Z', key: 1608130800000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:10:00.000Z', key: 1608131400000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:20:00.000Z', key: 1608132000000, doc_count: 1 },
{ key_as_string: '2020-12-16T15:30:00.000Z', key: 1608132600000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:40:00.000Z', key: 1608133200000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:50:00.000Z', key: 1608133800000, doc_count: 0 },
{ key_as_string: '2020-12-16T16:00:00.000Z', key: 1608134400000, doc_count: 0 },
],
});
});
it('Alert summary with a filter where the alert is active', async () => {
const alertSummary = await supertestWithoutAuth
.post(`${getSpaceUrlPrefix(SPACE1)}${ALERT_SUMMARY_URL}`)
.auth(superUser.username, superUser.password)
.set('kbn-xsrf', 'true')
.send({
gte: '2020-12-16T15:00:00.000Z',
lte: '2020-12-16T16:00:00.000Z',
filter: [
{
terms: {
_id: [LOGS_ALERT_ID],
},
},
],
featureIds: ['logs'],
fixed_interval: '10m',
})
.expect(200);
expect(alertSummary.body).to.eql({
activeAlertCount: 1,
recoveredAlertCount: 0,
activeAlerts: [
{ key_as_string: '1608130800000', key: 1608130800000, doc_count: 0 },
{ key_as_string: '1608131400000', key: 1608131400000, doc_count: 1 },
{ key_as_string: '1608132000000', key: 1608132000000, doc_count: 1 },
{ key_as_string: '1608132600000', key: 1608132600000, doc_count: 1 },
{ key_as_string: '1608133200000', key: 1608133200000, doc_count: 1 },
{ key_as_string: '1608133800000', key: 1608133800000, doc_count: 1 },
{ key_as_string: '1608134400000', key: 1608134400000, doc_count: 1 },
],
recoveredAlerts: [
{ key_as_string: '2020-12-16T15:00:00.000Z', key: 1608130800000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:10:00.000Z', key: 1608131400000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:20:00.000Z', key: 1608132000000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:30:00.000Z', key: 1608132600000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:40:00.000Z', key: 1608133200000, doc_count: 0 },
{ key_as_string: '2020-12-16T15:50:00.000Z', key: 1608133800000, doc_count: 0 },
{ key_as_string: '2020-12-16T16:00:00.000Z', key: 1608134400000, doc_count: 0 },
],
});
});
});
};

View file

@ -20,7 +20,6 @@ export default ({ getService }: FtrProviderContext) => {
const TEST_URL = '/internal/rac/alerts';
const ALERTS_INDEX_URL = `${TEST_URL}/index`;
const SPACE1 = 'space1';
const INDEX_ALIAS = '*';
const APM_ALERT_INDEX = '.alerts-observability.apm.alerts';
const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts';
@ -54,12 +53,12 @@ export default ({ getService }: FtrProviderContext) => {
describe('Users:', () => {
it(`${obsOnlySpacesAll.username} should be able to access the APM alert in ${SPACE1}`, async () => {
const indexNames = await getAPMIndexName(obsOnlySpacesAll, SPACE1);
expect(indexNames.includes(`${APM_ALERT_INDEX}-${INDEX_ALIAS}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below
expect(indexNames.includes(`${APM_ALERT_INDEX}-*`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below
});
it(`${superUser.username} should be able to access the APM alert in ${SPACE1}`, async () => {
const indexNames = await getAPMIndexName(superUser, SPACE1);
expect(indexNames.includes(`${APM_ALERT_INDEX}-${INDEX_ALIAS}`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below
expect(indexNames.includes(`${APM_ALERT_INDEX}-*`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below
});
it(`${secOnlyRead.username} should NOT be able to access the APM alert in ${SPACE1}`, async () => {

View file

@ -46,7 +46,7 @@ export default ({ getService }: FtrProviderContext) => {
'logs',
'uptime',
]);
expect(Object.keys(browserFields)).to.eql(['base']);
expect(Object.keys(browserFields)).to.eql(['base', 'event', 'kibana', 'message']);
});
it(`${superUser.username} should be able to get browser fields for o11y featureIds`, async () => {
@ -56,7 +56,7 @@ export default ({ getService }: FtrProviderContext) => {
'logs',
'uptime',
]);
expect(Object.keys(browserFields)).to.eql(['base']);
expect(Object.keys(browserFields)).to.eql(['base', 'event', 'kibana', 'message']);
});
it(`${superUser.username} should NOT be able to get browser fields for siem featureId`, async () => {

View file

@ -30,5 +30,6 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => {
loadTestFile(require.resolve('./find_alerts'));
loadTestFile(require.resolve('./search_strategy'));
loadTestFile(require.resolve('./get_browser_fields_by_feature_id'));
loadTestFile(require.resolve('./get_alert_summary'));
});
};