Skip to content

Commit

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

* lower case test subj

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

* Tag filter and aggregation APIs

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

* Fix and add more tests

* Fix test

* Fix test and add new function test

* Addressed comments

* Lint

* Create new load tags function

* bump bundle size

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
JiaweiWu and kibanamachine authored May 6, 2022
1 parent 8aa9241 commit 19298ee
Show file tree
Hide file tree
Showing 31 changed files with 685 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pageLoadAssetSize:
telemetry: 51957
telemetryManagementSection: 38586
transform: 41007
triggersActionsUi: 103400
triggersActionsUi: 104400
upgradeAssistant: 81241
urlForwarding: 32579
usageCollection: 39762
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/alerting/common/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export interface RuleAggregations {
ruleEnabledStatus: { enabled: number; disabled: number };
ruleMutedStatus: { muted: number; unmuted: number };
ruleSnoozedStatus: { snoozed: number };
ruleTags: string[];
}

export interface MappedParamsProperties {
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/alerting/server/routes/aggregate_rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('aggregateRulesRoute', () => {
ruleSnoozedStatus: {
snoozed: 4,
},
ruleTags: ['a', 'b', 'c'],
};
rulesClient.aggregate.mockResolvedValueOnce(aggregateResult);

Expand Down Expand Up @@ -94,6 +95,11 @@ describe('aggregateRulesRoute', () => {
"rule_snoozed_status": Object {
"snoozed": 4,
},
"rule_tags": Array [
"a",
"b",
"c",
],
},
}
`);
Expand Down Expand Up @@ -129,6 +135,7 @@ describe('aggregateRulesRoute', () => {
rule_snoozed_status: {
snoozed: 4,
},
rule_tags: ['a', 'b', 'c'],
},
});
});
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/alerting/server/routes/aggregate_rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ const rewriteBodyRes: RewriteResponseCase<AggregateResult> = ({
ruleEnabledStatus,
ruleMutedStatus,
ruleSnoozedStatus,
ruleTags,
...rest
}) => ({
...rest,
rule_execution_status: alertExecutionStatus,
rule_enabled_status: ruleEnabledStatus,
rule_muted_status: ruleMutedStatus,
rule_snoozed_status: ruleSnoozedStatus,
rule_tags: ruleTags,
});

export const aggregateRulesRoute = (
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/alerting/server/rules_client/rules_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ export interface RuleAggregation {
doc_count: number;
}>;
};
tags: {
buckets: Array<{
key: string;
doc_count: number;
}>;
};
}

export interface ConstructorOptions {
Expand Down Expand Up @@ -200,6 +206,7 @@ export interface AggregateResult {
ruleEnabledStatus?: { enabled: number; disabled: number };
ruleMutedStatus?: { muted: number; unmuted: number };
ruleSnoozedStatus?: { snoozed: number };
ruleTags?: string[];
}

export interface FindResult<Params extends RuleTypeParams> {
Expand Down Expand Up @@ -921,6 +928,9 @@ export class RulesClient {
muted: {
terms: { field: 'alert.attributes.muteAll' },
},
tags: {
terms: { field: 'alert.attributes.tags', order: { _key: 'asc' } },
},
snoozed: {
date_range: {
field: 'alert.attributes.snoozeEndTime',
Expand Down Expand Up @@ -990,6 +1000,9 @@ export class RulesClient {
snoozed: snoozedBuckets.reduce((acc, bucket) => acc + bucket.doc_count, 0),
};

const tagsBuckets = resp.aggregations.tags?.buckets || [];
ret.ruleTags = tagsBuckets.map((bucket) => bucket.key);

return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,22 @@ describe('aggregate()', () => {
},
],
},
tags: {
buckets: [
{
key: 'a',
doc_count: 10,
},
{
key: 'b',
doc_count: 20,
},
{
key: 'c',
doc_count: 30,
},
],
},
},
});

Expand Down Expand Up @@ -160,6 +176,11 @@ describe('aggregate()', () => {
"ruleSnoozedStatus": Object {
"snoozed": 2,
},
"ruleTags": Array [
"a",
"b",
"c",
],
}
`);
expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -187,6 +208,9 @@ describe('aggregate()', () => {
ranges: [{ from: 'now' }],
},
},
tags: {
terms: { field: 'alert.attributes.tags', order: { _key: 'asc' } },
},
},
},
]);
Expand Down Expand Up @@ -221,6 +245,9 @@ describe('aggregate()', () => {
ranges: [{ from: 'now' }],
},
},
tags: {
terms: { field: 'alert.attributes.tags', order: { _key: 'asc' } },
},
},
},
]);
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,
ruleTagFilter: false,
ruleStatusFilter: false,
rulesDetailLogs: true,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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 { EuiSpacer } from '@elastic/eui';
import { getRuleTagFilterLazy } from '../../../common/get_rule_tag_filter';

export const RuleTagFilterSandbox = () => {
const [selectedTags, setSelectedTags] = useState<string[]>([]);

return (
<div style={{ flex: 1 }}>
{getRuleTagFilterLazy({
tags: ['tag1', 'tag2', 'tag3', 'tag4'],
selectedTags,
onChange: setSelectedTags,
})}
<EuiSpacer />
<div>selected tags: {JSON.stringify(selectedTags)}</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

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

export const InternalShareableComponentsSandbox: React.FC<{}> = () => {
return (
<>
<RuleStatusDropdownSandbox />
<RuleTagFilterSandbox />
<RuleStatusFilterSandbox />
<RuleTagBadgeSandbox />
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { httpServiceMock } from '@kbn/core/public/mocks';
import { loadRuleAggregations } from './aggregate';
import { loadRuleAggregations, loadRuleTags } from './aggregate';

const http = httpServiceMock.createStartContract();

Expand Down Expand Up @@ -289,4 +289,68 @@ describe('loadRuleAggregations', () => {
]
`);
});

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

const result = await loadRuleAggregations({
http,
searchText: 'baz',
tagsFilter: ['a', 'b', 'c'],
});

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.tags:(a or b or c)",
"search": "baz",
"search_fields": "[\\"name\\",\\"tags\\"]",
},
},
]
`);
});

test('loadRuleTags should call the aggregate API with no filters', async () => {
const resolvedValue = {
rule_tags: ['a', 'b', 'c'],
};
http.get.mockResolvedValueOnce(resolvedValue);

const result = await loadRuleTags({
http,
});

expect(result).toEqual({
ruleTags: ['a', 'b', 'c'],
});

expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_aggregate",
]
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,63 @@ import { RuleAggregations, RuleStatus } from '../../../types';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { mapFiltersToKql } from './map_filters_to_kql';

export interface RuleTagsAggregations {
ruleTags: string[];
}

const rewriteBodyRes: RewriteRequestCase<RuleAggregations> = ({
rule_execution_status: ruleExecutionStatus,
rule_enabled_status: ruleEnabledStatus,
rule_muted_status: ruleMutedStatus,
rule_snoozed_status: ruleSnoozedStatus,
rule_tags: ruleTags,
...rest
}: any) => ({
...rest,
ruleExecutionStatus,
ruleEnabledStatus,
ruleMutedStatus,
ruleSnoozedStatus,
ruleTags,
});

const rewriteTagsBodyRes: RewriteRequestCase<RuleTagsAggregations> = ({
rule_tags: ruleTags,
}: any) => ({
ruleTags,
});

// TODO: https://github.com/elastic/kibana/issues/131682
export async function loadRuleTags({ http }: { http: HttpSetup }): Promise<RuleTagsAggregations> {
const res = await http.get<AsApiContract<RuleAggregations>>(
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_aggregate`
);
return rewriteTagsBodyRes(res);
}

export async function loadRuleAggregations({
http,
searchText,
typesFilter,
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleStatusesFilter,
tagsFilter,
}: {
http: HttpSetup;
searchText?: string;
typesFilter?: string[];
actionTypesFilter?: string[];
ruleExecutionStatusesFilter?: string[];
ruleStatusesFilter?: RuleStatus[];
tagsFilter?: string[];
}): Promise<RuleAggregations> {
const filters = mapFiltersToKql({
typesFilter,
actionTypesFilter,
ruleExecutionStatusesFilter,
ruleStatusesFilter,
tagsFilter,
});
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 @@ -7,7 +7,7 @@

export { alertingFrameworkHealth } from './health';
export { mapFiltersToKql } from './map_filters_to_kql';
export { loadRuleAggregations } from './aggregate';
export { loadRuleAggregations, loadRuleTags } from './aggregate';
export { createRule } from './create';
export { deleteRules } from './delete';
export { disableRule, disableRules } from './disable';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ describe('mapFiltersToKql', () => {
]);
});

test('should handle tagsFilter', () => {
expect(
mapFiltersToKql({
tagsFilter: ['a', 'b', 'c'],
})
).toEqual(['alert.attributes.tags:(a or b or c)']);
});

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

test('should handle typesFilter, actionTypesFilter and ruleExecutionStatusesFilter', () => {
test('should handle typesFilter, actionTypesFilter, ruleExecutionStatusesFilter, and tagsFilter', () => {
expect(
mapFiltersToKql({
typesFilter: ['type', 'filter'],
actionTypesFilter: ['action', 'types', 'filter'],
ruleExecutionStatusesFilter: ['alert', 'statuses', 'filter'],
tagsFilter: ['a', 'b', 'c'],
})
).toEqual([
'alert.attributes.alertTypeId:(type or filter)',
'(alert.attributes.actions:{ actionTypeId:action } OR alert.attributes.actions:{ actionTypeId:types } OR alert.attributes.actions:{ actionTypeId:filter })',
'alert.attributes.executionStatus.status:(alert or statuses or filter)',
'alert.attributes.tags:(a or b or c)',
]);
});
});
Loading

0 comments on commit 19298ee

Please sign in to comment.