Skip to content

Commit

Permalink
[RAM] Add shareable rule status filter (#130705)
Browse files Browse the repository at this point in the history
* rule state filter

* turn off experiment

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Status filter API call

* Fix tests

* rename state to status, added tests

* Address comments and fix tests

* Revert experiment flag

* Remove unused translations

* Addressed comments

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
JiaweiWu and kibanamachine authored May 3, 2022
1 parent 28092ef commit 07985db
Show file tree
Hide file tree
Showing 31 changed files with 785 additions and 94 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function useFetchRules({
page,
searchText,
typesFilter: typesFilter.length > 0 ? typesFilter : OBSERVABILITY_RULE_TYPES,
ruleStatusesFilter: ruleLastResponseFilter,
ruleExecutionStatusesFilter: ruleLastResponseFilter,
sort,
});
setRulesState((oldState) => ({
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -29364,7 +29364,6 @@
"xpack.triggersActionsUI.sections.rulesList.ruleStatusActive": "Actif",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusDropdownMenuLabel": "Modifier le statut de la règle ou répéter",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusError": "Erreur",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusFilterLabel": "Dernière réponse",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusLicenseError": "Erreur de licence",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusOk": "Ok",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusPending": "En attente",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -29433,7 +29433,6 @@
"xpack.triggersActionsUI.sections.rulesList.ruleStatusActive": "アクティブ",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusDropdownMenuLabel": "ルールステータスまたはスヌーズを変更",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusError": "エラー",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusFilterLabel": "前回の応答",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusLicenseError": "ライセンスエラー",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusOk": "OK",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusPending": "保留中",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -29468,7 +29468,6 @@
"xpack.triggersActionsUI.sections.rulesList.ruleStatusActive": "活动",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusDropdownMenuLabel": "更改规则状态或暂停",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusError": "错误",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusFilterLabel": "上次响应",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusLicenseError": "许可证错误",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusOk": "确定",
"xpack.triggersActionsUI.sections.rulesList.ruleStatusPending": "待处理",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const allowedExperimentalValues = Object.freeze({
rulesListDatagrid: true,
internalAlertsTable: false,
internalShareableComponentsSandbox: false,
ruleStatusFilter: false,
rulesDetailLogs: true,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 React, { useState } from 'react';
import { RuleStatusFilterProps } from '../../../types';
import { getRuleStatusFilterLazy } from '../../../common/get_rule_status_filter';

export const RuleStatusFilterSandbox = () => {
const [selectedStatuses, setSelectedStatuses] = useState<
RuleStatusFilterProps['selectedStatuses']
>([]);

return (
<div style={{ flex: 1 }}>
{getRuleStatusFilterLazy({
selectedStatuses,
onChange: setSelectedStatuses,
})}
<div>Selected states: {JSON.stringify(selectedStatuses)}</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@

import React from 'react';
import { RuleStatusDropdownSandbox } from './rule_status_dropdown_sandbox';
import { RuleStatusFilterSandbox } from './rule_status_filter_sandbox';
import { RuleTagBadgeSandbox } from './rule_tag_badge_sandbox';

export const InternalShareableComponentsSandbox: React.FC<{}> = () => {
return (
<>
<RuleStatusDropdownSandbox />
<RuleStatusFilterSandbox />
<RuleTagBadgeSandbox />
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,84 @@ describe('loadRuleAggregations', () => {
]
`);
});

test('should call aggregate API with ruleStatusesFilter', async () => {
const resolvedValue = {
rule_execution_status: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
};
http.get.mockResolvedValue(resolvedValue);

let result = await loadRuleAggregations({
http,
ruleStatusesFilter: ['enabled'],
});

expect(result).toEqual({
ruleExecutionStatus: {
ok: 4,
active: 2,
error: 1,
pending: 1,
unknown: 0,
},
});

expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.enabled:(true) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)",
"search": undefined,
"search_fields": undefined,
},
},
]
`);

result = await loadRuleAggregations({
http,
ruleStatusesFilter: ['enabled', 'snoozed'],
});

expect(http.get.mock.calls[1]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)",
"search": undefined,
"search_fields": undefined,
},
},
]
`);

result = await loadRuleAggregations({
http,
ruleStatusesFilter: ['enabled', 'disabled', 'snoozed'],
});

expect(http.get.mock.calls[1]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)",
"search": undefined,
"search_fields": undefined,
},
},
]
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import { HttpSetup } from '@kbn/core/public';
import { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common';
import { RuleAggregations } from '../../../types';
import { RuleAggregations, RuleStatus } from '../../../types';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { mapFiltersToKql } from './map_filters_to_kql';

Expand All @@ -29,15 +29,22 @@ export async function loadRuleAggregations({
searchText,
typesFilter,
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleStatusesFilter,
}: {
http: HttpSetup;
searchText?: string;
typesFilter?: string[];
actionTypesFilter?: string[];
ruleStatusesFilter?: string[];
ruleExecutionStatusesFilter?: string[];
ruleStatusesFilter?: RuleStatus[];
}): Promise<RuleAggregations> {
const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, ruleStatusesFilter });
const filters = mapFiltersToKql({
typesFilter,
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleStatusesFilter,
});
const res = await http.get<AsApiContract<RuleAggregations>>(
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,62 @@ describe('mapFiltersToKql', () => {
]);
});

test('should handle ruleStatusesFilter', () => {
test('should handle ruleExecutionStatusesFilter', () => {
expect(
mapFiltersToKql({
ruleStatusesFilter: ['alert', 'statuses', 'filter'],
ruleExecutionStatusesFilter: ['alert', 'statuses', 'filter'],
})
).toEqual(['alert.attributes.executionStatus.status:(alert or statuses or filter)']);
});

test('should handle ruleStatusesFilter', () => {
expect(
mapFiltersToKql({
ruleStatusesFilter: ['enabled'],
})
).toEqual([
'alert.attributes.enabled:(true) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)',
]);

expect(
mapFiltersToKql({
ruleStatusesFilter: ['disabled'],
})
).toEqual([
'alert.attributes.enabled:(false) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)',
]);

expect(
mapFiltersToKql({
ruleStatusesFilter: ['snoozed'],
})
).toEqual(['(alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)']);

expect(
mapFiltersToKql({
ruleStatusesFilter: ['enabled', 'snoozed'],
})
).toEqual([
'alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)',
]);

expect(
mapFiltersToKql({
ruleStatusesFilter: ['disabled', 'snoozed'],
})
).toEqual([
'alert.attributes.enabled:(false) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)',
]);

expect(
mapFiltersToKql({
ruleStatusesFilter: ['enabled', 'disabled', 'snoozed'],
})
).toEqual([
'alert.attributes.enabled:(true or false) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)',
]);
});

test('should handle typesFilter and actionTypesFilter', () => {
expect(
mapFiltersToKql({
Expand All @@ -52,12 +100,12 @@ describe('mapFiltersToKql', () => {
]);
});

test('should handle typesFilter, actionTypesFilter and ruleStatusesFilter', () => {
test('should handle typesFilter, actionTypesFilter and ruleExecutionStatusesFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
actionTypesFilter: ['action', 'types', 'filter'],
ruleStatusesFilter: ['alert', 'statuses', 'filter'],
ruleExecutionStatusesFilter: ['alert', 'statuses', 'filter'],
})
).toEqual([
'alert.attributes.alertTypeId:(type or filter)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,34 @@
* 2.0.
*/

import { RuleStatus } from '../../../types';

const getEnablementFilter = (ruleStatusFilter: RuleStatus[] = []) => {
const enablementFilters = ruleStatusFilter.reduce<string[]>((result, filter) => {
if (filter === 'enabled') {
return [...result, 'true'];
}
if (filter === 'disabled') {
return [...result, 'false'];
}
return result;
}, []);
return `alert.attributes.enabled:(${enablementFilters.join(' or ')})`;
};

export const mapFiltersToKql = ({
typesFilter,
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleStatusesFilter,
}: {
typesFilter?: string[];
actionTypesFilter?: string[];
ruleStatusesFilter?: string[];
ruleExecutionStatusesFilter?: string[];
ruleStatusesFilter?: RuleStatus[];
}): string[] => {
const filters = [];

if (typesFilter && typesFilter.length) {
filters.push(`alert.attributes.alertTypeId:(${typesFilter.join(' or ')})`);
}
Expand All @@ -29,8 +47,27 @@ export const mapFiltersToKql = ({
].join('')
);
}
if (ruleExecutionStatusesFilter && ruleExecutionStatusesFilter.length) {
filters.push(
`alert.attributes.executionStatus.status:(${ruleExecutionStatusesFilter.join(' or ')})`
);
}

if (ruleStatusesFilter && ruleStatusesFilter.length) {
filters.push(`alert.attributes.executionStatus.status:(${ruleStatusesFilter.join(' or ')})`);
const enablementFilter = getEnablementFilter(ruleStatusesFilter);
const snoozedFilter = `(alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)`;
const hasEnablement =
ruleStatusesFilter.includes('enabled') || ruleStatusesFilter.includes('disabled');
const hasSnoozed = ruleStatusesFilter.includes('snoozed');

if (hasEnablement && !hasSnoozed) {
filters.push(`${enablementFilter} and not ${snoozedFilter}`);
} else if (!hasEnablement && hasSnoozed) {
filters.push(snoozedFilter);
} else {
filters.push(`${enablementFilter} or ${snoozedFilter}`);
}
}

return filters;
};
Loading

0 comments on commit 07985db

Please sign in to comment.