From 3a07e12af8136a4be0b4355373b7910ebeed4a50 Mon Sep 17 00:00:00 2001 From: mgiota Date: Fri, 18 Mar 2022 22:17:55 +0100 Subject: [PATCH 01/15] users with read permissions can not edit/delete/create rules --- .../public/pages/rules/config.ts | 5 ++ .../public/pages/rules/index.tsx | 48 +++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/config.ts b/x-pack/plugins/observability/public/pages/rules/config.ts index 0296fdb73b951..ce772f8e93881 100644 --- a/x-pack/plugins/observability/public/pages/rules/config.ts +++ b/x-pack/plugins/observability/public/pages/rules/config.ts @@ -91,3 +91,8 @@ export function convertRulesToTableItems( enabledInLicense: !!ruleTypeIndex.get(rule.ruleTypeId)?.enabledInLicense, })); } + +type Capabilities = Record; + +export const hasExecuteActionsCapability = (capabilities: Capabilities) => + capabilities?.actions?.execute; diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index d6f932baeefba..904c87a722211 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -44,6 +44,7 @@ import { DEFAULT_SEARCH_PAGE_SIZE, convertRulesToTableItems, OBSERVABILITY_SOLUTIONS, + hasExecuteActionsCapability, } from './config'; import { LAST_RESPONSE_COLUMN_TITLE, @@ -67,9 +68,11 @@ export function RulesPage() { http, docLinks, triggersActionsUi, + application: { capabilities }, notifications: { toasts }, } = useKibana().services; - + const ruleTypeRegistry = triggersActionsUi.ruleTypeRegistry; + const canExecuteActions = hasExecuteActionsCapability(capabilities); const [page, setPage] = useState({ index: 0, size: DEFAULT_SEARCH_PAGE_SIZE }); const [sort, setSort] = useState['sort']>({ field: 'name', @@ -80,6 +83,9 @@ export function RulesPage() { const [rulesToDelete, setRulesToDelete] = useState([]); const [createRuleFlyoutVisibility, setCreateRuleFlyoutVisibility] = useState(false); + const isRuleTypeEditableInContext = (ruleTypeId: string) => + ruleTypeRegistry.has(ruleTypeId) ? !ruleTypeRegistry.get(ruleTypeId).requiresAppContext : false; + const onRuleEdit = (ruleItem: RuleTableItem) => { setCurrentRuleToEdit(ruleItem); }; @@ -90,8 +96,14 @@ export function RulesPage() { sort, }); const { data: rules, totalItemCount, error } = rulesState; - const { ruleTypeIndex } = useLoadRuleTypes({ filteredSolutions: OBSERVABILITY_SOLUTIONS }); + const { ruleTypeIndex, ruleTypes } = useLoadRuleTypes({ + filteredSolutions: OBSERVABILITY_SOLUTIONS, + }); + const authorizedRuleTypes = [...ruleTypes.values()]; + const authorizedToCreateAnyRules = authorizedRuleTypes.some( + (ruleType) => ruleType.authorizedConsumers[ALERTS_FEATURE_ID]?.all + ); useBreadcrumbs([ { text: RULES_BREADCRUMB_TEXT, @@ -152,6 +164,9 @@ export function RulesPage() { {RULES_PAGE_TITLE} , rightSideItems: [ - setCreateRuleFlyoutVisibility(true)} - > - - , + authorizedToCreateAnyRules && ( + setCreateRuleFlyoutVisibility(true)} + > + + + ), Date: Fri, 18 Mar 2022 22:55:35 +0100 Subject: [PATCH 02/15] users with read permissions can not change the status --- .../public/pages/rules/components/status.tsx | 28 +++++++++++++++---- .../pages/rules/components/status_context.tsx | 5 ++-- .../public/pages/rules/index.tsx | 1 + .../observability/public/pages/rules/types.ts | 2 ++ 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/status.tsx b/x-pack/plugins/observability/public/pages/rules/components/status.tsx index abc2dc8bfa492..f13d1a9b3805f 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status.tsx @@ -5,21 +5,37 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import { EuiBadge } from '@elastic/eui'; +import { noop } from 'lodash/fp'; import { StatusProps } from '../types'; import { statusMap } from '../config'; -export function Status({ type, onClick }: StatusProps) { +export function Status({ type, disabled, onClick }: StatusProps) { + console.log(disabled, '!!disabled'); + const props = useMemo( + () => ({ + color: statusMap[type].color, + ...(!disabled ? { onClick } : { onClick: noop }), + ...(!disabled ? { iconType: 'arrowDown', iconSide: 'right' as const } : {}), + ...(!disabled ? { iconOnClick: onClick } : { iconOnClick: noop }), + }), + [disabled, onClick, type] + ); return ( - + {statusMap[type].label} + + ); + /* {statusMap[type].label} - - ); + + };*/ } diff --git a/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx b/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx index 49761d7c43154..711d91271bf7e 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx @@ -18,6 +18,7 @@ import { statusMap } from '../config'; export function StatusContext({ item, + disabled = false, onStatusChanged, enableRule, disableRule, @@ -29,8 +30,8 @@ export function StatusContext({ const currentStatus = item.enabled ? RuleStatus.enabled : RuleStatus.disabled; const popOverButton = useMemo( - () => , - [currentStatus, togglePopover] + () => , + [disabled, currentStatus, togglePopover] ); const onContextMenuItemClick = useCallback( diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index 904c87a722211..902c3741d84de 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -145,6 +145,7 @@ export function RulesPage() { render: (_enabled: boolean, item: RuleTableItem) => { return ( reload()} enableRule={async () => await enableRule({ http, id: item.id })} diff --git a/x-pack/plugins/observability/public/pages/rules/types.ts b/x-pack/plugins/observability/public/pages/rules/types.ts index 9d58847b8e0c9..1334a7297816a 100644 --- a/x-pack/plugins/observability/public/pages/rules/types.ts +++ b/x-pack/plugins/observability/public/pages/rules/types.ts @@ -9,6 +9,7 @@ import { AlertExecutionStatus } from '../../../../alerting/common'; import { RuleTableItem, Rule } from '../../../../triggers_actions_ui/public'; export interface StatusProps { type: RuleStatus; + disabled: boolean; onClick: () => void; } @@ -27,6 +28,7 @@ export type Status = Record< export interface StatusContextProps { item: RuleTableItem; + disabled: boolean; onStatusChanged: (status: RuleStatus) => void; enableRule: (rule: Rule) => Promise; disableRule: (rule: Rule) => Promise; From 882de2e385f399b888abcdffacabc4edc8fd496a Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 21 Mar 2022 09:53:29 +0100 Subject: [PATCH 03/15] clean up unused code --- .../public/pages/rules/components/status.tsx | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/status.tsx b/x-pack/plugins/observability/public/pages/rules/components/status.tsx index f13d1a9b3805f..faa2acdfdc933 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status.tsx @@ -27,15 +27,4 @@ export function Status({ type, disabled, onClick }: StatusProps) { {statusMap[type].label} ); - /* - {statusMap[type].label} - - };*/ } From 20607d6fc3a52741d2f3c4cf6de39c21b2407966 Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 21 Mar 2022 10:11:29 +0100 Subject: [PATCH 04/15] localization for change status aria label --- .../observability/public/pages/rules/components/status.tsx | 7 ++++++- .../observability/public/pages/rules/translations.ts | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/status.tsx b/x-pack/plugins/observability/public/pages/rules/components/status.tsx index faa2acdfdc933..7646f162d7a61 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status.tsx @@ -10,6 +10,7 @@ import { EuiBadge } from '@elastic/eui'; import { noop } from 'lodash/fp'; import { StatusProps } from '../types'; import { statusMap } from '../config'; +import { RULES_CHANGE_STATUS } from '../translations'; export function Status({ type, disabled, onClick }: StatusProps) { console.log(disabled, '!!disabled'); @@ -23,7 +24,11 @@ export function Status({ type, disabled, onClick }: StatusProps) { [disabled, onClick, type] ); return ( - + {statusMap[type].label} ); diff --git a/x-pack/plugins/observability/public/pages/rules/translations.ts b/x-pack/plugins/observability/public/pages/rules/translations.ts index b72d03bf8e566..69f0b5beebf46 100644 --- a/x-pack/plugins/observability/public/pages/rules/translations.ts +++ b/x-pack/plugins/observability/public/pages/rules/translations.ts @@ -144,6 +144,13 @@ export const SEARCH_PLACEHOLDER = i18n.translate( { defaultMessage: 'Search' } ); +export const RULES_CHANGE_STATUS = i18n.translate( + 'xpack.observability.rules.rulesTable.changeStatusAriaLabel', + { + defaultMessage: 'Change status', + } +); + export const confirmModalText = ( numIdsToDelete: number, singleTitle: string, From 237cb2381498a294cb222d95c2d439470c1b1280 Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 21 Mar 2022 10:12:03 +0100 Subject: [PATCH 05/15] remove console log --- .../observability/public/pages/rules/components/status.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/status.tsx b/x-pack/plugins/observability/public/pages/rules/components/status.tsx index 7646f162d7a61..612d6f8f30bdd 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status.tsx @@ -13,7 +13,6 @@ import { statusMap } from '../config'; import { RULES_CHANGE_STATUS } from '../translations'; export function Status({ type, disabled, onClick }: StatusProps) { - console.log(disabled, '!!disabled'); const props = useMemo( () => ({ color: statusMap[type].color, From 2d51427d3c2effd0e25029a29a2be65f8a995aad Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 21 Mar 2022 11:57:30 +0100 Subject: [PATCH 06/15] add muted status --- .../public/pages/rules/components/name.tsx | 14 +--------- .../pages/rules/components/status_context.tsx | 26 +++++++++++++++++-- .../public/pages/rules/config.ts | 4 +++ .../public/pages/rules/index.tsx | 2 ++ .../observability/public/pages/rules/types.ts | 2 ++ .../triggers_actions_ui/public/index.ts | 1 + 6 files changed, 34 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/name.tsx b/x-pack/plugins/observability/public/pages/rules/components/name.tsx index 2b1f831256910..a800de9db9422 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/name.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/name.tsx @@ -34,17 +34,5 @@ export function Name({ name, rule }: RuleNameProps) { ); - return ( - <> - {link} - {rule.enabled && rule.muteAll && ( - - - - )} - - ); + return <>{link}; } diff --git a/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx b/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx index 711d91271bf7e..b0534a0b0a91e 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx @@ -23,12 +23,18 @@ export function StatusContext({ enableRule, disableRule, muteRule, + unMuteRule, }: StatusContextProps) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); - const currentStatus = item.enabled ? RuleStatus.enabled : RuleStatus.disabled; + let currentStatus: RuleStatus; + if (item.enabled) { + currentStatus = item.muteAll ? RuleStatus.muted : RuleStatus.enabled; + } else { + currentStatus = RuleStatus.disabled; + } const popOverButton = useMemo( () => , [disabled, currentStatus, togglePopover] @@ -42,15 +48,30 @@ export function StatusContext({ if (status === RuleStatus.enabled) { await enableRule({ ...item, enabled: true }); + if (item.muteAll) { + await unMuteRule({ ...item, muteAll: false }); + } } else if (status === RuleStatus.disabled) { await disableRule({ ...item, enabled: false }); + } else if (status === RuleStatus.muted) { + await muteRule({ ...item, muteAll: true }); } setIsUpdating(false); onStatusChanged(status); } }, - [item, togglePopover, enableRule, disableRule, currentStatus, onStatusChanged] + [ + item, + togglePopover, + enableRule, + disableRule, + muteRule, + unMuteRule, + currentStatus, + onStatusChanged, + ] ); + const panelItems = useMemo( () => Object.values(RuleStatus).map((status: RuleStatus) => ( @@ -58,6 +79,7 @@ export function StatusContext({ icon={status === currentStatus ? 'check' : 'empty'} key={status} onClick={() => onContextMenuItemClick(status)} + disabled={status === RuleStatus.muted && currentStatus === RuleStatus.disabled} > {statusMap[status].label} diff --git a/x-pack/plugins/observability/public/pages/rules/config.ts b/x-pack/plugins/observability/public/pages/rules/config.ts index 454cfa147ccd9..c9adc5e31f55c 100644 --- a/x-pack/plugins/observability/public/pages/rules/config.ts +++ b/x-pack/plugins/observability/public/pages/rules/config.ts @@ -26,6 +26,10 @@ export const statusMap: Status = { color: 'default', label: 'Disabled', }, + [RuleStatus.muted]: { + color: 'warning', + label: 'Muted', + }, }; export const DEFAULT_SEARCH_PAGE_SIZE: number = 25; diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index e90ddccafc778..079d57ae7f58b 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -39,6 +39,7 @@ import { disableRule, muteRule, useLoadRuleTypes, + unmuteRule, } from '../../../../triggers_actions_ui/public'; import { AlertExecutionStatus, ALERTS_FEATURE_ID } from '../../../../alerting/common'; import { Pagination } from './types'; @@ -180,6 +181,7 @@ export function RulesPage() { enableRule={async () => await enableRule({ http, id: item.id })} disableRule={async () => await disableRule({ http, id: item.id })} muteRule={async () => await muteRule({ http, id: item.id })} + unMuteRule={async () => await unmuteRule({ http, id: item.id })} /> ); }, diff --git a/x-pack/plugins/observability/public/pages/rules/types.ts b/x-pack/plugins/observability/public/pages/rules/types.ts index 623959a1d5114..53a0f2a454202 100644 --- a/x-pack/plugins/observability/public/pages/rules/types.ts +++ b/x-pack/plugins/observability/public/pages/rules/types.ts @@ -16,6 +16,7 @@ export interface StatusProps { export enum RuleStatus { enabled = 'enabled', disabled = 'disabled', + muted = 'muted', } export type Status = Record< @@ -33,6 +34,7 @@ export interface StatusContextProps { enableRule: (rule: Rule) => Promise; disableRule: (rule: Rule) => Promise; muteRule: (rule: Rule) => Promise; + unMuteRule: (rule: Rule) => Promise; } export interface StatusFilterProps { diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index eb346e43cfbc9..b1ef489bfef70 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -55,6 +55,7 @@ export { deleteRules } from './application/lib/rule_api/delete'; export { enableRule } from './application/lib/rule_api/enable'; export { disableRule } from './application/lib/rule_api/disable'; export { muteRule } from './application/lib/rule_api/mute'; +export { unmuteRule } from './application/lib/rule_api/unmute'; export { loadRuleAggregations } from './application/lib/rule_api/aggregate'; export { useLoadRuleTypes } from './application/hooks/use_load_rule_types'; From ca868babe28910eac920f996dbabbd5ad2f8db90 Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 21 Mar 2022 12:00:13 +0100 Subject: [PATCH 07/15] rename to snoozed --- .../public/pages/rules/components/status_context.tsx | 6 +++--- x-pack/plugins/observability/public/pages/rules/config.ts | 4 ++-- x-pack/plugins/observability/public/pages/rules/types.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx b/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx index b0534a0b0a91e..c7bd29d85b17a 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/status_context.tsx @@ -31,7 +31,7 @@ export function StatusContext({ let currentStatus: RuleStatus; if (item.enabled) { - currentStatus = item.muteAll ? RuleStatus.muted : RuleStatus.enabled; + currentStatus = item.muteAll ? RuleStatus.snoozed : RuleStatus.enabled; } else { currentStatus = RuleStatus.disabled; } @@ -53,7 +53,7 @@ export function StatusContext({ } } else if (status === RuleStatus.disabled) { await disableRule({ ...item, enabled: false }); - } else if (status === RuleStatus.muted) { + } else if (status === RuleStatus.snoozed) { await muteRule({ ...item, muteAll: true }); } setIsUpdating(false); @@ -79,7 +79,7 @@ export function StatusContext({ icon={status === currentStatus ? 'check' : 'empty'} key={status} onClick={() => onContextMenuItemClick(status)} - disabled={status === RuleStatus.muted && currentStatus === RuleStatus.disabled} + disabled={status === RuleStatus.snoozed && currentStatus === RuleStatus.disabled} > {statusMap[status].label} diff --git a/x-pack/plugins/observability/public/pages/rules/config.ts b/x-pack/plugins/observability/public/pages/rules/config.ts index c9adc5e31f55c..342c9e052976c 100644 --- a/x-pack/plugins/observability/public/pages/rules/config.ts +++ b/x-pack/plugins/observability/public/pages/rules/config.ts @@ -26,9 +26,9 @@ export const statusMap: Status = { color: 'default', label: 'Disabled', }, - [RuleStatus.muted]: { + [RuleStatus.snoozed]: { color: 'warning', - label: 'Muted', + label: 'Snoozed', }, }; diff --git a/x-pack/plugins/observability/public/pages/rules/types.ts b/x-pack/plugins/observability/public/pages/rules/types.ts index 53a0f2a454202..8d6012cb9933f 100644 --- a/x-pack/plugins/observability/public/pages/rules/types.ts +++ b/x-pack/plugins/observability/public/pages/rules/types.ts @@ -16,7 +16,7 @@ export interface StatusProps { export enum RuleStatus { enabled = 'enabled', disabled = 'disabled', - muted = 'muted', + snoozed = 'snoozed', } export type Status = Record< From d87bd1c48f4bd3a2dde9e9b664fd300ce27f03d4 Mon Sep 17 00:00:00 2001 From: mgiota Date: Mon, 21 Mar 2022 13:11:11 +0100 Subject: [PATCH 08/15] remove unused imports --- .../observability/public/pages/rules/components/name.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/name.tsx b/x-pack/plugins/observability/public/pages/rules/components/name.tsx index a800de9db9422..cbde68ea27eb4 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/name.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/name.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText, EuiBadge } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiText } from '@elastic/eui'; import { RuleNameProps } from '../types'; import { useKibana } from '../../../utils/kibana_react'; From ab76864d5a9c7364e071683184db0b754f99c822 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 22 Mar 2022 08:36:23 +0100 Subject: [PATCH 09/15] rename snoozed to snoozed permanently --- x-pack/plugins/observability/public/pages/rules/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/rules/config.ts b/x-pack/plugins/observability/public/pages/rules/config.ts index 342c9e052976c..47e11f9ee8229 100644 --- a/x-pack/plugins/observability/public/pages/rules/config.ts +++ b/x-pack/plugins/observability/public/pages/rules/config.ts @@ -28,7 +28,7 @@ export const statusMap: Status = { }, [RuleStatus.snoozed]: { color: 'warning', - label: 'Snoozed', + label: 'Snoozed permanently', }, }; From 316a08617d237847eaf2de580c96c0f506b4bb50 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 22 Mar 2022 10:11:36 +0100 Subject: [PATCH 10/15] localize statuses --- .../public/pages/rules/config.ts | 9 +++++--- .../public/pages/rules/translations.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/config.ts b/x-pack/plugins/observability/public/pages/rules/config.ts index 47e11f9ee8229..ed34317b48e76 100644 --- a/x-pack/plugins/observability/public/pages/rules/config.ts +++ b/x-pack/plugins/observability/public/pages/rules/config.ts @@ -13,6 +13,9 @@ import { RULE_STATUS_PENDING, RULE_STATUS_UNKNOWN, RULE_STATUS_WARNING, + RULE_STATUS_ENABLED, + RULE_STATUS_DISABLED, + RULE_STATUS_SNOOZED_PERMANENTLY, } from './translations'; import { AlertExecutionStatuses } from '../../../../alerting/common'; import { Rule, RuleTypeIndex, RuleType } from '../../../../triggers_actions_ui/public'; @@ -20,15 +23,15 @@ import { Rule, RuleTypeIndex, RuleType } from '../../../../triggers_actions_ui/p export const statusMap: Status = { [RuleStatus.enabled]: { color: 'primary', - label: 'Enabled', + label: RULE_STATUS_ENABLED, }, [RuleStatus.disabled]: { color: 'default', - label: 'Disabled', + label: RULE_STATUS_DISABLED, }, [RuleStatus.snoozed]: { color: 'warning', - label: 'Snoozed permanently', + label: RULE_STATUS_SNOOZED_PERMANENTLY, }, }; diff --git a/x-pack/plugins/observability/public/pages/rules/translations.ts b/x-pack/plugins/observability/public/pages/rules/translations.ts index 69f0b5beebf46..49d55b48bbb69 100644 --- a/x-pack/plugins/observability/public/pages/rules/translations.ts +++ b/x-pack/plugins/observability/public/pages/rules/translations.ts @@ -53,6 +53,27 @@ export const RULE_STATUS_WARNING = i18n.translate( } ); +export const RULE_STATUS_ENABLED = i18n.translate( + 'xpack.observability.rules.rulesTable.ruleStatusEnabled', + { + defaultMessage: 'Enabled', + } +); + +export const RULE_STATUS_DISABLED = i18n.translate( + 'xpack.observability.rules.rulesTable.ruleStatusDisabled', + { + defaultMessage: 'Disabled', + } +); + +export const RULE_STATUS_SNOOZED_PERMANENTLY = i18n.translate( + 'xpack.observability.rules.rulesTable.ruleStatusSnoozedPermanently', + { + defaultMessage: 'Snoozed permanently', + } +); + export const LAST_RESPONSE_COLUMN_TITLE = i18n.translate( 'xpack.observability.rules.rulesTable.columns.lastResponseTitle', { From 1699116726a42e4389a8afe0392452a7da7da7df Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 22 Mar 2022 14:16:15 +0100 Subject: [PATCH 11/15] implement no data and no permission screen --- .../components/prompts/noData_prompt.tsx | 69 +++++++ .../prompts/noPermission_prompt.tsx | 44 +++++ .../public/pages/rules/index.tsx | 187 ++++++++++-------- 3 files changed, 217 insertions(+), 83 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/rules/components/prompts/noData_prompt.tsx create mode 100644 x-pack/plugins/observability/public/pages/rules/components/prompts/noPermission_prompt.tsx diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/noData_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/noData_prompt.tsx new file mode 100644 index 0000000000000..62f9735b815eb --- /dev/null +++ b/x-pack/plugins/observability/public/pages/rules/components/prompts/noData_prompt.tsx @@ -0,0 +1,69 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { EuiButton, EuiEmptyPrompt, EuiLink, EuiButtonEmpty, EuiPageTemplate } from '@elastic/eui'; + +export function NoDataPrompt({ + onCTAClicked, + documentationLink, +}: { + onCTAClicked: () => void; + documentationLink: string; +}) { + return ( + + + + + } + body={ +

+ +

+ } + actions={[ + + + , + + + Documentation + + , + ]} + /> +
+ ); +} diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/noPermission_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/noPermission_prompt.tsx new file mode 100644 index 0000000000000..edfe1c6840d8b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/rules/components/prompts/noPermission_prompt.tsx @@ -0,0 +1,44 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; +import { EuiEmptyPrompt, EuiPageTemplate } from '@elastic/eui'; + +export function NoPermissionPrompt() { + return ( + + + + + } + body={ +

+ +

+ } + /> +
+ ); +} diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index 079d57ae7f58b..ebf9a97ae11a2 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -32,6 +32,8 @@ import { ExecutionStatus } from './components/execution_status'; import { LastRun } from './components/last_run'; import { EditRuleFlyout } from './components/edit_rule_flyout'; import { DeleteModalConfirmation } from './components/delete_modal_confirmation'; +import { NoDataPrompt } from './components/prompts/noData_prompt'; +import { NoPermissionPrompt } from './components/prompts/noPermission_prompt'; import { deleteRules, RuleTableItem, @@ -78,6 +80,7 @@ export function RulesPage() { application: { capabilities }, notifications: { toasts }, } = useKibana().services; + const documentationLink = docLinks.links.alerting.guide; const ruleTypeRegistry = triggersActionsUi.ruleTypeRegistry; const canExecuteActions = hasExecuteActionsCapability(capabilities); const [page, setPage] = useState({ index: 0, size: DEFAULT_SEARCH_PAGE_SIZE }); @@ -243,6 +246,105 @@ export function RulesPage() { [] ); + const getRulesTable = () => { + if (totalItemCount === 0 && !rulesState.isLoading) { + return authorizedToCreateAnyRules ? ( + setCreateRuleFlyoutVisibility(true)} + /> + ) : ( + + ); + } + return ( + <> + + + { + setInputText(e.target.value); + if (e.target.value === '') { + setSearchText(e.target.value); + } + }} + onKeyUp={(e) => { + if (e.keyCode === ENTER_KEY) { + setSearchText(inputText); + } + }} + placeholder={SEARCH_PLACEHOLDER} + /> + + + setRuleLastResponseFilter(ids)} + /> + + + + + + , + + + + + + + + + + + + + + + + setPage(index)} + sort={sort} + onSortChange={(changedSort) => { + setSort(changedSort); + }} + /> + + + + ); + }; + return ( ), - - - { - setInputText(e.target.value); - if (e.target.value === '') { - setSearchText(e.target.value); - } - }} - onKeyUp={(e) => { - if (e.keyCode === ENTER_KEY) { - setSearchText(inputText); - } - }} - placeholder={SEARCH_PLACEHOLDER} - /> - - - setRuleLastResponseFilter(ids)} - /> - - - - - - , - - - - - - - - - - - - - - - - setPage(index)} - sort={sort} - onSortChange={(changedSort) => { - setSort(changedSort); - }} - /> - - + {getRulesTable()} {error && toasts.addDanger({ title: error, From f71fca78d3b265e8d0216d850b3599f18a50712c Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 22 Mar 2022 14:36:20 +0100 Subject: [PATCH 12/15] fix prompt filenames --- .../prompts/{noData_prompt.tsx => no_data_prompt.tsx} | 0 .../{noPermission_prompt.tsx => no_permission_prompt.tsx} | 0 x-pack/plugins/observability/public/pages/rules/index.tsx | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename x-pack/plugins/observability/public/pages/rules/components/prompts/{noData_prompt.tsx => no_data_prompt.tsx} (100%) rename x-pack/plugins/observability/public/pages/rules/components/prompts/{noPermission_prompt.tsx => no_permission_prompt.tsx} (100%) diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/noData_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/rules/components/prompts/noData_prompt.tsx rename to x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/noPermission_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_permission_prompt.tsx similarity index 100% rename from x-pack/plugins/observability/public/pages/rules/components/prompts/noPermission_prompt.tsx rename to x-pack/plugins/observability/public/pages/rules/components/prompts/no_permission_prompt.tsx diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index ebf9a97ae11a2..d5897294289e8 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -32,8 +32,8 @@ import { ExecutionStatus } from './components/execution_status'; import { LastRun } from './components/last_run'; import { EditRuleFlyout } from './components/edit_rule_flyout'; import { DeleteModalConfirmation } from './components/delete_modal_confirmation'; -import { NoDataPrompt } from './components/prompts/noData_prompt'; -import { NoPermissionPrompt } from './components/prompts/noPermission_prompt'; +import { NoDataPrompt } from './components/prompts/no_data_prompt'; +import { NoPermissionPrompt } from './components/prompts/no_permission_prompt'; import { deleteRules, RuleTableItem, From 4685334b94f4db43dbcd39bd80497541f95ea8c7 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 22 Mar 2022 15:43:48 +0100 Subject: [PATCH 13/15] fix i18n error --- .../public/pages/rules/components/prompts/no_data_prompt.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx index 62f9735b815eb..b9c0e24160004 100644 --- a/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx +++ b/x-pack/plugins/observability/public/pages/rules/components/prompts/no_data_prompt.tsx @@ -39,7 +39,7 @@ export function NoDataPrompt({ body={

From 4b9937e4de808471b6fe3181b7732bcb450e7c79 Mon Sep 17 00:00:00 2001 From: mgiota Date: Tue, 22 Mar 2022 22:50:55 +0100 Subject: [PATCH 14/15] change permanently to indefinitely --- x-pack/plugins/observability/public/pages/rules/config.ts | 4 ++-- .../observability/public/pages/rules/translations.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/rules/config.ts b/x-pack/plugins/observability/public/pages/rules/config.ts index ed34317b48e76..736f538ee7b21 100644 --- a/x-pack/plugins/observability/public/pages/rules/config.ts +++ b/x-pack/plugins/observability/public/pages/rules/config.ts @@ -15,7 +15,7 @@ import { RULE_STATUS_WARNING, RULE_STATUS_ENABLED, RULE_STATUS_DISABLED, - RULE_STATUS_SNOOZED_PERMANENTLY, + RULE_STATUS_SNOOZED_INDEFINITELY, } from './translations'; import { AlertExecutionStatuses } from '../../../../alerting/common'; import { Rule, RuleTypeIndex, RuleType } from '../../../../triggers_actions_ui/public'; @@ -31,7 +31,7 @@ export const statusMap: Status = { }, [RuleStatus.snoozed]: { color: 'warning', - label: RULE_STATUS_SNOOZED_PERMANENTLY, + label: RULE_STATUS_SNOOZED_INDEFINITELY, }, }; diff --git a/x-pack/plugins/observability/public/pages/rules/translations.ts b/x-pack/plugins/observability/public/pages/rules/translations.ts index 49d55b48bbb69..36f8ff62f1a4c 100644 --- a/x-pack/plugins/observability/public/pages/rules/translations.ts +++ b/x-pack/plugins/observability/public/pages/rules/translations.ts @@ -67,10 +67,10 @@ export const RULE_STATUS_DISABLED = i18n.translate( } ); -export const RULE_STATUS_SNOOZED_PERMANENTLY = i18n.translate( - 'xpack.observability.rules.rulesTable.ruleStatusSnoozedPermanently', +export const RULE_STATUS_SNOOZED_INDEFINITELY = i18n.translate( + 'xpack.observability.rules.rulesTable.ruleStatusSnoozedIndefinitely', { - defaultMessage: 'Snoozed permanently', + defaultMessage: 'Snoozed indefinitely', } ); From 652504e8ee6d2424e88d3e9d20d63dac73e36a1b Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 23 Mar 2022 08:34:55 +0100 Subject: [PATCH 15/15] 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;