forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Alerting] formalize alert status and add status fields to alert save…
…d object resolves elastic#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. The data is added to the alert as the `executionStatus` field, with the following shape: ```ts interface AlertExecutionStatus { status: 'ok' | 'active' | 'error' | 'unknown'; date: Date; error?: { reason: 'read' | 'decrypt' | 'execute' | 'unknown'; message: string; }; } ``` interim commits: calculate the execution status, some refactoring write the execution status to the alert after execution use real date in execution status on create add an await to an async fn comment out status update to see if SIEM FT succeeds fix SIEM FT alert deletion issue use partial updates and retries in alerts clients to avoid conflicts fix jest tests clean up conflict-fixin code moar conflict-prevention fixing fix type error with find result add reasons to alert execution errors add some jest tests add some function tests fix status update to use alert namespace fix function test finish function tests more fixes after rebase fix type checks and jest tests after rebase add migration and find functional tests fix relative import
- Loading branch information
Showing
51 changed files
with
1,142 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
x-pack/plugins/alerts/server/lib/alert_execution_status.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* | ||
* 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'; | ||
import { | ||
executionStatusFromState, | ||
executionStatusFromError, | ||
alertExecutionStatusToRaw, | ||
alertExecutionStatusFromRaw, | ||
} from './alert_execution_status'; | ||
import { ErrorWithReason } from './error_with_reason'; | ||
|
||
describe('AlertExecutionStatus', () => { | ||
describe('executionStatusFromState()', () => { | ||
test('empty task state', () => { | ||
const status = executionStatusFromState({}); | ||
checkDateIsNearNow(status.date); | ||
expect(status.status).toBe('ok'); | ||
expect(status.error).toBe(undefined); | ||
}); | ||
|
||
test('task state with no instances', () => { | ||
const status = executionStatusFromState({ alertInstances: {} }); | ||
checkDateIsNearNow(status.date); | ||
expect(status.status).toBe('ok'); | ||
expect(status.error).toBe(undefined); | ||
}); | ||
|
||
test('task state with one instance', () => { | ||
const status = executionStatusFromState({ alertInstances: { a: {} } }); | ||
checkDateIsNearNow(status.date); | ||
expect(status.status).toBe('active'); | ||
expect(status.error).toBe(undefined); | ||
}); | ||
}); | ||
|
||
describe('executionStatusFromError()', () => { | ||
test('error with no reason', () => { | ||
const status = executionStatusFromError(new Error('boo!')); | ||
expect(status.status).toBe('error'); | ||
expect(status.error).toMatchInlineSnapshot(` | ||
Object { | ||
"message": "boo!", | ||
"reason": "unknown", | ||
} | ||
`); | ||
}); | ||
|
||
test('error with a reason', () => { | ||
const status = executionStatusFromError(new ErrorWithReason('execute', new Error('hoo!'))); | ||
expect(status.status).toBe('error'); | ||
expect(status.error).toMatchInlineSnapshot(` | ||
Object { | ||
"message": "hoo!", | ||
"reason": "execute", | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe('alertExecutionStatusToRaw()', () => { | ||
const date = new Date('2020-09-03T16:26:58Z'); | ||
const status = 'ok'; | ||
const reason: AlertExecutionStatusErrorReasons = 'decrypt'; | ||
const error = { reason, message: 'wops' }; | ||
|
||
test('status without an error', () => { | ||
expect(alertExecutionStatusToRaw({ date, status })).toMatchInlineSnapshot(` | ||
Object { | ||
"date": "2020-09-03T16:26:58.000Z", | ||
"error": null, | ||
"status": "ok", | ||
} | ||
`); | ||
}); | ||
|
||
test('status with an error', () => { | ||
expect(alertExecutionStatusToRaw({ date, status, error })).toMatchInlineSnapshot(` | ||
Object { | ||
"date": "2020-09-03T16:26:58.000Z", | ||
"error": Object { | ||
"message": "wops", | ||
"reason": "decrypt", | ||
}, | ||
"status": "ok", | ||
} | ||
`); | ||
}); | ||
}); | ||
|
||
describe('alertExecutionStatusFromRaw()', () => { | ||
const date = new Date('2020-09-03T16:26:58Z').toISOString(); | ||
const status = 'active'; | ||
const reason: AlertExecutionStatusErrorReasons = 'execute'; | ||
const error = { reason, message: 'wops' }; | ||
|
||
test('no input', () => { | ||
const result = alertExecutionStatusFromRaw(); | ||
expect(result).toBe(undefined); | ||
}); | ||
|
||
test('undefined input', () => { | ||
const result = alertExecutionStatusFromRaw(undefined); | ||
expect(result).toBe(undefined); | ||
}); | ||
|
||
test('null input', () => { | ||
const result = alertExecutionStatusFromRaw(null); | ||
expect(result).toBe(undefined); | ||
}); | ||
|
||
test('invalid date', () => { | ||
const result = alertExecutionStatusFromRaw({ date: 'an invalid date' })!; | ||
checkDateIsNearNow(result.date); | ||
expect(result.status).toBe('unknown'); | ||
expect(result.error).toBe(undefined); | ||
}); | ||
|
||
test('valid date', () => { | ||
const result = alertExecutionStatusFromRaw({ date }); | ||
expect(result).toMatchInlineSnapshot(` | ||
Object { | ||
"date": 2020-09-03T16:26:58.000Z, | ||
"status": "unknown", | ||
} | ||
`); | ||
}); | ||
|
||
test('valid status and date', () => { | ||
const result = alertExecutionStatusFromRaw({ status, date }); | ||
expect(result).toMatchInlineSnapshot(` | ||
Object { | ||
"date": 2020-09-03T16:26:58.000Z, | ||
"status": "active", | ||
} | ||
`); | ||
}); | ||
|
||
test('valid status, date and error', () => { | ||
const result = alertExecutionStatusFromRaw({ status, date, error }); | ||
expect(result).toMatchInlineSnapshot(` | ||
Object { | ||
"date": 2020-09-03T16:26:58.000Z, | ||
"error": Object { | ||
"message": "wops", | ||
"reason": "execute", | ||
}, | ||
"status": "active", | ||
} | ||
`); | ||
}); | ||
}); | ||
}); | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
function checkDateIsNearNow(date: any) { | ||
expect(date instanceof Date).toBe(true); | ||
// allow for lots of slop in the time difference | ||
expect(Date.now() - date.valueOf()).toBeLessThanOrEqual(10000); | ||
} |
60 changes: 60 additions & 0 deletions
60
x-pack/plugins/alerts/server/lib/alert_execution_status.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
/* | ||
* 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 { AlertTaskState, AlertExecutionStatus, RawAlertExecutionStatus } from '../types'; | ||
import { getReasonFromError } from './error_with_reason'; | ||
|
||
export function executionStatusFromState(state: AlertTaskState): AlertExecutionStatus { | ||
const instanceIds = Object.keys(state.alertInstances ?? {}); | ||
return { | ||
date: new Date(), | ||
status: instanceIds.length === 0 ? 'ok' : 'active', | ||
}; | ||
} | ||
|
||
export function executionStatusFromError(error: Error): AlertExecutionStatus { | ||
return { | ||
date: new Date(), | ||
status: 'error', | ||
error: { | ||
reason: getReasonFromError(error), | ||
message: error.message, | ||
}, | ||
}; | ||
} | ||
|
||
export function alertExecutionStatusToRaw({ | ||
date, | ||
status, | ||
error, | ||
}: AlertExecutionStatus): RawAlertExecutionStatus { | ||
return { | ||
date: date.toISOString(), | ||
status, | ||
error: error ?? null, | ||
}; | ||
} | ||
|
||
export function alertExecutionStatusFromRaw( | ||
rawAlertExecutionStatus?: Partial<RawAlertExecutionStatus> | null | undefined | ||
): AlertExecutionStatus | undefined { | ||
if (!rawAlertExecutionStatus) return undefined; | ||
|
||
const { date, status = 'unknown', error } = rawAlertExecutionStatus; | ||
|
||
let parsedDateMillis = date ? Date.parse(date) : Date.now(); | ||
if (isNaN(parsedDateMillis)) { | ||
// TODO: log a message? | ||
parsedDateMillis = Date.now(); | ||
} | ||
|
||
const parsedDate = new Date(parsedDateMillis); | ||
if (error) { | ||
return { date: parsedDate, status, error }; | ||
} else { | ||
return { date: parsedDate, status }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
x-pack/plugins/alerts/server/lib/error_with_reason.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/* | ||
* 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 { ErrorWithReason, getReasonFromError, isErrorWithReason } from './error_with_reason'; | ||
|
||
describe('ErrorWithReason', () => { | ||
const plainError = new Error('well, actually'); | ||
const errorWithReason = new ErrorWithReason('decrypt', plainError); | ||
|
||
test('ErrorWithReason class', () => { | ||
expect(errorWithReason.message).toBe(plainError.message); | ||
expect(errorWithReason.error).toBe(plainError); | ||
expect(errorWithReason.reason).toBe('decrypt'); | ||
}); | ||
|
||
test('getReasonFromError()', () => { | ||
expect(getReasonFromError(plainError)).toBe('unknown'); | ||
expect(getReasonFromError(errorWithReason)).toBe('decrypt'); | ||
}); | ||
|
||
test('isErrorWithReason()', () => { | ||
expect(isErrorWithReason(plainError)).toBe(false); | ||
expect(isErrorWithReason(errorWithReason)).toBe(true); | ||
}); | ||
}); |
Oops, something went wrong.