From 688ba650670aad12e9b116ccb454e1fae26acf8e Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 26 Aug 2021 18:13:05 -0400 Subject: [PATCH 1/2] fix(notifications): style badge 'attention' if unread danger/warn notifications --- src/app/AppLayout/AppLayout.tsx | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index e294710d2..06c485190 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -36,15 +36,17 @@ * SOFTWARE. */ import * as React from 'react'; +import * as _ from 'lodash'; import { ServiceContext } from '@app/Shared/Services/Services'; import { NotificationCenter } from '@app/Notifications/NotificationCenter'; import { IAppRoute, routes } from '@app/routes'; -import { AboutModal, Button, Nav, NavItem, NavList, NotificationBadge, Page, PageHeader, +import { AboutModal, AlertVariant, Button, Nav, NavItem, NavList, NotificationBadge, Page, PageHeader, PageHeaderTools, PageHeaderToolsGroup, PageHeaderToolsItem, PageSidebar, SkipToContent, Text, TextContent, TextList, TextListItem } from '@patternfly/react-core'; import { BellIcon, CogIcon, HelpIcon } from '@patternfly/react-icons'; +import { map } from 'rxjs/operators'; import { matchPath, NavLink, useHistory, useLocation } from 'react-router-dom'; -import { NotificationsContext } from '../Notifications/Notifications'; +import { Notification, NotificationsContext } from '../Notifications/Notifications'; import { AuthModal } from './AuthModal'; import { SslErrorModal } from './SslErrorModal'; @@ -70,6 +72,7 @@ const AppLayout: React.FunctionComponent = ({children}) => { const [isNotificationDrawerExpanded, setNotificationDrawerExpanded] = React.useState(false); const [cryostatVersion, setCryostatVersion] = React.useState('unknown'); const [unreadNotificationsCount, setUnreadNotificationsCount] = React.useState(0); + const [errorNotificationsCount, setErrorNotificationsCount] = React.useState(0); const location = useLocation(); React.useEffect(() => { @@ -89,6 +92,16 @@ const AppLayout: React.FunctionComponent = ({children}) => { return () => sub.unsubscribe(); }, [notificationsContext, notificationsContext.unreadNotifications, unreadNotificationsCount, setUnreadNotificationsCount]); + React.useEffect(() => { + const sub = notificationsContext + .unreadNotifications() + .pipe(map((notifications: Notification[]) => + _.filter(notifications, n => n.variant === AlertVariant.danger || n.variant === AlertVariant.warning) + )) + .subscribe(s => setErrorNotificationsCount(s.length)); + return () => sub.unsubscribe(); + }, [notificationsContext, notificationsContext.unreadNotifications, unreadNotificationsCount, setUnreadNotificationsCount]); + const dismissAuthModal = () => { setShowAuthModal(false); }; @@ -134,7 +147,7 @@ const AppLayout: React.FunctionComponent = ({children}) => { 0 ? 'attention' : unreadNotificationsCount === 0 ? 'read' : 'unread'} onClick={handleNotificationCenterToggle} aria-label='Notifications' > From 547dec1f6e3a49468bf17521045b98faf0550233 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 26 Aug 2021 18:28:55 -0400 Subject: [PATCH 2/2] feat(notifications): display danger/warning notifications as toasts and also store in drawer --- src/app/AppLayout/AppLayout.tsx | 29 ++++++++++++++++++-- src/app/Notifications/NotificationCenter.tsx | 8 ++++-- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index 06c485190..0688aea58 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -40,9 +40,11 @@ import * as _ from 'lodash'; import { ServiceContext } from '@app/Shared/Services/Services'; import { NotificationCenter } from '@app/Notifications/NotificationCenter'; import { IAppRoute, routes } from '@app/routes'; -import { AboutModal, AlertVariant, Button, Nav, NavItem, NavList, NotificationBadge, Page, PageHeader, +import { AboutModal, Alert, AlertGroup, AlertVariant, AlertActionCloseButton, + Button, Nav, NavItem, NavList, NotificationBadge, Page, PageHeader, PageHeaderTools, PageHeaderToolsGroup, PageHeaderToolsItem, PageSidebar, - SkipToContent, Text, TextContent, TextList, TextListItem } from '@patternfly/react-core'; + SkipToContent, Text, TextContent, TextList, TextListItem +} from '@patternfly/react-core'; import { BellIcon, CogIcon, HelpIcon } from '@patternfly/react-icons'; import { map } from 'rxjs/operators'; import { matchPath, NavLink, useHistory, useLocation } from 'react-router-dom'; @@ -71,6 +73,7 @@ const AppLayout: React.FunctionComponent = ({children}) => { const [aboutModalOpen, setAboutModalOpen] = React.useState(false); const [isNotificationDrawerExpanded, setNotificationDrawerExpanded] = React.useState(false); const [cryostatVersion, setCryostatVersion] = React.useState('unknown'); + const [notifications, setNotifications] = React.useState([] as Notification[]); const [unreadNotificationsCount, setUnreadNotificationsCount] = React.useState(0); const [errorNotificationsCount, setErrorNotificationsCount] = React.useState(0); const location = useLocation(); @@ -87,6 +90,11 @@ const AppLayout: React.FunctionComponent = ({children}) => { return () => sub.unsubscribe(); }) + React.useEffect(() => { + const sub = notificationsContext.notifications().subscribe(setNotifications); + return () => sub.unsubscribe(); + }, []); + React.useEffect(() => { const sub = notificationsContext.unreadNotifications().subscribe(s => setUnreadNotificationsCount(s.length)); return () => sub.unsubscribe(); @@ -105,6 +113,9 @@ const AppLayout: React.FunctionComponent = ({children}) => { const dismissAuthModal = () => { setShowAuthModal(false); }; + const handleMarkNotificationRead = React.useCallback(key => { + notificationsContext.setRead(key, true); + }, [notificationsContext]); React.useEffect(() => { const sub = serviceContext.target.sslFailure().subscribe(() => { @@ -260,6 +271,20 @@ const AppLayout: React.FunctionComponent = ({children}) => { ); const NotificationDrawer = React.useMemo(() => (), []); return (<> + + { + notifications + .filter(n => !n.read && (n.variant === AlertVariant.danger || n.variant === AlertVariant.warning)) + .map(( { key, title, message, variant } ) => ( + handleMarkNotificationRead(key)} />} + >{message} + + )) + } +