Skip to content
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

[Response Ops] Adds recovery context for ES query rule type #132839

Merged
merged 15 commits into from Jun 4, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -5,13 +5,13 @@
* 2.0.
*/

import { EsQueryAlertActionContext, addMessages } from './action_context';
import { EsQueryAlertParamsSchema } from './alert_type_params';
import { OnlyEsQueryAlertParams } from './types';
import { EsQueryRuleActionContext, addMessages } from './action_context';
import { EsQueryRuleParamsSchema } from './rule_type_params';
import { OnlyEsQueryRuleParams } from './types';

describe('ActionContext', () => {
it('generates expected properties', async () => {
const params = EsQueryAlertParamsSchema.validate({
const params = EsQueryRuleParamsSchema.validate({
index: ['[index]'],
timeField: '[timeField]',
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
Expand All @@ -21,18 +21,18 @@ describe('ActionContext', () => {
thresholdComparator: '>',
threshold: [4],
searchType: 'esQuery',
}) as OnlyEsQueryAlertParams;
const base: EsQueryAlertActionContext = {
}) as OnlyEsQueryRuleParams;
const base: EsQueryRuleActionContext = {
date: '2020-01-01T00:00:00.000Z',
value: 42,
conditions: 'count greater than 4',
hits: [],
link: 'link-mock',
};
const context = addMessages({ name: '[alert-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(`"alert '[alert-name]' matched query"`);
const context = addMessages({ name: '[rule-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(`"rule '[rule-name]' matched query"`);
expect(context.message).toEqual(
`alert '[alert-name]' is active:
`rule '[rule-name]' is active:

- Value: 42
- Conditions Met: count greater than 4 over 5m
Expand All @@ -41,8 +41,39 @@ describe('ActionContext', () => {
);
});

it('generates expected properties when isRecovered is true', async () => {
const params = EsQueryRuleParamsSchema.validate({
index: ['[index]'],
timeField: '[timeField]',
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
size: 100,
timeWindowSize: 5,
timeWindowUnit: 'm',
thresholdComparator: '>',
threshold: [4],
searchType: 'esQuery',
}) as OnlyEsQueryRuleParams;
const base: EsQueryRuleActionContext = {
date: '2020-01-01T00:00:00.000Z',
value: 42,
conditions: 'count not greater than 4',
hits: [],
link: 'link-mock',
};
const context = addMessages({ name: '[rule-name]' }, base, params, true);
expect(context.title).toMatchInlineSnapshot(`"rule '[rule-name]' recovered"`);
expect(context.message).toEqual(
`rule '[rule-name]' is recovered:

- Value: 42
- Conditions Met: count not greater than 4 over 5m
- Timestamp: 2020-01-01T00:00:00.000Z
- Link: link-mock`
);
});

it('generates expected properties if comparator is between', async () => {
const params = EsQueryAlertParamsSchema.validate({
const params = EsQueryRuleParamsSchema.validate({
index: ['[index]'],
timeField: '[timeField]',
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
Expand All @@ -52,18 +83,18 @@ describe('ActionContext', () => {
thresholdComparator: 'between',
threshold: [4, 5],
searchType: 'esQuery',
}) as OnlyEsQueryAlertParams;
const base: EsQueryAlertActionContext = {
}) as OnlyEsQueryRuleParams;
const base: EsQueryRuleActionContext = {
date: '2020-01-01T00:00:00.000Z',
value: 4,
conditions: 'count between 4 and 5',
hits: [],
link: 'link-mock',
};
const context = addMessages({ name: '[alert-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(`"alert '[alert-name]' matched query"`);
const context = addMessages({ name: '[rule-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(`"rule '[rule-name]' matched query"`);
expect(context.message).toEqual(
`alert '[alert-name]' is active:
`rule '[rule-name]' is active:

- Value: 4
- Conditions Met: count between 4 and 5 over 5m
Expand Down
Expand Up @@ -8,60 +8,63 @@
import { i18n } from '@kbn/i18n';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { RuleExecutorOptions, AlertInstanceContext } from '@kbn/alerting-plugin/server';
import { OnlyEsQueryAlertParams, OnlySearchSourceAlertParams } from './types';
import { OnlyEsQueryRuleParams, OnlySearchSourceRuleParams } from './types';

// alert type context provided to actions
// rule type context provided to actions

type AlertInfo = Pick<RuleExecutorOptions, 'name'>;
type RuleInfo = Pick<RuleExecutorOptions, 'name'>;

export interface ActionContext extends EsQueryAlertActionContext {
export interface ActionContext extends EsQueryRuleActionContext {
// a short pre-constructed message which may be used in an action field
title: string;
// a longer pre-constructed message which may be used in an action field
message: string;
}

export interface EsQueryAlertActionContext extends AlertInstanceContext {
// the date the alert was run as an ISO date
export interface EsQueryRuleActionContext extends AlertInstanceContext {
// the date the rule was run as an ISO date
date: string;
// the value that met the threshold
value: number;
// threshold conditions
conditions: string;
// query matches
hits: estypes.SearchHit[];
// a link to see records that triggered the alert for Discover alert
// a link which navigates to stack management in case of Elastic query alert
// a link to see records that triggered the rule for Discover rule
// a link which navigates to stack management in case of Elastic query rule
link: string;
}

export function addMessages(
alertInfo: AlertInfo,
baseContext: EsQueryAlertActionContext,
params: OnlyEsQueryAlertParams | OnlySearchSourceAlertParams
ruleInfo: RuleInfo,
baseContext: EsQueryRuleActionContext,
params: OnlyEsQueryRuleParams | OnlySearchSourceRuleParams,
isRecovered: boolean = false
): ActionContext {
const title = i18n.translate('xpack.stackAlerts.esQuery.alertTypeContextSubjectTitle', {
defaultMessage: `alert '{name}' matched query`,
defaultMessage: `rule '{name}' {verb}`,
values: {
name: alertInfo.name,
name: ruleInfo.name,
verb: isRecovered ? 'recovered' : 'matched query',
},
});

const window = `${params.timeWindowSize}${params.timeWindowUnit}`;
const message = i18n.translate('xpack.stackAlerts.esQuery.alertTypeContextMessageDescription', {
defaultMessage: `alert '{name}' is active:
defaultMessage: `rule '{name}' is {verb}:

- Value: {value}
- Conditions Met: {conditions} over {window}
- Timestamp: {date}
- Link: {link}`,
values: {
name: alertInfo.name,
name: ruleInfo.name,
value: baseContext.value,
conditions: baseContext.conditions,
window,
date: baseContext.date,
link: baseContext.link,
verb: isRecovered ? 'recovered' : 'active',
},
});

Expand Down
Expand Up @@ -5,8 +5,14 @@
* 2.0.
*/

import { getSearchParams, getValidTimefieldSort, tryToParseAsDate } from './executor';
import { OnlyEsQueryAlertParams } from './types';
import {
getSearchParams,
getValidTimefieldSort,
tryToParseAsDate,
getContextConditionsDescription,
} from './executor';
import { OnlyEsQueryRuleParams } from './types';
import { Comparator } from '../../../common/comparator_types';

describe('es_query executor', () => {
const defaultProps = {
Expand Down Expand Up @@ -49,13 +55,13 @@ describe('es_query executor', () => {

describe('getSearchParams', () => {
it('should return search params correctly', () => {
const result = getSearchParams(defaultProps as OnlyEsQueryAlertParams);
const result = getSearchParams(defaultProps as OnlyEsQueryRuleParams);
expect(result.parsedQuery.query).toBe('test-query');
});

it('should throw invalid query error', () => {
expect(() =>
getSearchParams({ ...defaultProps, esQuery: '' } as OnlyEsQueryAlertParams)
getSearchParams({ ...defaultProps, esQuery: '' } as OnlyEsQueryRuleParams)
).toThrow('invalid query specified: "" - query must be JSON');
});

Expand All @@ -64,7 +70,7 @@ describe('es_query executor', () => {
getSearchParams({
...defaultProps,
esQuery: '{ "someProperty": "test-query" }',
} as OnlyEsQueryAlertParams)
} as OnlyEsQueryRuleParams)
).toThrow('invalid query specified: "{ "someProperty": "test-query" }" - query must be JSON');
});

Expand All @@ -74,8 +80,25 @@ describe('es_query executor', () => {
...defaultProps,
timeWindowSize: 5,
timeWindowUnit: 'r',
} as OnlyEsQueryAlertParams)
} as OnlyEsQueryRuleParams)
).toThrow('invalid format for windowSize: "5r"');
});
});

describe('getContextConditionsDescription', () => {
it('should return conditions correctly', () => {
const result = getContextConditionsDescription(Comparator.GT, [10]);
expect(result).toBe(`Number of matching documents is greater than 10`);
});

it('should return conditions correctly when isRecovered is true', () => {
const result = getContextConditionsDescription(Comparator.GT, [10], true);
expect(result).toBe(`Number of matching documents is NOT greater than 10`);
});

it('should return conditions correctly when multiple thresholds provided', () => {
const result = getContextConditionsDescription(Comparator.BETWEEN, [10, 20], true);
expect(result).toBe(`Number of matching documents is NOT between 10 and 20`);
});
});
});