Skip to content

Commit

Permalink
fix: notifications panel
Browse files Browse the repository at this point in the history
Design fixes.
Display notification errors.
Remove `useNotifications` hook from docs and public API for now.

risk: low
JIRA: F1-1020
  • Loading branch information
kandl committed Jan 10, 2025
1 parent 3deb13b commit 0888ae2
Show file tree
Hide file tree
Showing 22 changed files with 193 additions and 88 deletions.
14 changes: 0 additions & 14 deletions docs/content/en/latest/references/notifications_panel/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,17 +253,3 @@ Component for rendering loading placeholder items.
| Name | Type | Description |
| ---------- | ------ | ------------------------------------- |
| itemHeight | number | Height of the skeleton item in pixels |

## Custom Implementation

If you find that the provided components and customizations are not sufficient for your use case, you can use the `useNotifications` hook to implement your own notification UI.
The hook provides methods for fetching unread/all notifications, refreshing notifications, marking them as read, and more.

#### Props

| Name | Type | Default | Description |
| -------------------------- | ------------------ | ------- | ------------------------------------------------------------------------ |
| backend | IAnalyticalBackend | - | Backend instance. Falls back to BackendProvider context if not specified |
| workspace | string | - | Workspace ID. Falls back to WorkspaceProvider context if not specified |
| refreshInterval (required) | number | - | Time in milliseconds between notification refreshes. Set to 0 to disable |
| itemsPerPage (required) | number | - | Number of notifications loaded in each batch |
27 changes: 0 additions & 27 deletions libs/sdk-ui-ext/api/sdk-ui-ext.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,14 +622,6 @@ export type ISizeInfoDefault = ISizeInfo & {
default: number;
};

// @public
export interface IUseNotificationsProps {
backend?: IAnalyticalBackend;
itemsPerPage: number;
refreshInterval: number;
workspace?: string;
}

// @internal (undocumented)
export interface IUserEditDialogProps extends IWithTelemetryProps {
// (undocumented)
Expand Down Expand Up @@ -751,25 +743,6 @@ export type TelemetryEvent = "multiple-users-deleted" | "multiple-groups-deleted
// @internal (undocumented)
export type TrackEventCallback = (event: TelemetryEvent) => void;

// @public (undocumented)
export function useNotifications({ workspace, refreshInterval, itemsPerPage }: IUseNotificationsProps): {
notifications: INotification[];
notificationsStatus: "error" | "loading" | "pending" | "success";
notificationsError: GoodDataSdkError;
notificationsHasNextPage: boolean;
notificationsLoadNextPage: () => void;
notificationsReset: () => void;
unreadNotifications: INotification[];
unreadNotificationsStatus: "error" | "loading" | "pending" | "success";
unreadNotificationsError: GoodDataSdkError;
unreadNotificationsHasNextPage: boolean;
unreadNotificationsLoadNextPage: () => void;
unreadNotificationsReset: () => void;
unreadNotificationsCount: number;
markNotificationAsRead: (notificationId: string) => Promise<void>;
markAllNotificationsAsRead: () => Promise<void>;
};

// @internal (undocumented)
export const UserEditDialog: React_2.FC<IUserEditDialogProps>;

Expand Down
2 changes: 0 additions & 2 deletions libs/sdk-ui-ext/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ export * from "./internal/components/attributeHierarchies/index.js";

export * from "./internal/components/pluggableVisualizations/alerts.js";

export { useNotifications } from "./notificationsPanel/data/useNotifications.js";
export type { IUseNotificationsProps } from "./notificationsPanel/data/useNotifications.js";
export { NotificationsPanel } from "./notificationsPanel/NotificationsPanel/NotificationsPanel.js";
export type {
INotificationsPanelProps,
Expand Down
15 changes: 15 additions & 0 deletions libs/sdk-ui-ext/src/internal/translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -2087,5 +2087,20 @@
"value": "Triggered for",
"comment": "",
"limit": 0
},
"notifications.panel.error.learnMore": {
"value": "Learn more",
"comment": "",
"limit": 0
},
"notifications.panel.error.traceId": {
"value": "Trace ID",
"comment": "",
"limit": 0
},
"notifications.panel.error.message": {
"value": "The alert could not be processed.",
"comment": "",
"limit": 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { IAlertDescription, IAlertNotification, INotification } from "@gooddata/
import { getDateTimeConfig, IDateConfig, UiIcon } from "@gooddata/sdk-ui-kit";
import { bem } from "../bem.js";
import { Tooltip } from "../components/Tooltip.js";
import { Popup } from "../components/Popup.js";
// import { NotificationFiltersDetail } from "../NotificationFiltersDetail/NotificationFiltersDetail.js";
import { NotificationTriggerDetail } from "../NotificationTriggersDetail/NotificationTriggersDetail.js";
import { defineMessages, FormattedDate, FormattedMessage, FormattedTime, useIntl } from "react-intl";
Expand Down Expand Up @@ -39,7 +40,9 @@ export function AlertNotification({
const target = event.target;
const targetIsElement = target instanceof Element;
const isNotificationsDetailsLink =
targetIsElement && target.closest(`[data-id="notification-detail"]`);
targetIsElement &&
(target.closest(`[data-id="notification-detail"]`) ||
target.closest(`[data-id="notification-error"]`));
if (isNotificationsDetailsLink) {
return;
}
Expand All @@ -53,6 +56,11 @@ export function AlertNotification({
// const isSliced = notification.details.data.alert.attribute;
// const showSeparator = filterCount && filterCount > 0 && isSliced;
const notificationTitle = getNotificationTitle(notification);
const isSliced = notification.details.data.alert.attribute;
const hasTriggers = notification.details.data.alert.totalValueCount > 0;
const isError = notification.details.data.alert.status !== "SUCCESS";
const errorMessage = notification.details.data.alert.errorMessage;
const traceId = notification.details.data.alert.traceId;

return (
<div className={b({ isRead: notification.isRead })} onClick={clickNotification}>
Expand All @@ -64,11 +72,44 @@ export function AlertNotification({
<div className={e("title", { isRead: notification.isRead })} title={notificationTitle}>
{notificationTitle}
</div>
<div className={e("links")}>
{/* <NotificationFiltersDetail notification={notification} />
{showSeparator ? "・" : null} */}
<NotificationTriggerDetail notification={notification} />
</div>
{isError ? (
<div className={e("error")}>
<div className={e("error-icon")}>
<UiIcon type="crossCircle" size={12} color="error" />
</div>
<div>
<FormattedMessage id="notifications.panel.error.message" />{" "}
<Popup
popup={
<div className={e("error-popup")}>
{errorMessage}
<br />
<FormattedMessage id="notifications.panel.error.traceId" />: {traceId}
</div>
}
>
{({ toggle, id }) => (
<u
data-id="notification-error"
id={id}
onClick={() => {
toggle();
}}
>
<FormattedMessage id="notifications.panel.error.learnMore" />
</u>
)}
</Popup>
</div>
</div>
) : null}
{!isError && isSliced && hasTriggers ? (
<div className={e("links")}>
{/* <NotificationFiltersDetail notification={notification} />
{showSeparator ? "・" : null} */}
<NotificationTriggerDetail notification={notification} />
</div>
) : null}
</div>
<div className={e("time")}>
<NotificationTime config={getDateTimeConfig(notification.createdAt)} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// (C) 2024 GoodData Corporation
// (C) 2024-2025 GoodData Corporation

.gd-ui-ext-notification {
$root: &;
Expand Down Expand Up @@ -72,20 +72,19 @@
position: absolute;
top: 0;
right: 0;
border: 1.4px solid var(--gd-palette-complementary-0);
border: 1.4px solid var(--gd-palette-complementary-2);
box-sizing: content-box;
}

&__details {
display: flex;
flex-direction: column;
justify-content: space-between;
justify-content: center;
flex-shrink: 1;

width: 100%;
height: 100%;

overflow: hidden;
min-width: 0;
}

&__title {
Expand Down Expand Up @@ -130,5 +129,29 @@
display: flex;
flex-direction: row;
align-items: center;
height: 16px;
}

&__error {
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
height: 16px;
font-size: 12px;
font-weight: 400;
color: var(--gd-palette-error-base);
white-space: nowrap;
position: relative;
}

&__error-icon {
width: 12px;
height: 12px;
}

&__error-popup {
font-size: 12px;
line-height: 18px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
background-color: var(--gd-palette-complementary-0);

box-shadow: 0 2px 10px 0 var(--gd-shadow-color);
border-radius: var(--gd-modal-borderRadius, 3px);

display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React from "react";
import cx from "classnames";
import { UiIcon } from "@gooddata/sdk-ui-kit";
import { bem } from "../bem.js";
import { useIsDarkTheme } from "@gooddata/sdk-ui-theme-provider";

const { b, e } = bem("gd-ui-ext-notifications-panel-button");

Expand Down Expand Up @@ -52,6 +53,7 @@ export function DefaultNotificationsPanelButton({
toggleNotificationPanel,
hasUnreadNotifications,
}: INotificationsPanelButtonComponentProps) {
const isDarkTheme = useIsDarkTheme();
return (
<button
ref={buttonRef}
Expand All @@ -60,7 +62,7 @@ export function DefaultNotificationsPanelButton({
>
<span className={e("icon")}>
{hasUnreadNotifications ? <span className={e("unread-status")} /> : null}
<UiIcon type="alert" size={14} color="complementary-0" />
<UiIcon type="alert" size={14} color={isDarkTheme ? "complementary-9" : "complementary-0"} />
</span>
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,9 @@ function NotificationsPanelController({

const handleNotificationClick = useCallback(
(notification: INotification) => {
markNotificationAsRead(notification.id);
if (!notification.isRead) {
markNotificationAsRead(notification.id);
}
closeNotificationsPanel();
onNotificationClick?.(notification);
},
Expand Down
36 changes: 36 additions & 0 deletions libs/sdk-ui-ext/src/notificationsPanel/components/Popup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// (C) 2024-2025 GoodData Corporation
import React, { useRef, useState, useCallback } from "react";
import { Bubble } from "@gooddata/sdk-ui-kit";
import { v4 as uuidv4 } from "uuid";

const ALIGN_POINTS = [{ align: "bc tr" }, { align: "tc br" }];

export function Popup({
children,
popup,
}: {
children: ({ toggle, id }: { toggle: () => void; id: string }) => React.ReactNode;
popup: React.ReactNode;
}) {
const [isOpen, setIsOpen] = useState(false);
const { current: id } = useRef(`popup-${uuidv4()}`);
const toggle = useCallback(() => setIsOpen((x) => !x), []);
const close = useCallback(() => setIsOpen(false), []);

return (
<>
{children({ toggle, id })}
{isOpen ? (
<Bubble
alignPoints={ALIGN_POINTS}
alignTo={`#${id}`}
closeOnOutsideClick
closeOnParentScroll
onClose={close}
>
{popup}
</Bubble>
) : null}
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useFetchNotifications } from "./useFetchNotifications.js";
/**
* Hook for fetching all and unread notifications.
*
* @public
* @internal
*/
export interface IUseNotificationsProps {
/**
Expand Down Expand Up @@ -38,7 +38,7 @@ export interface IUseNotificationsProps {
}

/**
* @public
* @internal
*/
export function useNotifications({ workspace, refreshInterval, itemsPerPage }: IUseNotificationsProps) {
const effectiveWorkspace = useWorkspace(workspace);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// (C) 2024-2025 GoodData Corporation
@use "@gooddata/sdk-ui-kit/styles/scss/bubble.scss";
@use "@gooddata/sdk-ui-kit/styles/scss/overlay.scss";
@use "@gooddata/sdk-ui-kit/styles/scss/tabs.scss";
@use "@gooddata/sdk-ui-kit/src/@ui/UiIcon/UiIcon.scss";
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-ui-kit/api/sdk-ui-kit.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,7 @@ export interface IConfirmDialogBaseProps extends IDialogBaseProps {
}

// @internal (undocumented)
export type IconType = "check" | "plus" | "sync" | "alert" | "close" | "question";
export type IconType = "check" | "plus" | "sync" | "alert" | "close" | "question" | "crossCircle";

// @internal (undocumented)
export interface ICustomizableCheckmarkProps {
Expand Down
2 changes: 1 addition & 1 deletion libs/sdk-ui-kit/src/@ui/@types/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
/**
* @internal
*/
export type IconType = "check" | "plus" | "sync" | "alert" | "close" | "question";
export type IconType = "check" | "plus" | "sync" | "alert" | "close" | "question" | "crossCircle";
9 changes: 9 additions & 0 deletions libs/sdk-ui-kit/src/@ui/UiIcon/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,13 @@ export const iconsConfig: Record<IconType, IIconConfig> = {
),
viewBox: "0 0 20 20",
},
crossCircle: {
content: (
<path
d="M8.60156 8.07617L6.15234 5.90234L8.60156 3.72266C8.66016 3.66797 8.69141 3.59961 8.69531 3.51758C8.70312 3.43555 8.67969 3.36328 8.625 3.30078C8.57031 3.23828 8.5 3.20508 8.41406 3.20117C8.33203 3.19727 8.26172 3.22266 8.20312 3.27734L5.70117 5.49805L3.19922 3.27734C3.13672 3.22266 3.06445 3.19727 2.98242 3.20117C2.90039 3.20508 2.83203 3.23828 2.77734 3.30078C2.72266 3.36328 2.69727 3.43555 2.70117 3.51758C2.70508 3.59961 2.73828 3.66797 2.80078 3.72266L5.25 5.90234L2.80078 8.07617C2.73828 8.13086 2.70508 8.19922 2.70117 8.28125C2.69727 8.36328 2.72266 8.43555 2.77734 8.49805C2.80469 8.5332 2.83789 8.55859 2.87695 8.57422C2.91602 8.58984 2.95703 8.59766 3 8.59766C3.03516 8.59766 3.07031 8.5918 3.10547 8.58008C3.14062 8.56836 3.17188 8.54883 3.19922 8.52148L5.70117 6.30078L8.20312 8.52148C8.23047 8.54883 8.26172 8.56836 8.29688 8.58008C8.33203 8.5918 8.36719 8.59766 8.40234 8.59766C8.44141 8.59766 8.48047 8.58984 8.51953 8.57422C8.55859 8.55859 8.59375 8.5332 8.625 8.49805C8.67969 8.43555 8.70312 8.36328 8.69531 8.28125C8.69141 8.19922 8.66016 8.13086 8.60156 8.07617ZM5.70117 11.5977C5.31836 11.5977 4.94336 11.5605 4.57617 11.4863C4.21289 11.416 3.85938 11.3105 3.51562 11.1699C3.17578 11.0293 2.84961 10.8555 2.53711 10.6484C2.22852 10.4375 1.93945 10.1973 1.66992 9.92773C1.40039 9.66211 1.16211 9.37305 0.955078 9.06055C0.748047 8.74805 0.572266 8.42188 0.427734 8.08203C0.287109 7.74219 0.179688 7.38867 0.105469 7.02148C0.0351562 6.6543 0 6.28125 0 5.90234C0 5.51953 0.0351562 5.14648 0.105469 4.7832C0.179688 4.41602 0.287109 4.0625 0.427734 3.72266C0.572266 3.37891 0.748047 3.05273 0.955078 2.74414C1.16211 2.43164 1.40039 2.14062 1.66992 1.87109C1.93945 1.60156 2.22852 1.36328 2.53711 1.15625C2.84961 0.949219 3.17578 0.775391 3.51562 0.634766C3.85938 0.490234 4.21289 0.382812 4.57617 0.3125C4.94336 0.238281 5.31836 0.201172 5.70117 0.201172C6.08008 0.201172 6.45312 0.238281 6.82031 0.3125C7.1875 0.382812 7.54102 0.490234 7.88086 0.634766C8.22461 0.775391 8.55078 0.949219 8.85938 1.15625C9.17188 1.36328 9.46289 1.60156 9.73242 1.87109C10.002 2.14062 10.2402 2.43164 10.4473 2.74414C10.6543 3.05273 10.8281 3.37891 10.9688 3.72266C11.1094 4.0625 11.2168 4.41602 11.291 4.7832C11.3652 5.14648 11.4023 5.51953 11.4023 5.90234C11.4023 6.28125 11.3652 6.6543 11.291 7.02148C11.2168 7.38867 11.1094 7.74219 10.9688 8.08203C10.8281 8.42188 10.6543 8.74805 10.4473 9.06055C10.2402 9.37305 10.002 9.66211 9.73242 9.92773C9.46289 10.1973 9.17188 10.4375 8.85938 10.6484C8.55078 10.8555 8.22461 11.0293 7.88086 11.1699C7.54102 11.3105 7.1875 11.416 6.82031 11.4863C6.45312 11.5605 6.08008 11.5977 5.70117 11.5977ZM5.70117 0.798828C4.99805 0.798828 4.33594 0.933594 3.71484 1.20312C3.09766 1.46875 2.55664 1.83398 2.0918 2.29883C1.63086 2.75977 1.26562 3.30078 0.996094 3.92188C0.730469 4.53906 0.597656 5.19922 0.597656 5.90234C0.597656 6.60547 0.730469 7.26758 0.996094 7.88867C1.26562 8.50586 1.63086 9.04492 2.0918 9.50586C2.55664 9.9668 3.09766 10.332 3.71484 10.6016C4.33594 10.8672 4.99805 11 5.70117 11C6.4043 11 7.06445 10.8672 7.68164 10.6016C8.30273 10.332 8.84375 9.9668 9.30469 9.50586C9.76562 9.04492 10.1289 8.50586 10.3945 7.88867C10.6641 7.26758 10.7988 6.60547 10.7988 5.90234C10.7988 5.19922 10.6641 4.53906 10.3945 3.92188C10.1289 3.30078 9.76562 2.75977 9.30469 2.29883C8.84375 1.83398 8.30273 1.46875 7.68164 1.20312C7.06445 0.933594 6.4043 0.798828 5.70117 0.798828Z"
className={e("fill")}
/>
),
viewBox: "0 0 12 12",
},
};
3 changes: 3 additions & 0 deletions libs/sdk-ui-theme-provider/api/sdk-ui-theme-provider.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export const ThemeProvider: React_2.FC<IThemeProviderProps>;
// @public (undocumented)
export type ThemeStatus = "pending" | "loading" | "success";

// @public
export const useIsDarkTheme: () => boolean;

// @public
export const useTheme: (theme?: ITheme) => ITheme | undefined;

Expand Down
Loading

0 comments on commit 0888ae2

Please sign in to comment.