Skip to content

Commit

Permalink
Status filter API call
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaweiWu committed Apr 26, 2022
1 parent 2e6ded4 commit b42c959
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 14 deletions.
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,
ruleStateFilter: false,
rulesDetailLogs: true,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ describe('mapFiltersToKql', () => {
).toEqual(['alert.attributes.executionStatus.status:(alert or statuses or filter)']);
});

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

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

expect(
mapFiltersToKql({
ruleStateFilter: ['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 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 = (ruleStateFilter: RuleStatus[] = []) => {
const enablementFilters = ruleStateFilter.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,
ruleStatusesFilter,
ruleStateFilter,
}: {
typesFilter?: string[];
actionTypesFilter?: string[];
ruleStatusesFilter?: string[];
ruleStateFilter?: RuleStatus[];
}): string[] => {
const filters = [];

if (typesFilter && typesFilter.length) {
filters.push(`alert.attributes.alertTypeId:(${typesFilter.join(' or ')})`);
}
Expand All @@ -32,5 +50,22 @@ export const mapFiltersToKql = ({
if (ruleStatusesFilter && ruleStatusesFilter.length) {
filters.push(`alert.attributes.executionStatus.status:(${ruleStatusesFilter.join(' or ')})`);
}

if (ruleStateFilter && ruleStateFilter.length) {
const enablementFilter = getEnablementFilter(ruleStateFilter);
const snoozedFilter = `(alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)`;
const hasEnablement =
ruleStateFilter.includes('enabled') || ruleStateFilter.includes('disabled');
const hasSnoozed = ruleStateFilter.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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,101 @@ describe('loadRules', () => {
]
`);
});

test('should call find API with ruleStateFilter', async () => {
const resolvedValue = {
page: 1,
per_page: 10,
total: 0,
data: [],
};
http.get.mockResolvedValue(resolvedValue);

let result = await loadRules({
http,
ruleStateFilter: ['enabled', 'snoozed'],
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)",
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);

result = await loadRules({
http,
ruleStateFilter: ['disabled'],
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[1]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.enabled:(false) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)",
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);

result = await loadRules({
http,
ruleStateFilter: ['enabled', 'disabled', 'snoozed'],
page: { index: 0, size: 10 },
});
expect(result).toEqual({
page: 1,
perPage: 10,
total: 0,
data: [],
});
expect(http.get.mock.calls[2]).toMatchInlineSnapshot(`
Array [
"/internal/alerting/rules/_find",
Object {
"query": Object {
"default_search_operator": "AND",
"filter": "alert.attributes.enabled:(true or false) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)",
"page": 1,
"per_page": 10,
"search": undefined,
"search_fields": undefined,
"sort_field": "name",
"sort_order": "asc",
},
},
]
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { HttpSetup } from '@kbn/core/public';
import { AsApiContract } from '@kbn/actions-plugin/common';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { Rule, Pagination, Sorting } from '../../../types';
import { Rule, Pagination, Sorting, RuleStatus } from '../../../types';
import { mapFiltersToKql } from './map_filters_to_kql';
import { transformRule } from './common_transformations';

Expand All @@ -22,6 +22,7 @@ export async function loadRules({
typesFilter,
actionTypesFilter,
ruleStatusesFilter,
ruleStateFilter,
sort = { field: 'name', direction: 'asc' },
}: {
http: HttpSetup;
Expand All @@ -30,14 +31,20 @@ export async function loadRules({
typesFilter?: string[];
actionTypesFilter?: string[];
ruleStatusesFilter?: string[];
ruleStateFilter?: RuleStatus[];
sort?: Sorting;
}): Promise<{
page: number;
perPage: number;
total: number;
data: Rule[];
}> {
const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, ruleStatusesFilter });
const filters = mapFiltersToKql({
typesFilter,
actionTypesFilter,
ruleStatusesFilter,
ruleStateFilter,
});
const res = await http.get<
AsApiContract<{
page: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,30 @@
import React, { useState, useCallback } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFilterButton, EuiPopover, EuiFilterGroup, EuiFilterSelectItem } from '@elastic/eui';
import { RuleStatus } from '../../../../types';

type State = 'enabled' | 'muted' | 'disabled';

const states: State[] = ['enabled', 'disabled', 'muted'];
const states: RuleStatus[] = ['enabled', 'disabled', 'snoozed'];

const optionStyles = {
textTransform: 'capitalize' as const,
};

const getOptionDataTestSubj = (state: State) => `ruleStateFilterOption-${state}`;
const getOptionDataTestSubj = (state: RuleStatus) => `ruleStateFilterOption-${state}`;

export interface RuleStateFilterProps {
selectedStates: State[];
selectedStates: RuleStatus[];
dataTestSubj?: string;
selectDataTestSubj?: string;
buttonDataTestSubj?: string;
optionDataTestSubj?: (state: State) => string;
onChange: (selectedStates: State[]) => void;
optionDataTestSubj?: (state: RuleStatus) => string;
onChange: (selectedStates: RuleStatus[]) => void;
}

export const RuleStateFilter = (props: RuleStateFilterProps) => {
const {
selectedStates = [],
dataTestSubj = 'ruleStateFilterSelect',
dataTestSubj = 'ruleStateFilter',
selectDataTestSubj = 'ruleStateFilterSelect',
buttonDataTestSubj = 'ruleStateFilterButton',
optionDataTestSubj = getOptionDataTestSubj,
onChange = () => {},
Expand All @@ -38,7 +39,7 @@ export const RuleStateFilter = (props: RuleStateFilterProps) => {
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);

const onFilterItemClick = useCallback(
(newOption: State) => () => {
(newOption: RuleStatus) => () => {
if (selectedStates.includes(newOption)) {
onChange(selectedStates.filter((option) => option !== newOption));
return;
Expand All @@ -53,7 +54,7 @@ export const RuleStateFilter = (props: RuleStateFilterProps) => {
}, [setIsPopoverOpen]);

return (
<EuiFilterGroup>
<EuiFilterGroup data-test-subj={dataTestSubj}>
<EuiPopover
isOpen={isPopoverOpen}
closePopover={() => setIsPopoverOpen(false)}
Expand All @@ -73,7 +74,7 @@ export const RuleStateFilter = (props: RuleStateFilterProps) => {
</EuiFilterButton>
}
>
<div data-test-subj={dataTestSubj}>
<div data-test-subj={selectDataTestSubj}>
{states.map((state) => {
return (
<EuiFilterSelectItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
RuleTableItem,
RuleType,
RuleTypeIndex,
RuleStatus,
Pagination,
Percentiles,
TriggersActionsUiConfig,
Expand Down Expand Up @@ -100,6 +101,8 @@ import { RuleDurationFormat } from './rule_duration_format';
import { shouldShowDurationWarning } from '../../../lib/execution_duration_utils';
import { getFormattedSuccessRatio } from '../../../lib/monitoring_utils';
import { triggersActionsUiConfig } from '../../../../common/lib/config_api';
import { RuleStateFilter } from './rule_state_filter';
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';

const ENTER_KEY = 13;

Expand Down Expand Up @@ -156,6 +159,7 @@ export const RulesList: React.FunctionComponent = () => {
const [typesFilter, setTypesFilter] = useState<string[]>([]);
const [actionTypesFilter, setActionTypesFilter] = useState<string[]>([]);
const [ruleStatusesFilter, setRuleStatusesFilter] = useState<string[]>([]);
const [ruleStateFilter, setRuleStateFilter] = useState<RuleStatus[]>([]);
const [ruleFlyoutVisible, setRuleFlyoutVisibility] = useState<boolean>(false);
const [editFlyoutVisible, setEditFlyoutVisibility] = useState<boolean>(false);
const [currentRuleToEdit, setCurrentRuleToEdit] = useState<RuleTableItem | null>(null);
Expand All @@ -165,6 +169,8 @@ export const RulesList: React.FunctionComponent = () => {
);
const [showErrors, setShowErrors] = useState(false);

const isRuleStateFilterEnabled = getIsExperimentalFeatureEnabled('ruleStateFilter');

useEffect(() => {
(async () => {
setConfig(await triggersActionsUiConfig({ http }));
Expand Down Expand Up @@ -228,6 +234,7 @@ export const RulesList: React.FunctionComponent = () => {
JSON.stringify(typesFilter),
JSON.stringify(actionTypesFilter),
JSON.stringify(ruleStatusesFilter),
JSON.stringify(ruleStateFilter),
]);

useEffect(() => {
Expand Down Expand Up @@ -287,6 +294,7 @@ export const RulesList: React.FunctionComponent = () => {
typesFilter,
actionTypesFilter,
ruleStatusesFilter,
ruleStateFilter,
sort,
});
await loadRuleAggs();
Expand All @@ -304,7 +312,9 @@ export const RulesList: React.FunctionComponent = () => {
isEmpty(searchText) &&
isEmpty(typesFilter) &&
isEmpty(actionTypesFilter) &&
isEmpty(ruleStatusesFilter)
isEmpty(ruleStatusesFilter) &&
isRuleStateFilterEnabled &&
isEmpty(ruleStateFilter)
);

setNoData(rulesResponse.data.length === 0 && !isFilterApplied);
Expand Down Expand Up @@ -960,6 +970,13 @@ export const RulesList: React.FunctionComponent = () => {
);
};

const getRuleStateFilter = () => {
if (isRuleStateFilterEnabled) {
return [<RuleStateFilter selectedStates={ruleStateFilter} onChange={setRuleStateFilter} />];
}
return [];
};

const toolsRight = [
<TypeFilter
key="type-filter"
Expand All @@ -971,6 +988,7 @@ export const RulesList: React.FunctionComponent = () => {
})
)}
/>,
...getRuleStateFilter(),
<ActionTypeFilter
key="action-type-filter"
actionTypes={actionTypes}
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/triggers_actions_ui/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,5 @@ export interface AlertsTableConfigurationRegistry {
id: string;
columns: EuiDataGridColumn[];
}

export type RuleStatus = 'enabled' | 'disabled' | 'snoozed';
Loading

0 comments on commit b42c959

Please sign in to comment.