From fe16680c6d7c0efd70861ddba11980518900b6cf Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 23 May 2022 16:48:17 +0100 Subject: [PATCH] Notification history: Use `Card` instead of reusing alert (#49418) * Use Card instead of reusing alert * only need clearSelectedNotifications now --- .../StoredNotificationItem.tsx | 98 ++++++------------- .../notifications/StoredNotifications.tsx | 73 ++++++-------- 2 files changed, 57 insertions(+), 114 deletions(-) diff --git a/public/app/core/components/AppNotifications/StoredNotificationItem.tsx b/public/app/core/components/AppNotifications/StoredNotificationItem.tsx index 034388c850a..4817cdab242 100644 --- a/public/app/core/components/AppNotifications/StoredNotificationItem.tsx +++ b/public/app/core/components/AppNotifications/StoredNotificationItem.tsx @@ -1,105 +1,63 @@ -import { css, cx } from '@emotion/css'; +import { css } from '@emotion/css'; import { formatDistanceToNow } from 'date-fns'; import React, { ReactNode } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; -import { Icon, IconName, useTheme2 } from '@grafana/ui'; -import { getIconFromSeverity } from '@grafana/ui/src/components/Alert/Alert'; +import { Card, Checkbox, useTheme2 } from '@grafana/ui'; export type AlertVariant = 'success' | 'warning' | 'error' | 'info'; export interface Props { + children?: ReactNode; className?: string; - title: string; + isSelected: boolean; + onClick: () => void; severity?: AlertVariant; + title: string; timestamp?: number; traceId?: string; - children?: ReactNode; } export const StoredNotificationItem = ({ + children, className, - title, + isSelected, + onClick, severity = 'error', + title, traceId, timestamp, - children, }: Props) => { const theme = useTheme2(); - const styles = getStyles(theme, severity); + const styles = getStyles(theme); const showTraceId = config.featureToggles.tracing && traceId; return ( -
-
- -
-
{title}
-
{children}
- {showTraceId && `Trace ID: ${traceId}`} - {timestamp && {formatDistanceToNow(timestamp, { addSuffix: true })}} -
+ + {title} + {children} + + + + + {showTraceId && {`Trace ID: ${traceId}`}} + {timestamp && formatDistanceToNow(timestamp, { addSuffix: true })} + + ); }; -const getStyles = (theme: GrafanaTheme2, severity: AlertVariant) => { - const color = theme.colors[severity]; - const borderRadius = theme.shape.borderRadius(); - +const getStyles = (theme: GrafanaTheme2) => { return { - wrapper: css({ - display: 'grid', - gridTemplateColumns: 'auto 1fr auto', - gridTemplateRows: 'auto 1fr auto', - gridTemplateAreas: ` - 'icon title close' - 'icon body body' - 'icon trace timestamp'`, - gap: `0 ${theme.spacing(2)}`, - background: theme.colors.background.secondary, - borderRadius: borderRadius, - }), - icon: css({ - gridArea: 'icon', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - padding: theme.spacing(2, 3), - background: color.main, - color: color.contrastText, - borderRadius: `${borderRadius} 0 0 ${borderRadius}`, - }), - title: css({ - gridArea: 'title', - alignSelf: 'center', - fontWeight: theme.typography.fontWeightMedium, - color: theme.colors.text.primary, - paddingTop: theme.spacing(1), - }), - body: css({ - gridArea: 'body', - maxHeight: '50vh', - marginRight: theme.spacing(1), - overflowY: 'auto', - overflowWrap: 'break-word', - wordBreak: 'break-word', - color: theme.colors.text.secondary, - }), trace: css({ - gridArea: 'trace', - justifySelf: 'start', - alignSelf: 'end', - paddingBottom: theme.spacing(1), - fontSize: theme.typography.pxToRem(10), + alignItems: 'flex-end', + alignSelf: 'flex-end', color: theme.colors.text.secondary, - }), - timestamp: css({ - gridArea: 'timestamp', - alignSelf: 'end', - padding: theme.spacing(1), + display: 'flex', + flexDirection: 'column', fontSize: theme.typography.pxToRem(10), - color: theme.colors.text.secondary, + justifySelf: 'flex-end', }), }; }; diff --git a/public/app/features/notifications/StoredNotifications.tsx b/public/app/features/notifications/StoredNotifications.tsx index b434e47a70d..acaa25910c5 100644 --- a/public/app/features/notifications/StoredNotifications.tsx +++ b/public/app/features/notifications/StoredNotifications.tsx @@ -3,7 +3,7 @@ import React, { useRef, useState } from 'react'; import { useEffectOnce } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; -import { Button, Checkbox, Icon, useStyles2 } from '@grafana/ui'; +import { Alert, Button, Checkbox, Icon, useStyles2 } from '@grafana/ui'; import { StoredNotificationItem } from 'app/core/components/AppNotifications/StoredNotificationItem'; import { clearAllNotifications, @@ -18,6 +18,9 @@ export function StoredNotifications() { const dispatch = useDispatch(); const notifications = useSelector((state) => selectWarningsAndErrors(state.appNotifications)); const [selectedNotificationIds, setSelectedNotificationIds] = useState([]); + const allNotificationsSelected = notifications.every((notification) => + selectedNotificationIds.includes(notification.id) + ); const lastReadTimestamp = useRef(useSelector((state) => selectLastReadTimestamp(state.appNotifications))); const styles = useStyles2(getStyles); @@ -26,19 +29,23 @@ export function StoredNotifications() { }); const clearSelectedNotifications = () => { - selectedNotificationIds.forEach((id) => { - dispatch(clearNotification(id)); - }); + if (allNotificationsSelected) { + dispatch(clearAllNotifications()); + } else { + selectedNotificationIds.forEach((id) => { + dispatch(clearNotification(id)); + }); + } setSelectedNotificationIds([]); }; - const clearAllNotifs = () => { - dispatch(clearAllNotifications()); + const handleAllCheckboxToggle = (isChecked: boolean) => { + setSelectedNotificationIds(isChecked ? notifications.map((n) => n.id) : []); }; - const handleCheckboxToggle = (id: string, isChecked: boolean) => { + const handleCheckboxToggle = (id: string) => { setSelectedNotificationIds((prevState) => { - if (isChecked && !prevState.includes(id)) { + if (!prevState.includes(id)) { return [...prevState, id]; } else { return prevState.filter((notificationId) => notificationId !== id); @@ -57,27 +64,26 @@ export function StoredNotifications() { return (
- This page displays all past errors and warnings. Once dismissed, they cannot be retrieved. +
-
    {notifications.map((notif) => (
  • - ) => - handleCheckboxToggle(notif.id, event.target.checked) - } - /> lastReadTimestamp.current })} + className={cx({ [styles.newItem]: notif.timestamp > lastReadTimestamp.current })} + isSelected={selectedNotificationIds.includes(notif.id)} + onClick={() => handleCheckboxToggle(notif.id)} severity={notif.severity} title={notif.title} timestamp={notif.timestamp} @@ -97,25 +103,11 @@ function getStyles(theme: GrafanaTheme2) { topRow: css({ alignItems: 'center', display: 'flex', - justifyContent: 'flex-end', - }), - smallText: css({ - fontSize: theme.typography.pxToRem(10), - color: theme.colors.text.secondary, - }), - side: css({ - display: 'flex', - flexDirection: 'column', - padding: '3px 6px', - paddingTop: theme.spacing(1), - alignItems: 'flex-end', - justifyContent: 'space-between', - flexShrink: 0, + gap: theme.spacing(2), }), list: css({ display: 'flex', flexDirection: 'column', - gap: theme.spacing(1), }), listItem: css({ alignItems: 'center', @@ -142,17 +134,10 @@ function getStyles(theme: GrafanaTheme2) { alignItems: 'center', gap: theme.spacing(1), }), - notification: css({ - flex: 1, - position: 'relative', - }), wrapper: css({ display: 'flex', flexDirection: 'column', gap: theme.spacing(2), }), - clearAll: css({ - alignSelf: 'flex-end', - }), }; }