Skip to content

Commit 5eb056a

Browse files
committed
Add groupBy alerts
1 parent d64d0df commit 5eb056a

7 files changed

Lines changed: 121 additions & 21 deletions

File tree

x-pack/legacy/plugins/infra/server/lib/alerting/metric_threshold/create_alert.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,27 @@
55
*/
66

77
import { i18n } from '@kbn/i18n';
8+
import { omit } from 'lodash';
9+
import uuid from 'uuid';
810
import { SavedObjectsClientContract } from 'src/core/server';
911
import {
1012
MetricThresholdAlertTypeParams,
1113
Comparator,
1214
METRIC_THRESHOLD_ALERT_TYPE_ID,
1315
} from './types';
1416
import { infraMetricAlertSavedObjectType } from '../saved_object_mappings';
17+
import { getGroupings } from '../../metrics/get_groupings';
1518
import { ActionsClient } from '../../../../../actions';
1619
import { AlertsClient } from '../../../../../alerting';
20+
import { InfraDatabaseSearchResponse } from '../../../lib/adapters/framework';
1721

1822
interface Properties extends MetricThresholdAlertTypeParams {
1923
actions: {
2024
email: string;
2125
slack: string;
2226
log: any;
2327
};
28+
childOf?: string;
2429
}
2530

2631
interface Clients {
@@ -72,7 +77,7 @@ const aggregationNames = {
7277
}),
7378
};
7479

75-
const createAlert = async (
80+
export const createAlert = async (
7681
{ alertsClient, actionsClient, savedObjectsClient }: Clients,
7782
{
7883
actions: { slack, email, log },
@@ -83,6 +88,7 @@ const createAlert = async (
8388
threshold,
8489
interval,
8590
indexPattern,
91+
childOf,
8692
}: Properties
8793
) => {
8894
const actions: Array<{ params: Record<string, any>; action: Action; group: string }> = [];
@@ -213,15 +219,15 @@ const createAlert = async (
213219
'xpack.infra.alerting.metricThreshold.slackTemplateAlertText',
214220
{
215221
defaultMessage:
216-
'{searchFieldName} *{searchFieldValue}* has reported {aggregation} `{metric}` value {comparator} {threshold} within {interval}.\n\nThe current value is *{value}*',
222+
'{searchFieldName} *{searchFieldValue}* has reported {aggregation} `{metric}` value {comparator} `{threshold}` within {interval}.\n\nThe current value is *{value}*',
217223
values,
218224
}
219225
);
220226
const recoveredMessage = i18n.translate(
221227
'xpack.infra.alerting.metricThreshold.slackTemplateRecoveryText',
222228
{
223229
defaultMessage:
224-
'{searchFieldName} *{searchFieldValue}* has recovered from an alert state in which {aggregation} `{metric}` value was {comparator} {threshold} within {interval}\n\nThe current value is *{value}*',
230+
'{searchFieldName} *{searchFieldValue}* has recovered from an alert state in which {aggregation} `{metric}` value was {comparator} `{threshold}` within {interval}\n\nThe current value is *{value}*',
225231
values,
226232
}
227233
);
@@ -305,11 +311,68 @@ const createAlert = async (
305311

306312
await savedObjectsClient.create(
307313
infraMetricAlertSavedObjectType,
308-
{ searchField, threshold, interval, comparator, aggregation, metric, indexPattern },
314+
{
315+
searchField,
316+
threshold,
317+
interval,
318+
comparator,
319+
aggregation,
320+
metric,
321+
indexPattern,
322+
...(childOf ? { childOf } : {}),
323+
},
309324
{ id: createdAlert.id }
310325
);
311326

312327
return createdAlert.id;
313328
};
314329

315-
export { createAlert };
330+
export const createMultiAlert = async (
331+
search: <Aggregation>(options: object) => Promise<InfraDatabaseSearchResponse<{}, Aggregation>>,
332+
clients: Clients,
333+
props: Properties
334+
) => {
335+
const { savedObjectsClient } = clients;
336+
const { metric, aggregation, searchField, interval, indexPattern } = props;
337+
const options = {
338+
metrics: [
339+
{
340+
aggregation,
341+
field: metric,
342+
rate: false,
343+
},
344+
],
345+
groupBy: searchField.name,
346+
indexPattern,
347+
timerange: {
348+
from: `now-${interval}`,
349+
to: 'now',
350+
field: '@timestamp',
351+
interval,
352+
},
353+
};
354+
355+
const multiAlertId = uuid.v4();
356+
357+
const groupings = await getGroupings(search, options);
358+
if (!groupings.series) throw new Error('Unable to get groupings');
359+
const results = await Promise.all(
360+
groupings.series.map(({ id }: { id: string }) =>
361+
createAlert(clients, {
362+
...props,
363+
searchField: {
364+
...searchField,
365+
value: id,
366+
},
367+
childOf: multiAlertId,
368+
})
369+
)
370+
);
371+
372+
await savedObjectsClient.create(
373+
infraMetricAlertSavedObjectType,
374+
{ ...omit(props, 'actions'), childAlerts: results },
375+
{ id: multiAlertId }
376+
);
377+
return { id: multiAlertId, children: results };
378+
};

x-pack/legacy/plugins/infra/server/lib/alerting/saved_object_mappings.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,13 @@ import { MetricThresholdAlertTypeParams } from './metric_threshold/types';
1010
export const infraMetricAlertSavedObjectType = 'infrastructure-metric-alert';
1111

1212
export const infraAlertSavedObjectMappings: {
13-
[infraMetricAlertSavedObjectType]: ElasticsearchMappingOf<MetricThresholdAlertTypeParams>;
13+
[infraMetricAlertSavedObjectType]: ElasticsearchMappingOf<
14+
MetricThresholdAlertTypeParams & {
15+
currentAlertState: string;
16+
childAlerts: string;
17+
childOf: string;
18+
}
19+
>;
1420
} = {
1521
[infraMetricAlertSavedObjectType]: {
1622
properties: {
@@ -45,6 +51,12 @@ export const infraAlertSavedObjectMappings: {
4551
currentAlertState: {
4652
type: 'keyword',
4753
},
54+
childAlerts: {
55+
type: 'keyword',
56+
},
57+
childOf: {
58+
type: 'keyword',
59+
},
4860
},
4961
},
5062
};

x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_groupings.ts renamed to x-pack/legacy/plugins/infra/server/lib/metrics/get_groupings.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
*/
66

77
import { isObject, set } from 'lodash';
8-
import { InfraDatabaseSearchResponse } from '../../../lib/adapters/framework';
9-
import { MetricsExplorerRequest, MetricsExplorerResponse } from '../types';
8+
import { InfraDatabaseSearchResponse } from '../../lib/adapters/framework';
9+
import {
10+
MetricsExplorerRequest,
11+
MetricsExplorerResponse,
12+
} from '../../routes/metrics_explorer/types';
1013

1114
interface GroupingAggregation {
1215
groupingsCount: {

x-pack/legacy/plugins/infra/server/routes/alerts/create_metric_threshold_alert.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { InfraBackendLibs } from '../../lib/infra_types';
8-
import { createAlert } from '../../lib/alerting/metric_threshold/create_alert';
8+
import { createAlert, createMultiAlert } from '../../lib/alerting/metric_threshold/create_alert';
99
import { internalInfraFrameworkRequest } from '../../lib/adapters/framework/adapter_types';
1010

1111
export const initCreateMetricThresholdAlert = ({ framework }: InfraBackendLibs) =>
@@ -29,13 +29,22 @@ export const initCreateMetricThresholdAlert = ({ framework }: InfraBackendLibs)
2929
.getScopedSavedObjectsClient(internalReq);
3030

3131
try {
32-
const result = await createAlert(
33-
{ alertsClient, actionsClient, savedObjectsClient },
34-
req.payload
35-
);
36-
const alertId = result;
37-
38-
return res.response(alertId);
32+
if (req.payload.searchField.value === '*') {
33+
const search = <Aggregation>(searchOptions: object) =>
34+
framework.callWithRequest<{}, Aggregation>(req, 'search', searchOptions);
35+
const alertIds = await createMultiAlert(
36+
search,
37+
{ alertsClient, actionsClient, savedObjectsClient },
38+
req.payload
39+
);
40+
return res.response(alertIds);
41+
} else {
42+
const alertId = await createAlert(
43+
{ alertsClient, actionsClient, savedObjectsClient },
44+
req.payload
45+
);
46+
return res.response(alertId);
47+
}
3948
} catch (e) {
4049
if (e && e.output) {
4150
return res.response(e.output.payload.message).code(e.output.statusCode);

x-pack/legacy/plugins/infra/server/routes/alerts/delete_metric_alert.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import Boom from 'boom';
78
import { InfraBackendLibs } from '../../lib/infra_types';
89
import { internalInfraFrameworkRequest } from '../../lib/adapters/framework/adapter_types';
910
import { infraMetricAlertSavedObjectType } from '../../lib/alerting/saved_object_mappings';
@@ -28,7 +29,20 @@ export const initDeleteMetricAlert = ({ framework }: InfraBackendLibs) =>
2829
const { id } = req.query;
2930

3031
try {
31-
const result = await alertsClient.delete({ id });
32+
const savedAlert = await savedObjectsClient.get(infraMetricAlertSavedObjectType, id);
33+
if (savedAlert.attributes.childAlerts) {
34+
for (const alertID of savedAlert.attributes.childAlerts) {
35+
await alertsClient.delete({ id: alertID });
36+
await savedObjectsClient.delete(infraMetricAlertSavedObjectType, alertID);
37+
}
38+
} else {
39+
if (savedAlert.attributes.childOf) {
40+
throw new Boom(
41+
`Cannot delete the child of a groupBy alert. You must delete the parent alert, which has the ID of ${savedAlert.attributes.childOf})`
42+
);
43+
}
44+
await alertsClient.delete({ id });
45+
}
3246
await savedObjectsClient.delete(infraMetricAlertSavedObjectType, id);
3347

3448
return res.response().code(204);

x-pack/legacy/plugins/infra/server/routes/metrics_explorer/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { boomify } from 'boom';
88
import { InfraBackendLibs } from '../../lib/infra_types';
9-
import { getGroupings } from './lib/get_groupings';
9+
import { getGroupings } from '../../../server/lib/metrics/get_groupings';
1010
import { populateSeriesWithTSVBData } from './lib/populate_series_with_tsvb_data';
1111
import { metricsExplorerSchema } from './schema';
1212
import { MetricsExplorerResponse, MetricsExplorerWrappedRequest } from './types';
@@ -30,7 +30,6 @@ export const initMetricExplorerRoute = (libs: InfraBackendLibs) => {
3030
const options = req.payload;
3131
// First we get the groupings from a composite aggregation
3232
const response = await getGroupings(search, options);
33-
3433
// Then we take the results and fill in the data from TSVB with the
3534
// user's custom metrics
3635
const seriesWithMetrics = await Promise.all(

x-pack/legacy/plugins/infra/server/routes/metrics_explorer/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { InfraWrappableRequest } from '../../lib/adapters/framework';
88

99
export interface InfraTimerange {
1010
field: string;
11-
from: number;
12-
to: number;
11+
from: number | string;
12+
to: number | string;
1313
interval: string;
1414
}
1515

0 commit comments

Comments
 (0)