Skip to content

Commit

Permalink
[Alerting] formalize alert status and add status fields to alert save…
Browse files Browse the repository at this point in the history
…d object

resolves #51099

This formalizes the concept of "alert status", in terms of it's execution, with
some new fields in the alert saved object and types used with the alert client
and http APIs.

These fields are read-only from the client point-of-view; they are provided in
the alert structures, but are only updated by the alerting framework itself.
The values will be updated after each run of the alert type executor.

interim commits:

calculate the execution status, some refactoring
  • Loading branch information
pmuellr committed Aug 27, 2020
1 parent 9511285 commit ce43e9a
Show file tree
Hide file tree
Showing 33 changed files with 349 additions and 9 deletions.
13 changes: 13 additions & 0 deletions x-pack/plugins/alerts/common/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ export interface IntervalSchedule extends SavedObjectAttributes {
interval: string;
}

export type AlertExecutionStatuses = 'ok' | 'active' | 'error' | 'noData' | 'unknown';
export type AlertExecutionStatusErrorReasons = 'read' | 'decrypt' | 'execute' | 'unknown';

export interface AlertExecutionStatus {
status: AlertExecutionStatuses;
date: Date;
error?: {
reason: AlertExecutionStatusErrorReasons;
message: string;
};
}

export type AlertActionParams = SavedObjectAttributes;

export interface AlertAction {
Expand Down Expand Up @@ -44,6 +56,7 @@ export interface Alert {
throttle: string | null;
muteAll: boolean;
mutedInstanceIds: string[];
executionStatus: AlertExecutionStatus;
}

export type SanitizedAlert = Omit<Alert, 'apiKey'>;
48 changes: 48 additions & 0 deletions x-pack/plugins/alerts/server/alerts_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,10 @@ describe('create()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -372,6 +376,10 @@ describe('create()', () => {
"createdAt": "2019-02-12T21:01:22.479Z",
"createdBy": "elastic",
"enabled": true,
"executionStatus": Object {
"date": "2020-08-20T19:23:38Z",
"status": "unknown",
},
"muteAll": false,
"mutedInstanceIds": Array [],
"name": "abc",
Expand Down Expand Up @@ -564,6 +572,10 @@ describe('create()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -638,6 +650,10 @@ describe('create()', () => {
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"enabled": false,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -938,6 +954,10 @@ describe('create()', () => {
muteAll: false,
mutedInstanceIds: [],
tags: ['foo'],
executionStatus: {
date: '2020-08-20T19:23:38Z',
status: 'unknown',
},
},
{
references: [
Expand Down Expand Up @@ -1051,6 +1071,10 @@ describe('create()', () => {
muteAll: false,
mutedInstanceIds: [],
tags: ['foo'],
executionStatus: {
date: '2020-08-20T19:23:38Z',
status: 'unknown',
},
},
{
references: [
Expand Down Expand Up @@ -2025,6 +2049,10 @@ describe('get()', () => {
],
"alertTypeId": "123",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -2347,6 +2375,10 @@ const BaseAlertStatusSavedObject: SavedObject<RawAlert> = {
throttle: null,
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
date: '2020-08-20T19:23:38Z',
},
},
references: [],
};
Expand Down Expand Up @@ -2618,6 +2650,10 @@ describe('find()', () => {
],
"alertTypeId": "myType",
"createdAt": 2019-02-12T21:01:22.479Z,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -3059,6 +3095,10 @@ describe('update()', () => {
],
"createdAt": 2019-02-12T21:01:22.479Z,
"enabled": true,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -3236,6 +3276,10 @@ describe('update()', () => {
"apiKey": "MTIzOmFiYw==",
"createdAt": 2019-02-12T21:01:22.479Z,
"enabled": true,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down Expand Up @@ -3387,6 +3431,10 @@ describe('update()', () => {
"apiKey": null,
"createdAt": 2019-02-12T21:01:22.479Z,
"enabled": false,
"executionStatus": Object {
"date": 2019-02-12T21:01:22.479Z,
"status": "unknown",
},
"id": "1",
"params": Object {
"bar": true,
Expand Down
33 changes: 32 additions & 1 deletion x-pack/plugins/alerts/server/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {
SanitizedAlert,
AlertTaskState,
AlertStatus,
AlertExecutionStatus,
RawAlertExecutionStatus,
} from './types';
import { validateAlertTypeParams } from './lib';
import {
Expand Down Expand Up @@ -121,6 +123,7 @@ export interface CreateOptions {
| 'muteAll'
| 'mutedInstanceIds'
| 'actions'
| 'executionStatus'
> & { actions: NormalizedAlertAction[] };
options?: {
migrationVersion?: Record<string, string>;
Expand Down Expand Up @@ -224,6 +227,10 @@ export class AlertsClient {
params: validatedAlertTypeParams as RawAlert['params'],
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
date: '2020-08-20T19:23:38Z',
},
};
const createdAlert = await this.unsecuredSavedObjectsClient.create('alert', rawAlert, {
...options,
Expand Down Expand Up @@ -355,12 +362,18 @@ export class AlertsClient {
// eslint-disable-next-line @typescript-eslint/naming-convention
const authorizedData = data.map(({ id, attributes, updated_at, references }) => {
ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer);
return this.getAlertFromRaw(
const alert = this.getAlertFromRaw(
id,
fields ? (pick(attributes, fields) as RawAlert) : attributes,
updated_at,
references
);
// executionStatus is a synthetic field added by getAlertFromRaw(), so we
// need to remove it if it wasn't listed in the fields
if (fields && !fields.includes('executionStatus')) {
delete alert.executionStatus;
}
return alert;
});

logSuccessfulAuthorization();
Expand Down Expand Up @@ -864,11 +877,13 @@ export class AlertsClient {
updatedAt: SavedObject['updated_at'] = createdAt,
references: SavedObjectReference[] | undefined
): PartialAlert {
const executionStatus = this.rawAlertToExecutionStatus(rawAlert);
return {
id,
...rawAlert,
// we currently only support the Interval Schedule type
// Once we support additional types, this type signature will likely change
executionStatus,
schedule: rawAlert.schedule as IntervalSchedule,
actions: rawAlert.actions
? this.injectReferencesIntoActions(id, rawAlert.actions, references || [])
Expand All @@ -878,6 +893,22 @@ export class AlertsClient {
};
}

private rawAlertToExecutionStatus(rawAlert: Partial<RawAlert> = {}): AlertExecutionStatus {
const rawExecutionStatus: Partial<RawAlertExecutionStatus> = rawAlert.executionStatus || {};
const { status: rawStatus, date: rawDate, error: rawError } = rawExecutionStatus;

const result: AlertExecutionStatus = {
status: rawStatus || 'unknown',
date: rawDate ? new Date(rawDate) : new Date(),
};

if (rawError) {
result.error = rawError;
}

return result;
}

private validateActions(alertType: AlertType, actions: NormalizedAlertAction[]): void {
const { actionGroups: alertTypeActionGroups } = alertType;
const usedAlertActionGroups = actions.map((action) => action.group);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,4 +461,8 @@ const BaseAlert: SanitizedAlert = {
createdAt: new Date(),
updatedAt: new Date(),
apiKeyOwner: null,
executionStatus: {
status: 'unknown',
date: new Date('2020-08-20T19:23:38Z'),
},
};
23 changes: 23 additions & 0 deletions x-pack/plugins/alerts/server/lib/error_with_reason.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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 { AlertExecutionStatusErrorReasons } from '../types';

export class ErrorWithReason extends Error {
public readonly reason: AlertExecutionStatusErrorReasons;

constructor(reason: AlertExecutionStatusErrorReasons, message: string) {
super(message);
this.reason = reason;
}
}

export function getReasonFromError(error: Error): AlertExecutionStatusErrorReasons {
if (error instanceof ErrorWithReason) {
return error.reason;
}
return 'unknown';
}
1 change: 1 addition & 0 deletions x-pack/plugins/alerts/server/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
export { parseDuration, validateDurationSchema } from '../../common/parse_duration';
export { LicenseState } from './license_state';
export { validateAlertTypeParams } from './validate_alert_type_params';
export { ErrorWithReason, getReasonFromError } from './error_with_reason';
7 changes: 6 additions & 1 deletion x-pack/plugins/alerts/server/routes/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { mockLicenseState } from '../lib/license_state.mock';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { alertsClientMock } from '../alerts_client.mock';
import { Alert } from '../../common/alert';

const alertsClient = alertsClientMock.create();

Expand Down Expand Up @@ -46,7 +47,7 @@ describe('createAlertRoute', () => {
],
};

const createResult = {
const createResult: Alert = {
...mockedAlert,
enabled: true,
muteAll: false,
Expand All @@ -64,6 +65,10 @@ describe('createAlertRoute', () => {
actionTypeId: 'test',
},
],
executionStatus: {
status: 'unknown',
date: new Date('2020-08-20T19:23:38Z'),
},
};

it('creates an alert with proper parameters', async () => {
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/alerts/server/routes/get.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { mockLicenseState } from '../lib/license_state.mock';
import { verifyApiAccess } from '../lib/license_api_access';
import { mockHandlerArguments } from './_mock_handler_arguments';
import { alertsClientMock } from '../alerts_client.mock';
import { Alert } from '../../common';

const alertsClient = alertsClientMock.create();
jest.mock('../lib/license_api_access.ts', () => ({
Expand All @@ -21,7 +22,7 @@ beforeEach(() => {
});

describe('getAlertRoute', () => {
const mockedAlert = {
const mockedAlert: Alert = {
id: '1',
alertTypeId: '1',
schedule: { interval: '10s' },
Expand Down Expand Up @@ -51,6 +52,10 @@ describe('getAlertRoute', () => {
apiKeyOwner: '',
throttle: '30s',
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
date: new Date('2020-08-20T19:23:38Z'),
},
};

it('gets an alert with proper parameters', async () => {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerts/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function setupSavedObjects(
'muteAll',
'mutedInstanceIds',
'updatedBy',
'executionStatus',
]),
});
}
20 changes: 20 additions & 0 deletions x-pack/plugins/alerts/server/saved_objects/mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@
},
"mutedInstanceIds": {
"type": "keyword"
},
"executionStatus": {
"properties": {
"status": {
"type": "keyword"
},
"date": {
"type": "date"
},
"error": {
"properties": {
"reason": {
"type": "keyword"
},
"message": {
"type": "keyword"
}
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const alert: SanitizedAlert = {
throttle: null,
muteAll: false,
mutedInstanceIds: [],
executionStatus: {
status: 'unknown',
date: new Date('2020-08-20T19:23:38Z'),
},
};

describe('Alert Task Instance', () => {
Expand Down
Loading

0 comments on commit ce43e9a

Please sign in to comment.