Skip to content

Commit

Permalink
fix: make the notifications icon active in dynamic plugin (janus-idp#…
Browse files Browse the repository at this point in the history
…1111)

FLPATH-905: make the notifications icon active in dynamic plugin
  • Loading branch information
mareklibra authored Jan 24, 2024
1 parent 24d477c commit bc98491
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 145 deletions.
6 changes: 3 additions & 3 deletions plugins/notifications/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
...
{/* New code: */}
<SidebarDivider />
<NotificationsSidebarItem pollingInterval={5000} />
<SidebarItem icon={NotificationsActiveIcon} to="notifications" text="Notifications" />
{/* Existing code for reference: */}
<SidebarSpace />
Expand All @@ -55,13 +55,13 @@ export const Root = ({ children }: PropsWithChildren<{}>) => (
In the `packages/app/src/App.tsx`:

```
import { NOTIFICATIONS_ROUTE, NotificationsPage } from '@janus-idp/plugin-notifications';
import { NotificationsPage } from '@janus-idp/plugin-notifications';
...
export const AppBase = () => {
...
{/* New code: */}
<Route path={NOTIFICATIONS_ROUTE} element={<NotificationsPage />} />
<Route path="/notifications" element={<NotificationsPage />} />
```

## How to use the NotificationApi
Expand Down
2 changes: 1 addition & 1 deletion plugins/notifications/app-config.janus-idp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ dynamicPlugins:
appIcons:
- name: notificationsIcon
module: NotificationsPlugin
importName: NotificationsIcon
importName: NotificationsActiveIcon
dynamicRoutes:
- path: /notifications
importName: NotificationsPage
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';

import { configApiRef, useApi } from '@backstage/core-plugin-api';

import { Badge, Tooltip } from '@material-ui/core';
import NotificationsIcon from '@material-ui/icons/Notifications';
import NotificationsOffIcon from '@material-ui/icons/NotificationsOff';

import { notificationsApiRef } from '../../api';
import { DefaultPollingIntervalMs } from '../../constants';
import { Notification } from '../../openapi';
import { usePollingEffect } from '../usePollingEffect';
import { SystemNotificationAlert } from './SystemNotificationAlert';

const NotificationsErrorIcon = () => (
<Tooltip title="Failed to load notifications">
<NotificationsOffIcon />
</Tooltip>
);

/**
* Dynamic plugins recently do not support passing configuration
* to icons or making the left-side menu item texts active (so far strings only).
*
* This Icon component tries to workaround these limitations but will be subject of
* change as the extension points by dynamic plugins will evolve.
*/
export const NotificationsActiveIcon = () => {
const notificationsApi = useApi(notificationsApiRef);
const configApi = useApi(configApiRef);

let pollingInterval = configApi.getOptionalNumber(
'notifications.pollingIntervalMs',
);
if (pollingInterval === undefined) {
pollingInterval = DefaultPollingIntervalMs;
}

const [error, setError] = React.useState<Error | undefined>(undefined);
const [unreadCount, setUnreadCount] = React.useState(0);
const [pageLoadingTime] = React.useState(new Date(Date.now()));
const [lastSystemWideNotification, setLastSystemWideNotification] =
React.useState<Notification>();
const [closedNotificationId, setClosedNotificationId] =
React.useState<string>();

const pollCallback = React.useCallback(async () => {
try {
setUnreadCount(
await notificationsApi.getNotificationsCount({
read: false,
messageScope: 'user',
}),
);

const data = await notificationsApi.getNotifications({
pageSize: 1,
pageNumber: 1,
createdAfter: pageLoadingTime,
orderBy: 'created',
orderByDirec: 'desc',
messageScope: 'system',
});

setLastSystemWideNotification(data?.[0]);
} catch (e: unknown) {
setError(e as Error);
}
}, [notificationsApi, pageLoadingTime]);

usePollingEffect(pollCallback, [], pollingInterval);

if (!!error) {
return <NotificationsErrorIcon />;
}

if (unreadCount) {
return (
<>
<Badge color="secondary" variant="dot" overlap="circular">
<NotificationsIcon />
</Badge>

{lastSystemWideNotification &&
!lastSystemWideNotification.readByUser &&
closedNotificationId !== lastSystemWideNotification.id && (
<SystemNotificationAlert
message={lastSystemWideNotification.title}
onCloseNotification={() =>
setClosedNotificationId(lastSystemWideNotification.id)
}
/>
)}
</>
);
}

return <NotificationsIcon />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';

import { useRouteRef } from '@backstage/core-plugin-api';

import { IconButton, Link, makeStyles, Snackbar } from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';

import { notificationsRootRouteRef } from '../../routes';

const useStyles = makeStyles(_theme => ({
systemAlertAction: {
marginRight: '1rem',
},
}));

export type SystemNotificationAlertProps = {
message: string;
onCloseNotification: () => void;
};

export const SystemNotificationAlert = ({
message,
onCloseNotification,
}: SystemNotificationAlertProps) => {
const styles = useStyles();
const notificationsRoute = useRouteRef(notificationsRootRouteRef);

return (
<Snackbar
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
open
message={message}
action={
<>
<Link
href={`${notificationsRoute()}/updates`}
className={styles.systemAlertAction}
>
Show
</Link>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={onCloseNotification}
>
<CloseIcon fontSize="small" />
</IconButton>
</>
}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './NotificationsActiveIcon';
128 changes: 0 additions & 128 deletions plugins/notifications/src/components/NotificationsSidebarItem.tsx

This file was deleted.

3 changes: 1 addition & 2 deletions plugins/notifications/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export const NOTIFICATIONS_ROUTE = 'notifications';

export const DebounceDelayMs = 1000;
export const DefaultPollingIntervalMs = 5 * 1000;
6 changes: 2 additions & 4 deletions plugins/notifications/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ export {
export { type Notification } from './openapi';

// selected constants for export
export { NOTIFICATIONS_ROUTE } from './constants';
export { notificationsRootRouteRef } from './routes';

// selected components for export
export { NotificationsSidebarItem } from './components/NotificationsSidebarItem';
export { usePollingEffect } from './components/usePollingEffect';

export { default as NotificationsIcon } from '@material-ui/icons/Notifications';
export { NotificationsActiveIcon } from './components/NotificationsActiveIcon';
6 changes: 3 additions & 3 deletions plugins/notifications/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import {
} from '@backstage/core-plugin-api';

import { NotificationsApiImpl, notificationsApiRef } from './api';
import { rootRouteRef } from './routes';
import { notificationsRootRouteRef } from './routes';

export const notificationsPlugin = createPlugin({
id: 'notifications',
routes: {
root: rootRouteRef,
root: notificationsRootRouteRef,
},
apis: [
createApiFactory({
Expand All @@ -33,6 +33,6 @@ export const NotificationsPage = notificationsPlugin.provide(
name: 'NotificationsPage',
component: () =>
import('./components/NotificationsPage').then(m => m.NotificationsPage),
mountPoint: rootRouteRef,
mountPoint: notificationsRootRouteRef,
}),
);
6 changes: 2 additions & 4 deletions plugins/notifications/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { createRouteRef } from '@backstage/core-plugin-api';

import { NOTIFICATIONS_ROUTE } from './constants';

export const rootRouteRef = createRouteRef({
id: NOTIFICATIONS_ROUTE,
export const notificationsRootRouteRef = createRouteRef({
id: 'notifications',
});

0 comments on commit bc98491

Please sign in to comment.