-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Alerting] Preconfigured alert history index connector #94909
Changes from 47 commits
f055c2a
3ac2315
59146f6
97d9815
3ecfa2a
1b028a7
f2b34d6
b03e052
61948fd
3fa6846
b9ad6c3
4b1b787
957c333
4167d49
9b4eda6
6c30518
3a25517
24f2f2d
1ce3a36
22d5a59
2554887
2af2999
ac4248d
92531a7
fd94545
c8b44ee
5a88ab5
f75a958
6d769e8
7363eb3
c5060ee
0d4acb0
56848f9
bbe5fd6
85ccb96
7444b08
776e8df
af35a12
6f420b1
8bc04c3
07eb28d
79b282c
f3fea18
19f3673
dcbf451
f689618
75281b0
030bf55
5aedddb
75dcb88
095b495
094aa60
da82858
9e42c60
3f6c078
55e75a5
38e5519
1a17d94
8923efa
45278e2
fa502cc
b23fca9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,8 +53,11 @@ You can configure the following settings in the `kibana.yml` file. | |
+ | ||
Disabled action types will not appear as an option when creating new connectors, but existing connectors and actions of that type will remain in {kib} and will not function. | ||
|
||
| `xpack.actions.preconfiguredAlertHistoryEsIndex` | ||
| Enables a preconfigured alert history {es} <<index-action-type, Index>> connector. Defaults to `false`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we plan to allow-list this for Cloud? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and docker :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wondering if we would want to consider this to be "experimental" - with alerts-as-data coming, it seems likely this action might not be needed in the future? And that we might not want to continue to support it? Not sure, seems like there may also be indices that don't deal with alerts-as-data via the framework (however that might be implemented), and so this would be a "cheap" version of that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docker config update is part of this PR. I plan to open a followup PR for cloud when this PR is resolved. |
||
|
||
| `xpack.actions.preconfigured` | ||
| Specifies preconfigured action IDs and configs. Defaults to {}. | ||
| Specifies preconfigured connector IDs and configs. Defaults to {}. | ||
|
||
| `xpack.actions.proxyUrl` {ess-icon} | ||
| Specifies the proxy URL to use, if using a proxy for actions. By default, no proxy is used. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,3 +64,27 @@ Clicking a preconfigured connector shows the description, but not the configurat | |
|
||
[role="screenshot"] | ||
image::images/pre-configured-connectors-view-screen.png[Pre-configured connector view details] | ||
|
||
[float] | ||
[[preconfigured-connector-alert-history]] | ||
==== Alert history preconfigured connector | ||
|
||
{kib} offers a preconfigured <<index-action-type, Index>> connector to facilitate indexing active alert data into {es}. | ||
|
||
To use this connector, set the <<action-settings, `xpack.actions.preconfiguredAlertHistoryEsIndex`>> configuration to `true`. | ||
|
||
```js | ||
xpack.actions.preconfiguredAlertHistoryEsIndex: true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little worried about hard-coding the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated prefix to |
||
``` | ||
|
||
When creating a new rule, add an <<index-action-type, Index action>> and select the `Alert History ES Index (preconfigured)` connector. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This link confuses me. It takes me to the Index connector and action page. I'm still not sure what the actions are and how it relates to the screenshot. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a type of index connector, which is why I added the link to the index connector type. Normally with an index connector, the user who configures the connector decides what the index name is and the user who adds the connector to a rule decides what the data schema is. For this preconfigured alert history connector, the index name and data schema is pre-determined. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sames feels as @gchaps - it's a bit confusing. On all the connector pages, we have a *Preconfigured connector type section, and so I first thought - oh, this is the new preconfigured alert history thing - but it isn't. It just shows how to build an "ordinary" preconfigured index connector. Thinking maybe we should move the doc for this bit into the index connector doc, vs the preconfigured connectors page -though we could certainly reference it from there. |
||
|
||
[role="screenshot"] | ||
image::images/pre-configured-alert-history-connector.png[Select pre-configured alert history connectors] | ||
|
||
Documents are indexed using a preconfigured schema that captures the <<defining-alerts-actions-variables, action variables>> available for the rule. By default, these documents are indexed into the `alert-history-default` index, but you can specify a different index. Index names must start with `alert-history-` to take advantage of the preconfigured Alert History index template. | ||
|
||
[IMPORTANT] | ||
============================================== | ||
To write documents to the preconfigured index, you must have `all` or `write` privileges to the `alert-history-*` indices. Refer to <<xpack-kibana-role-management>> for more information. | ||
============================================== |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
/* | ||
* 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 { buildAlertHistoryDocument } from './alert_history_schema'; | ||
|
||
function getVariables(overrides = {}) { | ||
return { | ||
date: '2021-01-01T00:00:00.000Z', | ||
rule: { | ||
id: 'rule-id', | ||
name: 'rule-name', | ||
type: 'rule-type', | ||
spaceId: 'space-id', | ||
}, | ||
context: { | ||
contextVar1: 'contextValue1', | ||
contextVar2: 'contextValue2', | ||
}, | ||
params: { | ||
ruleParam: 1, | ||
ruleParamString: 'another param', | ||
}, | ||
tags: ['abc', 'def'], | ||
alert: { | ||
id: 'alert-id', | ||
actionGroup: 'action-group-id', | ||
actionGroupName: 'Action Group', | ||
}, | ||
...overrides, | ||
}; | ||
} | ||
|
||
describe('buildAlertHistoryDocument', () => { | ||
it('handles empty variables', () => { | ||
expect(buildAlertHistoryDocument({})).toBeNull(); | ||
}); | ||
|
||
it('returns null if rule type is not defined', () => { | ||
expect(buildAlertHistoryDocument(getVariables({ rule: { type: undefined } }))).toBeNull(); | ||
}); | ||
|
||
it('returns null if alert variables are not defined', () => { | ||
expect(buildAlertHistoryDocument(getVariables({ alert: undefined }))).toBeNull(); | ||
}); | ||
|
||
it('returns null if rule variables are not defined', () => { | ||
expect(buildAlertHistoryDocument(getVariables({ rule: undefined }))).toBeNull(); | ||
}); | ||
|
||
it('includes @timestamp field if date is null', () => { | ||
const alertHistoryDoc = buildAlertHistoryDocument(getVariables({ date: undefined })); | ||
expect(alertHistoryDoc).not.toBeNull(); | ||
expect(alertHistoryDoc!['@timestamp']).toBeTruthy(); | ||
}); | ||
|
||
it(`doesn't include context if context is empty`, () => { | ||
const alertHistoryDoc = buildAlertHistoryDocument(getVariables({ context: {} })); | ||
expect(alertHistoryDoc).not.toBeNull(); | ||
expect(alertHistoryDoc!.alert?.context).toBeFalsy(); | ||
}); | ||
|
||
it(`doesn't include params if params is empty`, () => { | ||
const alertHistoryDoc = buildAlertHistoryDocument(getVariables({ params: {} })); | ||
expect(alertHistoryDoc).not.toBeNull(); | ||
expect(alertHistoryDoc!.rule?.params).toBeFalsy(); | ||
}); | ||
|
||
it(`doesn't include tags if tags is empty array`, () => { | ||
const alertHistoryDoc = buildAlertHistoryDocument(getVariables({ tags: [] })); | ||
expect(alertHistoryDoc).not.toBeNull(); | ||
expect(alertHistoryDoc!.tags).toBeFalsy(); | ||
}); | ||
|
||
it(`included message if context contains message`, () => { | ||
const alertHistoryDoc = buildAlertHistoryDocument( | ||
getVariables({ | ||
context: { contextVar1: 'contextValue1', contextVar2: 'contextValue2', message: 'hello!' }, | ||
}) | ||
); | ||
expect(alertHistoryDoc).not.toBeNull(); | ||
expect(alertHistoryDoc!.message).toEqual('hello!'); | ||
}); | ||
|
||
it('builds alert history document from variables', () => { | ||
expect(buildAlertHistoryDocument(getVariables())).toEqual({ | ||
'@timestamp': '2021-01-01T00:00:00.000Z', | ||
alert: { | ||
ymao1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
actionGroup: 'action-group-id', | ||
actionGroupName: 'Action Group', | ||
context: { | ||
'rule-type': { | ||
contextVar1: 'contextValue1', | ||
contextVar2: 'contextValue2', | ||
}, | ||
}, | ||
id: 'alert-id', | ||
}, | ||
event: { | ||
kind: 'alert', | ||
}, | ||
rule: { | ||
id: 'rule-id', | ||
name: 'rule-name', | ||
params: { | ||
'rule-type': { | ||
ruleParam: 1, | ||
ruleParamString: 'another param', | ||
}, | ||
}, | ||
space: 'space-id', | ||
type: 'rule-type', | ||
}, | ||
tags: ['abc', 'def'], | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* 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 { isEmpty } from 'lodash'; | ||
|
||
export const ALERT_HISTORY_PREFIX = 'alert-history-'; | ||
export const AlertHistoryDefaultIndexName = `${ALERT_HISTORY_PREFIX}default`; | ||
export const AlertHistoryEsIndexConnectorId = 'preconfigured-alert-history-es-index'; | ||
|
||
export const buildAlertHistoryDocument = (variables: Record<string, unknown>) => { | ||
const { date, alert: alertVariables, context, params, tags, rule: ruleVariables } = variables as { | ||
date: string; | ||
alert: Record<string, unknown>; | ||
context: Record<string, unknown>; | ||
params: Record<string, unknown>; | ||
rule: Record<string, unknown>; | ||
tags: string[]; | ||
}; | ||
|
||
if (!alertVariables || !ruleVariables) { | ||
return null; | ||
} | ||
|
||
const { actionGroup, actionGroupName, id: alertId } = alertVariables as { | ||
actionGroup: string; | ||
actionGroupName: string; | ||
id: string; | ||
}; | ||
|
||
const { id: ruleId, name, spaceId, type } = ruleVariables as { | ||
id: string; | ||
name: string; | ||
spaceId: string; | ||
type: string; | ||
}; | ||
|
||
if (!type) { | ||
// can't build the document without a type | ||
return null; | ||
} | ||
|
||
const ruleType = type.replace(/\./g, '__'); | ||
|
||
const rule = { | ||
...(ruleId ? { id: ruleId } : {}), | ||
...(name ? { name } : {}), | ||
...(!isEmpty(params) ? { params: { [ruleType]: params } } : {}), | ||
...(spaceId ? { space: spaceId } : {}), | ||
...(type ? { type } : {}), | ||
}; | ||
const alert = { | ||
...(alertId ? { id: alertId } : {}), | ||
...(!isEmpty(context) ? { context: { [ruleType]: context } } : {}), | ||
...(actionGroup ? { actionGroup } : {}), | ||
...(actionGroupName ? { actionGroupName } : {}), | ||
}; | ||
|
||
const alertHistoryDoc = { | ||
'@timestamp': date ? date : new Date().toISOString(), | ||
...(tags && tags.length > 0 ? { tags } : {}), | ||
...(context?.message ? { message: context.message } : {}), | ||
...(!isEmpty(rule) ? { rule } : {}), | ||
...(!isEmpty(alert) ? { alert } : {}), | ||
}; | ||
|
||
return !isEmpty(alertHistoryDoc) ? { ...alertHistoryDoc, event: { kind: 'alert' } } : null; | ||
}; | ||
|
||
export const AlertHistoryDocumentTemplate = Object.freeze( | ||
buildAlertHistoryDocument({ | ||
rule: { | ||
id: '{{rule.id}}', | ||
name: '{{rule.name}}', | ||
type: '{{rule.type}}', | ||
spaceId: '{{rule.spaceId}}', | ||
}, | ||
context: '{{context}}', | ||
params: '{{params}}', | ||
tags: '{{rule.tags}}', | ||
alert: { | ||
id: '{{alert.id}}', | ||
actionGroup: '{{alert.actionGroup}}', | ||
actionGroupName: '{{alert.actionGroupName}}', | ||
}, | ||
}) | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
/* | ||
ymao1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* 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 { i18n } from '@kbn/i18n'; | ||
import { PreConfiguredAction } from '../../types'; | ||
import { ActionTypeId as EsIndexActionTypeId } from '../es_index'; | ||
import { AlertHistoryEsIndexConnectorId, AlertHistoryDefaultIndexName } from '../../../common'; | ||
|
||
export function getAlertHistoryEsIndex(): Readonly<PreConfiguredAction> { | ||
return Object.freeze({ | ||
name: i18n.translate('xpack.actions.alertHistoryEsIndexConnector.name', { | ||
defaultMessage: 'Alert history ES index', | ||
ymao1 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}), | ||
actionTypeId: EsIndexActionTypeId, | ||
id: AlertHistoryEsIndexConnectorId, | ||
isPreconfigured: true, | ||
config: { | ||
index: AlertHistoryDefaultIndexName, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm interested to see (haven't gotten there yet) how we convert this index name in the config into a parameter in the preconfigured connector. Seems like we'd have to have some special logic in the index connector executor to deal with this. Do we have a new "index name suffix" param in the index connector that the normal index connector doesn't use? Seems alright as I'm thinking about it, but kinda weird. But makes me wonder if this shouldn't be a separate action type, which presumably you'd only be able to create as a preconfigured action - which itself is also weird. |
||
}, | ||
secrets: {}, | ||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* 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 { ElasticsearchClient } from 'src/core/server'; | ||
import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; | ||
import { DeeplyMockedKeys } from '@kbn/utility-types/jest'; | ||
import { | ||
createAlertHistoryIndexTemplate, | ||
getAlertHistoryIndexTemplate, | ||
} from './create_alert_history_index_template'; | ||
|
||
type MockedLogger = ReturnType<typeof loggingSystemMock['createLogger']>; | ||
|
||
describe('createAlertHistoryIndexTemplate', () => { | ||
let logger: MockedLogger; | ||
let clusterClient: DeeplyMockedKeys<ElasticsearchClient>; | ||
|
||
beforeEach(() => { | ||
logger = loggingSystemMock.createLogger(); | ||
clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser; | ||
}); | ||
|
||
test(`should create index template if it doesn't exist`, async () => { | ||
// Response type for existsIndexTemplate is still TODO | ||
clusterClient.indices.existsIndexTemplate.mockResolvedValue({ | ||
body: false, | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
} as any); | ||
|
||
await createAlertHistoryIndexTemplate({ client: clusterClient, logger }); | ||
expect(clusterClient.indices.putIndexTemplate).toHaveBeenCalledWith({ | ||
name: `alert-history-template`, | ||
body: getAlertHistoryIndexTemplate(), | ||
create: true, | ||
}); | ||
}); | ||
|
||
test(`shouldn't create index template if it already exists`, async () => { | ||
// Response type for existsIndexTemplate is still TODO | ||
clusterClient.indices.existsIndexTemplate.mockResolvedValue({ | ||
body: true, | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
} as any); | ||
|
||
await createAlertHistoryIndexTemplate({ client: clusterClient, logger }); | ||
expect(clusterClient.indices.putIndexTemplate).not.toHaveBeenCalled(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we also have a mention within
docs/user/alerting/action-types/index.asciidoc
?