From 652504e8ee6d2424e88d3e9d20d63dac73e36a1b Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 23 Mar 2022 08:34:55 +0100 Subject: [PATCH] status filter --- .../public/hooks/use_fetch_rules.ts | 11 ++- .../components/filters/status_filter.tsx | 77 +++++++++++++++++++ .../rules/components/last_response_filter.tsx | 2 +- .../public/pages/rules/index.tsx | 11 +++ .../observability/public/pages/rules/types.ts | 1 + .../lib/rule_api/map_filters_to_kql.ts | 6 ++ .../public/application/lib/rule_api/rules.ts | 9 ++- 7 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/rules/components/filters/status_filter.tsx diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts index b81046df99d28..b6f7ea2d0579a 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts @@ -19,7 +19,13 @@ interface RuleState { totalItemCount: number; } -export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort }: FetchRulesProps) { +export function useFetchRules({ + searchText, + ruleLastResponseFilter, + ruleStatusFilter, + page, + sort, +}: FetchRulesProps) { const { http } = useKibana().services; const [rulesState, setRulesState] = useState({ @@ -39,6 +45,7 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } searchText, typesFilter: OBSERVABILITY_RULE_TYPES, ruleStatusesFilter: ruleLastResponseFilter, + ruleStatusFilter: [false], sort, }); setRulesState((oldState) => ({ @@ -50,7 +57,7 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } } catch (_e) { setRulesState((oldState) => ({ ...oldState, isLoading: false, error: RULES_LOAD_ERROR })); } - }, [http, page, searchText, ruleLastResponseFilter, sort]); + }, [http, page, searchText, ruleLastResponseFilter, ruleStatusFilter, sort]); useEffect(() => { fetchRules(); }, [fetchRules]); diff --git a/x-pack/plugins/observability/public/pages/rules/components/filters/status_filter.tsx b/x-pack/plugins/observability/public/pages/rules/components/filters/status_filter.tsx new file mode 100644 index 0000000000000..8c916380d50d1 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/rules/components/filters/status_filter.tsx @@ -0,0 +1,77 @@ +/* + * 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, useEffect, useMemo } from 'react'; +import { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { RuleStatus } from '../../types'; +import { statusMap } from '../../config'; + +export function StatusFilter({ selectedStatuses, onChange }) { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [selectedValues, setSelectedValues] = useState(selectedStatuses); + + useEffect(() => { + if (onChange) { + onChange(selectedValues); + } + }, [selectedValues, onChange]); + + useEffect(() => { + setSelectedValues(selectedStatuses); + }, [selectedStatuses]); + + const panelItems = useMemo( + () => + Object.values(RuleStatus).map((status: RuleStatus) => ( + { + const isPreviouslyChecked = selectedValues.includes(status); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter((val) => val !== status)); + } else { + setSelectedValues(selectedValues.concat(status)); + } + }} + checked={selectedValues.includes(status) ? 'on' : undefined} + data-test-subj={`ruleStatus${status}FilterOption`} + > + {statusMap[status].label} + + )), + [selectedValues] + ); + + return ( + + 0} + numActiveFilters={selectedValues.length} + numFilters={selectedValues.length} + onClick={() => setIsPopoverOpen(!isPopoverOpen)} + data-test-subj="ruleStatusFilterButton" + > + + + } + closePopover={() => setIsPopoverOpen(false)} + anchorPosition="downLeft" + isOpen={isPopoverOpen} + panelPaddingSize="none" + > + {panelItems} + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx b/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx index 5a9be48252909..b56f6c7515162 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/last_response_filter.tsx @@ -74,7 +74,7 @@ export const LastResponseFilter: React.FunctionComponent = ({ } }} checked={selectedValues.includes(item) ? 'on' : undefined} - data-test-subj={`ruleStatus${item}FilerOption`} + data-test-subj={`ruleLastResponse${item}FilterOption`} > {rulesStatusesTranslationsMapping[item]} diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index d5897294289e8..4ecf44ce48372 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -27,6 +27,8 @@ import { useFetchRules } from '../../hooks/use_fetch_rules'; import { RulesTable } from './components/rules_table'; import { Name } from './components/name'; import { LastResponseFilter } from './components/last_response_filter'; +import { StatusFilter } from './components/filters/status_filter'; + import { StatusContext } from './components/status_context'; import { ExecutionStatus } from './components/execution_status'; import { LastRun } from './components/last_run'; @@ -92,6 +94,7 @@ export function RulesPage() { const [searchText, setSearchText] = useState(); const [refreshInterval, setRefreshInterval] = useState(60000); const [isPaused, setIsPaused] = useState(false); + const [ruleStatusFilter, setRuleStatusFilter] = useState([]); const [ruleLastResponseFilter, setRuleLastResponseFilter] = useState([]); const [currentRuleToEdit, setCurrentRuleToEdit] = useState(null); const [rulesToDelete, setRulesToDelete] = useState([]); @@ -115,6 +118,7 @@ export function RulesPage() { const { rulesState, setRulesState, reload } = useFetchRules({ searchText, ruleLastResponseFilter, + ruleStatusFilter, page, sort, }); @@ -286,6 +290,13 @@ export function RulesPage() { onChange={(ids: string[]) => setRuleLastResponseFilter(ids)} /> + + setRuleStatusFilter(ids)} + /> + ['sort']; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts index d7b22a7a4aee4..9b33592732f37 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts @@ -9,10 +9,12 @@ export const mapFiltersToKql = ({ typesFilter, actionTypesFilter, ruleStatusesFilter, + ruleStatusFilter, }: { typesFilter?: string[]; actionTypesFilter?: string[]; ruleStatusesFilter?: string[]; + ruleStatusFilter?: boolean[]; }): string[] => { const filters = []; if (typesFilter && typesFilter.length) { @@ -29,8 +31,12 @@ export const mapFiltersToKql = ({ ].join('') ); } + // TODO rename ruleStatusesFilter to ruleLastResponseFilter in both triggers_actions_ui and observability plugins if (ruleStatusesFilter && ruleStatusesFilter.length) { filters.push(`alert.attributes.executionStatus.status:(${ruleStatusesFilter.join(' or ')})`); } + if (ruleStatusFilter && ruleStatusFilter.length) { + filters.push(`alert.attributes.enabled:(${ruleStatusFilter.join(' or ')})`); + } return filters; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.ts index 97c432a480355..58470f97b3731 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.ts @@ -22,6 +22,7 @@ export async function loadRules({ typesFilter, actionTypesFilter, ruleStatusesFilter, + ruleStatusFilter, sort = { field: 'name', direction: 'asc' }, }: { http: HttpSetup; @@ -30,6 +31,7 @@ export async function loadRules({ typesFilter?: string[]; actionTypesFilter?: string[]; ruleStatusesFilter?: string[]; + ruleStatusFilter?: boolean[]; sort?: Sorting; }): Promise<{ page: number; @@ -37,7 +39,12 @@ export async function loadRules({ total: number; data: Rule[]; }> { - const filters = mapFiltersToKql({ typesFilter, actionTypesFilter, ruleStatusesFilter }); + const filters = mapFiltersToKql({ + typesFilter, + actionTypesFilter, + ruleStatusesFilter, + ruleStatusFilter, + }); const res = await http.get< AsApiContract<{ page: number;