From 3a07e12af8136a4be0b4355373b7910ebeed4a50 Mon Sep 17 00:00:00 2001 From: mgiota Date: Fri, 18 Mar 2022 22:17:55 +0100 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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 7ddd80522f37c2a6ced03a0f3ca4e77e45dc5931 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 23 Mar 2022 09:47:14 +0100 Subject: [PATCH 15/18] do not show noData screen when filters are applied and don't match any results --- .../plugins/observability/public/hooks/use_fetch_rules.ts | 7 +++++++ x-pack/plugins/observability/public/pages/rules/index.tsx | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) 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..ce2aee0b330d0 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts @@ -6,6 +6,7 @@ */ import { useEffect, useState, useCallback } from 'react'; +import { isEmpty } from 'lodash'; import { loadRules, Rule } from '../../../triggers_actions_ui/public'; import { RULES_LOAD_ERROR } from '../pages/rules/translations'; import { FetchRulesProps } from '../pages/rules/types'; @@ -29,6 +30,8 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } totalItemCount: 0, }); + const [noData, setNoData] = useState(true); + const fetchRules = useCallback(async () => { setRulesState((oldState) => ({ ...oldState, isLoading: true })); @@ -47,6 +50,9 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } data: response.data, totalItemCount: response.total, })); + const isFilterApplied = !(isEmpty(searchText) && isEmpty(ruleLastResponseFilter)); + + setNoData(response.data.length === 0 && !isFilterApplied); } catch (_e) { setRulesState((oldState) => ({ ...oldState, isLoading: false, error: RULES_LOAD_ERROR })); } @@ -59,5 +65,6 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } rulesState, reload: fetchRules, setRulesState, + noData, }; } diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index d5897294289e8..7e4b273d4b96c 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -112,7 +112,7 @@ export function RulesPage() { setRefreshInterval(refreshIntervalChanged); }; - const { rulesState, setRulesState, reload } = useFetchRules({ + const { rulesState, setRulesState, reload, noData } = useFetchRules({ searchText, ruleLastResponseFilter, page, @@ -247,7 +247,7 @@ export function RulesPage() { ); const getRulesTable = () => { - if (totalItemCount === 0 && !rulesState.isLoading) { + if (noData && !rulesState.isLoading) { return authorizedToCreateAnyRules ? ( Date: Wed, 23 Mar 2022 10:02:29 +0100 Subject: [PATCH 16/18] add centered spinner on initial load --- .../public/hooks/use_fetch_rules.ts | 3 +++ .../components/center_justified_spinner.tsx | 25 +++++++++++++++++++ .../public/pages/rules/index.tsx | 6 ++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/observability/public/pages/rules/components/center_justified_spinner.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 ce2aee0b330d0..b49efedc0f06c 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts @@ -31,6 +31,7 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } }); const [noData, setNoData] = useState(true); + const [initialLoad, setInitialLoad] = useState(true); const fetchRules = useCallback(async () => { setRulesState((oldState) => ({ ...oldState, isLoading: true })); @@ -56,6 +57,7 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } } catch (_e) { setRulesState((oldState) => ({ ...oldState, isLoading: false, error: RULES_LOAD_ERROR })); } + setInitialLoad(false); }, [http, page, searchText, ruleLastResponseFilter, sort]); useEffect(() => { fetchRules(); @@ -66,5 +68,6 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } reload: fetchRules, setRulesState, noData, + initialLoad, }; } diff --git a/x-pack/plugins/observability/public/pages/rules/components/center_justified_spinner.tsx b/x-pack/plugins/observability/public/pages/rules/components/center_justified_spinner.tsx new file mode 100644 index 0000000000000..867d530eb4e2f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/rules/components/center_justified_spinner.tsx @@ -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 from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; + +interface Props { + size?: EuiLoadingSpinnerSize; +} + +export function CenterJustifiedSpinner({ size }: Props) { + return ( + + + + + + ); +} diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index 7e4b273d4b96c..17f3b31fdc0d7 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -34,6 +34,7 @@ import { EditRuleFlyout } from './components/edit_rule_flyout'; import { DeleteModalConfirmation } from './components/delete_modal_confirmation'; import { NoDataPrompt } from './components/prompts/no_data_prompt'; import { NoPermissionPrompt } from './components/prompts/no_permission_prompt'; +import { CenterJustifiedSpinner } from './components/center_justified_spinner'; import { deleteRules, RuleTableItem, @@ -112,7 +113,7 @@ export function RulesPage() { setRefreshInterval(refreshIntervalChanged); }; - const { rulesState, setRulesState, reload, noData } = useFetchRules({ + const { rulesState, setRulesState, reload, noData, initialLoad } = useFetchRules({ searchText, ruleLastResponseFilter, page, @@ -257,6 +258,9 @@ export function RulesPage() { ); } + if (initialLoad) { + return ; + } return ( <> From b98f94759654545a0a019308ce444dc5810e8859 Mon Sep 17 00:00:00 2001 From: mgiota Date: Wed, 23 Mar 2022 10:50:14 +0100 Subject: [PATCH 17/18] move currrent functionality from triggers_actions_ui related to pagination to the use_fetch_rules hook --- .../observability/public/hooks/use_fetch_rules.ts | 14 ++++++++++++-- .../observability/public/pages/rules/index.tsx | 1 + .../observability/public/pages/rules/types.ts | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) 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 b49efedc0f06c..53b2f68821710 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_rules.ts @@ -20,7 +20,13 @@ interface RuleState { totalItemCount: number; } -export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort }: FetchRulesProps) { +export function useFetchRules({ + searchText, + ruleLastResponseFilter, + setPage, + page, + sort, +}: FetchRulesProps) { const { http } = useKibana().services; const [rulesState, setRulesState] = useState({ @@ -51,6 +57,10 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } data: response.data, totalItemCount: response.total, })); + + if (!response.data?.length && page.index > 0) { + setPage({ ...page, index: 0 }); + } const isFilterApplied = !(isEmpty(searchText) && isEmpty(ruleLastResponseFilter)); setNoData(response.data.length === 0 && !isFilterApplied); @@ -58,7 +68,7 @@ export function useFetchRules({ searchText, ruleLastResponseFilter, page, sort } setRulesState((oldState) => ({ ...oldState, isLoading: false, error: RULES_LOAD_ERROR })); } setInitialLoad(false); - }, [http, page, searchText, ruleLastResponseFilter, sort]); + }, [http, page, setPage, searchText, ruleLastResponseFilter, sort]); useEffect(() => { fetchRules(); }, [fetchRules]); diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index 17f3b31fdc0d7..a79f017007944 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -117,6 +117,7 @@ export function RulesPage() { searchText, ruleLastResponseFilter, page, + setPage, sort, }); const { data: rules, totalItemCount, error } = rulesState; diff --git a/x-pack/plugins/observability/public/pages/rules/types.ts b/x-pack/plugins/observability/public/pages/rules/types.ts index 8d6012cb9933f..1a15cf3d9cef2 100644 --- a/x-pack/plugins/observability/public/pages/rules/types.ts +++ b/x-pack/plugins/observability/public/pages/rules/types.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { Dispatch, SetStateAction } from 'react'; import { EuiTableSortingType, EuiBasicTableColumn } from '@elastic/eui'; import { AlertExecutionStatus } from '../../../../alerting/common'; import { RuleTableItem, Rule } from '../../../../triggers_actions_ui/public'; @@ -69,6 +70,7 @@ export interface FetchRulesProps { searchText: string | undefined; ruleLastResponseFilter: string[]; page: Pagination; + setPage: Dispatch>; sort: EuiTableSortingType['sort']; } From d3e91629d64780cbf58cb3f9544430f750fdae02 Mon Sep 17 00:00:00 2001 From: mgiota Date: Thu, 24 Mar 2022 00:40:04 +0100 Subject: [PATCH 18/18] disable status column if license is not enabled --- x-pack/plugins/observability/public/pages/rules/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/rules/index.tsx b/x-pack/plugins/observability/public/pages/rules/index.tsx index a79f017007944..21664ca63507d 100644 --- a/x-pack/plugins/observability/public/pages/rules/index.tsx +++ b/x-pack/plugins/observability/public/pages/rules/index.tsx @@ -180,7 +180,7 @@ export function RulesPage() { render: (_enabled: boolean, item: RuleTableItem) => { return ( reload()} enableRule={async () => await enableRule({ http, id: item.id })}