@backstage/plugin-notifications 0.0.0-nightly-20240207020916

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # @backstage/plugin-notifications
2
+
3
+ ## 0.0.0-nightly-20240207020916
4
+
5
+ ### Patch Changes
6
+
7
+ - fb8fc24: Initial notifications system for backstage
8
+ - Updated dependencies
9
+ - @backstage/core-components@0.0.0-nightly-20240207020916
10
+ - @backstage/plugin-signals-react@0.0.0-nightly-20240207020916
11
+ - @backstage/core-plugin-api@0.0.0-nightly-20240207020916
12
+ - @backstage/theme@0.0.0-nightly-20240207020916
13
+ - @backstage/plugin-notifications-common@0.0.0-nightly-20240207020916
14
+ - @backstage/errors@1.2.3
15
+ - @backstage/types@1.1.1
16
+
17
+ ## 0.0.1-next.0
18
+
19
+ ### Patch Changes
20
+
21
+ - fb8fc24: Initial notifications system for backstage
22
+ - Updated dependencies
23
+ - @backstage/core-components@0.14.0-next.1
24
+ - @backstage/plugin-signals-react@0.0.1-next.2
25
+ - @backstage/core-plugin-api@1.9.0-next.1
26
+ - @backstage/theme@0.5.1-next.0
27
+ - @backstage/plugin-notifications-common@0.0.1-next.0
28
+ - @backstage/errors@1.2.3
29
+ - @backstage/types@1.1.1
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # notifications
2
+
3
+ Welcome to the notifications plugin!
4
+
5
+ _This plugin was created through the Backstage CLI_
6
+
7
+ ## Getting started
8
+
9
+ First, install the `@backstage/plugin-notifications-backend` and `@backstage/plugin-notifications-node` packages.
10
+ See the documentation for installation instructions.
11
+
12
+ To add the notifications main menu, add the following to your `packages/app/src/components/Root/Root.tsx`:
13
+
14
+ ```tsx
15
+ import { NotificationsSidebarItem } from '@backstage/plugin-notifications';
16
+
17
+ <SidebarPage>
18
+ <Sidebar>
19
+ <SidebarGroup>
20
+ // ...
21
+ <NotificationsSidebarItem />
22
+ </SidebarGroup>
23
+ </Sidebar>
24
+ </SidebarPage>;
25
+ ```
26
+
27
+ Also add the route to notifications to `packages/app/src/App.tsx`:
28
+
29
+ ```tsx
30
+ import { NotificationsPage } from '@backstage/plugin-notifications';
31
+
32
+ <FlatRoutes>
33
+ // ...
34
+ <Route path="/notifications" element={<NotificationsPage />} />
35
+ </FlatRoutes>;
36
+ ```
37
+
38
+ ## Real-time notifications
39
+
40
+ To be able to get real-time notifications to the UI without need for the user to refresh the page, you also need to
41
+ add `@backstage/plugin-signals` package to your installation.
@@ -0,0 +1,89 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { ErrorPanel, PageWithHeader, Content } from '@backstage/core-components';
3
+ import { useNotificationsApi, NotificationsTable } from '../index.esm.js';
4
+ import { makeStyles, Grid, Button } from '@material-ui/core';
5
+ import Bookmark from '@material-ui/icons/Bookmark';
6
+ import Check from '@material-ui/icons/Check';
7
+ import Inbox from '@material-ui/icons/Inbox';
8
+ import { useSignal } from '@backstage/plugin-signals-react';
9
+ import '@backstage/core-plugin-api';
10
+ import '@backstage/errors';
11
+ import 'react-use/lib/useAsyncRetry';
12
+ import '@material-ui/icons/Notifications';
13
+ import 'react-router-dom';
14
+ import '@material-ui/core/Checkbox';
15
+ import '@material-ui/icons/Close';
16
+ import 'react-relative-time';
17
+ import '@material-ui/icons/ArrowForward';
18
+
19
+ const useStyles = makeStyles((_theme) => ({
20
+ filterButton: {
21
+ width: "100%",
22
+ justifyContent: "start"
23
+ }
24
+ }));
25
+ const NotificationsPage = () => {
26
+ const [type, setType] = useState("undone");
27
+ const [refresh, setRefresh] = React.useState(false);
28
+ const { error, value, retry } = useNotificationsApi(
29
+ (api) => api.getNotifications({ type }),
30
+ [type]
31
+ );
32
+ useEffect(() => {
33
+ if (refresh) {
34
+ retry();
35
+ setRefresh(false);
36
+ }
37
+ }, [refresh, setRefresh, retry]);
38
+ const { lastSignal } = useSignal("notifications");
39
+ useEffect(() => {
40
+ if (lastSignal && lastSignal.action) {
41
+ setRefresh(true);
42
+ }
43
+ }, [lastSignal]);
44
+ const onUpdate = () => {
45
+ setRefresh(true);
46
+ };
47
+ const styles = useStyles();
48
+ if (error) {
49
+ return /* @__PURE__ */ React.createElement(ErrorPanel, { error: new Error("Failed to load notifications") });
50
+ }
51
+ return /* @__PURE__ */ React.createElement(PageWithHeader, { title: "Notifications", themeId: "tool" }, /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 2 }, /* @__PURE__ */ React.createElement(
52
+ Button,
53
+ {
54
+ className: styles.filterButton,
55
+ startIcon: /* @__PURE__ */ React.createElement(Inbox, null),
56
+ variant: type === "undone" ? "contained" : "text",
57
+ onClick: () => setType("undone")
58
+ },
59
+ "Inbox"
60
+ ), /* @__PURE__ */ React.createElement(
61
+ Button,
62
+ {
63
+ className: styles.filterButton,
64
+ startIcon: /* @__PURE__ */ React.createElement(Check, null),
65
+ variant: type === "done" ? "contained" : "text",
66
+ onClick: () => setType("done")
67
+ },
68
+ "Done"
69
+ ), /* @__PURE__ */ React.createElement(
70
+ Button,
71
+ {
72
+ className: styles.filterButton,
73
+ startIcon: /* @__PURE__ */ React.createElement(Bookmark, null),
74
+ variant: type === "saved" ? "contained" : "text",
75
+ onClick: () => setType("saved")
76
+ },
77
+ "Saved"
78
+ )), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 10 }, /* @__PURE__ */ React.createElement(
79
+ NotificationsTable,
80
+ {
81
+ notifications: value,
82
+ type,
83
+ onUpdate
84
+ }
85
+ )))));
86
+ };
87
+
88
+ export { NotificationsPage };
89
+ //# sourceMappingURL=index-9074aad8.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-9074aad8.esm.js","sources":["../../src/components/NotificationsPage/NotificationsPage.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { useEffect, useState } from 'react';\nimport {\n Content,\n ErrorPanel,\n PageWithHeader,\n} from '@backstage/core-components';\nimport { NotificationsTable } from '../NotificationsTable';\nimport { useNotificationsApi } from '../../hooks';\nimport { Button, Grid, makeStyles } from '@material-ui/core';\nimport Bookmark from '@material-ui/icons/Bookmark';\nimport Check from '@material-ui/icons/Check';\nimport Inbox from '@material-ui/icons/Inbox';\nimport { NotificationType } from '@backstage/plugin-notifications-common';\nimport { useSignal } from '@backstage/plugin-signals-react';\n\nconst useStyles = makeStyles(_theme => ({\n filterButton: {\n width: '100%',\n justifyContent: 'start',\n },\n}));\n\nexport const NotificationsPage = () => {\n const [type, setType] = useState<NotificationType>('undone');\n const [refresh, setRefresh] = React.useState(false);\n\n const { error, value, retry } = useNotificationsApi(\n api => api.getNotifications({ type }),\n [type],\n );\n\n useEffect(() => {\n if (refresh) {\n retry();\n setRefresh(false);\n }\n }, [refresh, setRefresh, retry]);\n\n const { lastSignal } = useSignal('notifications');\n useEffect(() => {\n if (lastSignal && lastSignal.action) {\n setRefresh(true);\n }\n }, [lastSignal]);\n\n const onUpdate = () => {\n setRefresh(true);\n };\n\n const styles = useStyles();\n if (error) {\n return <ErrorPanel error={new Error('Failed to load notifications')} />;\n }\n\n return (\n <PageWithHeader title=\"Notifications\" themeId=\"tool\">\n <Content>\n <Grid container>\n <Grid item xs={2}>\n <Button\n className={styles.filterButton}\n startIcon={<Inbox />}\n variant={type === 'undone' ? 'contained' : 'text'}\n onClick={() => setType('undone')}\n >\n Inbox\n </Button>\n <Button\n className={styles.filterButton}\n startIcon={<Check />}\n variant={type === 'done' ? 'contained' : 'text'}\n onClick={() => setType('done')}\n >\n Done\n </Button>\n <Button\n className={styles.filterButton}\n startIcon={<Bookmark />}\n variant={type === 'saved' ? 'contained' : 'text'}\n onClick={() => setType('saved')}\n >\n Saved\n </Button>\n </Grid>\n <Grid item xs={10}>\n <NotificationsTable\n notifications={value}\n type={type}\n onUpdate={onUpdate}\n />\n </Grid>\n </Grid>\n </Content>\n </PageWithHeader>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /// <reference types="react" />
2
+ import * as React from 'react';
3
+ import React__default from 'react';
4
+ import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
5
+ import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';
6
+ import { NotificationType, Notification as Notification$1, NotificationStatus } from '@backstage/plugin-notifications-common';
7
+
8
+ /** @public */
9
+ declare const notificationsPlugin: _backstage_core_plugin_api.BackstagePlugin<{
10
+ root: _backstage_core_plugin_api.RouteRef<undefined>;
11
+ }, {}>;
12
+ /** @public */
13
+ declare const NotificationsPage: () => React.JSX.Element;
14
+
15
+ /** @public */
16
+ declare const notificationsApiRef: _backstage_core_plugin_api.ApiRef<NotificationsApi>;
17
+ /** @public */
18
+ type GetNotificationsOptions = {
19
+ type?: NotificationType;
20
+ offset?: number;
21
+ limit?: number;
22
+ search?: string;
23
+ };
24
+ /** @public */
25
+ type UpdateNotificationsOptions = {
26
+ ids: string[];
27
+ done?: boolean;
28
+ read?: boolean;
29
+ saved?: boolean;
30
+ };
31
+ /** @public */
32
+ interface NotificationsApi {
33
+ getNotifications(options?: GetNotificationsOptions): Promise<Notification$1[]>;
34
+ getStatus(): Promise<NotificationStatus>;
35
+ updateNotifications(options: UpdateNotificationsOptions): Promise<Notification$1[]>;
36
+ }
37
+
38
+ /** @public */
39
+ declare class NotificationsClient implements NotificationsApi {
40
+ private readonly discoveryApi;
41
+ private readonly fetchApi;
42
+ constructor(options: {
43
+ discoveryApi: DiscoveryApi;
44
+ fetchApi: FetchApi;
45
+ });
46
+ getNotifications(options?: GetNotificationsOptions): Promise<Notification$1[]>;
47
+ getStatus(): Promise<NotificationStatus>;
48
+ updateNotifications(options: UpdateNotificationsOptions): Promise<Notification$1[]>;
49
+ private request;
50
+ }
51
+
52
+ /** @public */
53
+ declare function useNotificationsApi<T>(f: (api: NotificationsApi) => Promise<T>, deps?: any[]): {
54
+ retry: () => void;
55
+ loading: boolean;
56
+ error?: undefined;
57
+ value?: undefined;
58
+ } | {
59
+ retry: () => void;
60
+ loading: false;
61
+ error: Error;
62
+ value?: undefined;
63
+ } | {
64
+ retry: () => void;
65
+ loading: true;
66
+ error?: Error | undefined;
67
+ value?: T | undefined;
68
+ } | {
69
+ retry: () => void;
70
+ loading: false;
71
+ error?: undefined;
72
+ value: T;
73
+ };
74
+
75
+ /** @public */
76
+ declare function useWebNotifications(): {
77
+ sendWebNotification: (options: {
78
+ title: string;
79
+ description: string;
80
+ }) => Notification | null;
81
+ };
82
+
83
+ /** @public */
84
+ declare function useTitleCounter(): {
85
+ setNotificationCount: (newCount: number) => void;
86
+ };
87
+
88
+ /** @public */
89
+ declare const NotificationsSidebarItem: (props?: {
90
+ webNotificationsEnabled?: boolean;
91
+ titleCounterEnabled?: boolean;
92
+ }) => React__default.JSX.Element;
93
+
94
+ /** @public */
95
+ declare const NotificationsTable: (props: {
96
+ onUpdate: () => void;
97
+ type: NotificationType;
98
+ notifications?: Notification$1[];
99
+ }) => React__default.JSX.Element;
100
+
101
+ export { GetNotificationsOptions, NotificationsApi, NotificationsClient, NotificationsPage, NotificationsSidebarItem, NotificationsTable, UpdateNotificationsOptions, notificationsApiRef, notificationsPlugin, useNotificationsApi, useTitleCounter, useWebNotifications };
@@ -0,0 +1,435 @@
1
+ import { createRouteRef, createApiRef, createPlugin, createApiFactory, discoveryApiRef, fetchApiRef, createRoutableExtension, useApi, useRouteRef } from '@backstage/core-plugin-api';
2
+ import { ResponseError } from '@backstage/errors';
3
+ import useAsyncRetry from 'react-use/lib/useAsyncRetry';
4
+ import React, { useState, useEffect, useCallback } from 'react';
5
+ import { SidebarItem } from '@backstage/core-components';
6
+ import NotificationsIcon from '@material-ui/icons/Notifications';
7
+ import { useSignal } from '@backstage/plugin-signals-react';
8
+ import { makeStyles, Table, TableHead, TableRow, TableCell, Button, TableBody, Typography, Box, Tooltip, IconButton } from '@material-ui/core';
9
+ import { useNavigate } from 'react-router-dom';
10
+ import Checkbox from '@material-ui/core/Checkbox';
11
+ import Check from '@material-ui/icons/Check';
12
+ import Bookmark from '@material-ui/icons/Bookmark';
13
+ import Inbox from '@material-ui/icons/Inbox';
14
+ import CloseIcon from '@material-ui/icons/Close';
15
+ import RelativeTime from 'react-relative-time';
16
+ import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
17
+
18
+ const rootRouteRef = createRouteRef({
19
+ id: "notifications"
20
+ });
21
+
22
+ const notificationsApiRef = createApiRef({
23
+ id: "plugin.notifications.service"
24
+ });
25
+
26
+ var __defProp = Object.defineProperty;
27
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
28
+ var __publicField = (obj, key, value) => {
29
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
30
+ return value;
31
+ };
32
+ class NotificationsClient {
33
+ constructor(options) {
34
+ __publicField(this, "discoveryApi");
35
+ __publicField(this, "fetchApi");
36
+ this.discoveryApi = options.discoveryApi;
37
+ this.fetchApi = options.fetchApi;
38
+ }
39
+ async getNotifications(options) {
40
+ const queryString = new URLSearchParams();
41
+ if (options == null ? void 0 : options.type) {
42
+ queryString.append("type", options.type);
43
+ }
44
+ if ((options == null ? void 0 : options.limit) !== void 0) {
45
+ queryString.append("limit", options.limit.toString(10));
46
+ }
47
+ if ((options == null ? void 0 : options.offset) !== void 0) {
48
+ queryString.append("offset", options.offset.toString(10));
49
+ }
50
+ if (options == null ? void 0 : options.search) {
51
+ queryString.append("search", options.search);
52
+ }
53
+ const urlSegment = `?${queryString}`;
54
+ return await this.request(urlSegment);
55
+ }
56
+ async getStatus() {
57
+ return await this.request("status");
58
+ }
59
+ async updateNotifications(options) {
60
+ return await this.request("update", {
61
+ method: "POST",
62
+ body: JSON.stringify(options),
63
+ headers: { "Content-Type": "application/json" }
64
+ });
65
+ }
66
+ async request(path, init) {
67
+ const baseUrl = `${await this.discoveryApi.getBaseUrl("notifications")}/`;
68
+ const url = new URL(path, baseUrl);
69
+ const response = await this.fetchApi.fetch(url.toString(), init);
70
+ if (!response.ok) {
71
+ throw await ResponseError.fromResponse(response);
72
+ }
73
+ return response.json();
74
+ }
75
+ }
76
+
77
+ const notificationsPlugin = createPlugin({
78
+ id: "notifications",
79
+ routes: {
80
+ root: rootRouteRef
81
+ },
82
+ apis: [
83
+ createApiFactory({
84
+ api: notificationsApiRef,
85
+ deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },
86
+ factory: ({ discoveryApi, fetchApi }) => new NotificationsClient({ discoveryApi, fetchApi })
87
+ })
88
+ ]
89
+ });
90
+ const NotificationsPage = notificationsPlugin.provide(
91
+ createRoutableExtension({
92
+ name: "NotificationsPage",
93
+ component: () => import('./esm/index-9074aad8.esm.js').then((m) => m.NotificationsPage),
94
+ mountPoint: rootRouteRef
95
+ })
96
+ );
97
+
98
+ function useNotificationsApi(f, deps = []) {
99
+ const notificationsApi = useApi(notificationsApiRef);
100
+ return useAsyncRetry(async () => {
101
+ return await f(notificationsApi);
102
+ }, deps);
103
+ }
104
+
105
+ function useWebNotifications() {
106
+ const [webNotificationPermission, setWebNotificationPermission] = useState("default");
107
+ const [webNotifications, setWebNotifications] = useState([]);
108
+ useEffect(() => {
109
+ if ("Notification" in window && webNotificationPermission === "default") {
110
+ window.Notification.requestPermission().then((permission) => {
111
+ setWebNotificationPermission(permission);
112
+ });
113
+ }
114
+ }, [webNotificationPermission]);
115
+ document.addEventListener("visibilitychange", () => {
116
+ if (document.visibilityState === "visible") {
117
+ webNotifications.forEach((n) => n.close());
118
+ setWebNotifications([]);
119
+ }
120
+ });
121
+ const sendWebNotification = useCallback(
122
+ (options) => {
123
+ if (webNotificationPermission !== "granted") {
124
+ return null;
125
+ }
126
+ const notification = new Notification(options.title, {
127
+ body: options.description
128
+ });
129
+ return notification;
130
+ },
131
+ [webNotificationPermission]
132
+ );
133
+ return { sendWebNotification };
134
+ }
135
+
136
+ function useTitleCounter() {
137
+ const [title, setTitle] = useState(document.title);
138
+ const [count, setCount] = useState(0);
139
+ const getPrefix = (value) => {
140
+ return value === 0 ? "" : `(${value}) `;
141
+ };
142
+ const cleanTitle = (currentTitle) => {
143
+ return currentTitle.replace(/^\(\d+\)\s/, "");
144
+ };
145
+ useEffect(() => {
146
+ document.title = title;
147
+ }, [title]);
148
+ useEffect(() => {
149
+ const baseTitle = cleanTitle(title);
150
+ setTitle(`${getPrefix(count)}${baseTitle}`);
151
+ return () => {
152
+ document.title = cleanTitle(title);
153
+ };
154
+ }, [title, count]);
155
+ const titleElement = document.querySelector("title");
156
+ if (titleElement) {
157
+ new MutationObserver(() => {
158
+ setTitle(document.title);
159
+ }).observe(titleElement, {
160
+ subtree: true,
161
+ characterData: true,
162
+ childList: true
163
+ });
164
+ }
165
+ const setNotificationCount = useCallback(
166
+ (newCount) => setCount(newCount),
167
+ []
168
+ );
169
+ return { setNotificationCount };
170
+ }
171
+
172
+ const NotificationsSidebarItem = (props) => {
173
+ const { webNotificationsEnabled = false, titleCounterEnabled = true } = props != null ? props : { webNotificationsEnabled: false, titleCounterEnabled: true };
174
+ const { loading, error, value, retry } = useNotificationsApi(
175
+ (api) => api.getStatus()
176
+ );
177
+ const [unreadCount, setUnreadCount] = React.useState(0);
178
+ const notificationsRoute = useRouteRef(rootRouteRef);
179
+ const { lastSignal } = useSignal("notifications");
180
+ const { sendWebNotification } = useWebNotifications();
181
+ const [refresh, setRefresh] = React.useState(false);
182
+ const { setNotificationCount } = useTitleCounter();
183
+ useEffect(() => {
184
+ if (refresh) {
185
+ retry();
186
+ setRefresh(false);
187
+ }
188
+ }, [refresh, retry]);
189
+ useEffect(() => {
190
+ const handleWebNotification = (signal) => {
191
+ if (!webNotificationsEnabled || !("notification" in signal)) {
192
+ return;
193
+ }
194
+ const notificationData = signal.notification;
195
+ if (!notificationData || !("title" in notificationData) || !("description" in notificationData) || !("title" in notificationData)) {
196
+ return;
197
+ }
198
+ const notification = sendWebNotification({
199
+ title: notificationData.title,
200
+ description: notificationData.description
201
+ });
202
+ if (notification) {
203
+ notification.onclick = (event) => {
204
+ event.preventDefault();
205
+ notification.close();
206
+ window.open(notificationData.link, "_blank");
207
+ };
208
+ }
209
+ };
210
+ if (lastSignal && lastSignal.action) {
211
+ handleWebNotification(lastSignal);
212
+ setRefresh(true);
213
+ }
214
+ }, [lastSignal, sendWebNotification, webNotificationsEnabled]);
215
+ useEffect(() => {
216
+ if (!loading && !error && value) {
217
+ setUnreadCount(value.unread);
218
+ if (titleCounterEnabled) {
219
+ setNotificationCount(value.unread);
220
+ }
221
+ }
222
+ }, [loading, error, value, titleCounterEnabled, setNotificationCount]);
223
+ return /* @__PURE__ */ React.createElement(
224
+ SidebarItem,
225
+ {
226
+ icon: NotificationsIcon,
227
+ to: notificationsRoute(),
228
+ text: "Notifications",
229
+ hasNotifications: !error && !!unreadCount
230
+ }
231
+ );
232
+ };
233
+
234
+ const useStyles = makeStyles((theme) => ({
235
+ table: {
236
+ border: `1px solid ${theme.palette.divider}`
237
+ },
238
+ header: {
239
+ borderBottom: `1px solid ${theme.palette.divider}`
240
+ },
241
+ notificationRow: {
242
+ cursor: "pointer",
243
+ "&.unread": {
244
+ border: "1px solid rgba(255, 255, 255, .3)"
245
+ },
246
+ "& .hideOnHover": {
247
+ display: "initial"
248
+ },
249
+ "& .showOnHover": {
250
+ display: "none"
251
+ },
252
+ "&:hover": {
253
+ "& .hideOnHover": {
254
+ display: "none"
255
+ },
256
+ "& .showOnHover": {
257
+ display: "initial"
258
+ }
259
+ }
260
+ },
261
+ actionButton: {
262
+ padding: "9px"
263
+ },
264
+ checkBox: {
265
+ padding: "0 10px 10px 0"
266
+ }
267
+ }));
268
+ const NotificationsTable = (props) => {
269
+ var _a, _b;
270
+ const { notifications, type } = props;
271
+ const navigate = useNavigate();
272
+ const styles = useStyles();
273
+ const [selected, setSelected] = useState([]);
274
+ const notificationsApi = useApi(notificationsApiRef);
275
+ const onCheckBoxClick = (id) => {
276
+ const index = selected.indexOf(id);
277
+ if (index !== -1) {
278
+ setSelected(selected.filter((s) => s !== id));
279
+ } else {
280
+ setSelected([...selected, id]);
281
+ }
282
+ };
283
+ useEffect(() => {
284
+ setSelected([]);
285
+ }, [type]);
286
+ const isChecked = (id) => {
287
+ return selected.indexOf(id) !== -1;
288
+ };
289
+ const isAllSelected = () => {
290
+ return selected.length === (notifications == null ? void 0 : notifications.length) && notifications.length > 0;
291
+ };
292
+ return /* @__PURE__ */ React.createElement(Table, { size: "small", className: styles.table }, /* @__PURE__ */ React.createElement(TableHead, null, /* @__PURE__ */ React.createElement(TableRow, null, /* @__PURE__ */ React.createElement(TableCell, { colSpan: 3 }, type !== "saved" && !(notifications == null ? void 0 : notifications.length) && "No notifications", type !== "saved" && !!(notifications == null ? void 0 : notifications.length) && /* @__PURE__ */ React.createElement(
293
+ Checkbox,
294
+ {
295
+ size: "small",
296
+ style: { paddingLeft: 0 },
297
+ checked: isAllSelected(),
298
+ onClick: () => {
299
+ if (isAllSelected()) {
300
+ setSelected([]);
301
+ } else {
302
+ setSelected(
303
+ notifications ? notifications.map((n) => n.id) : []
304
+ );
305
+ }
306
+ }
307
+ }
308
+ ), type === "saved" && `${(_a = notifications == null ? void 0 : notifications.length) != null ? _a : 0} saved notifications`, selected.length === 0 && !!(notifications == null ? void 0 : notifications.length) && type !== "saved" && "Select all", selected.length > 0 && `${selected.length} selected`, type === "done" && selected.length > 0 && /* @__PURE__ */ React.createElement(
309
+ Button,
310
+ {
311
+ startIcon: /* @__PURE__ */ React.createElement(Inbox, { fontSize: "small" }),
312
+ onClick: () => {
313
+ notificationsApi.updateNotifications({ ids: selected, done: false }).then(() => props.onUpdate());
314
+ setSelected([]);
315
+ }
316
+ },
317
+ "Move to inbox"
318
+ ), type === "undone" && selected.length > 0 && /* @__PURE__ */ React.createElement(
319
+ Button,
320
+ {
321
+ startIcon: /* @__PURE__ */ React.createElement(Check, { fontSize: "small" }),
322
+ onClick: () => {
323
+ notificationsApi.updateNotifications({ ids: selected, done: true }).then(() => props.onUpdate());
324
+ setSelected([]);
325
+ }
326
+ },
327
+ "Mark as done"
328
+ )))), /* @__PURE__ */ React.createElement(TableBody, null, (_b = props.notifications) == null ? void 0 : _b.map((notification) => {
329
+ return /* @__PURE__ */ React.createElement(
330
+ TableRow,
331
+ {
332
+ key: notification.id,
333
+ className: `${styles.notificationRow} ${!notification.read ? "unread" : ""}`,
334
+ hover: true
335
+ },
336
+ /* @__PURE__ */ React.createElement(
337
+ TableCell,
338
+ {
339
+ width: "60px",
340
+ style: { verticalAlign: "center", paddingRight: "0px" }
341
+ },
342
+ /* @__PURE__ */ React.createElement(
343
+ Checkbox,
344
+ {
345
+ className: styles.checkBox,
346
+ size: "small",
347
+ checked: isChecked(notification.id),
348
+ onClick: () => onCheckBoxClick(notification.id)
349
+ }
350
+ )
351
+ ),
352
+ /* @__PURE__ */ React.createElement(
353
+ TableCell,
354
+ {
355
+ onClick: () => notificationsApi.updateNotifications({ ids: [notification.id], read: true }).then(() => navigate(notification.payload.link)),
356
+ style: { paddingLeft: 0 }
357
+ },
358
+ /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2" }, notification.payload.title),
359
+ /* @__PURE__ */ React.createElement(Typography, { variant: "body2" }, notification.payload.description)
360
+ ),
361
+ /* @__PURE__ */ React.createElement(TableCell, { style: { textAlign: "right" } }, /* @__PURE__ */ React.createElement(Box, { className: "hideOnHover" }, /* @__PURE__ */ React.createElement(RelativeTime, { value: notification.created })), /* @__PURE__ */ React.createElement(Box, { className: "showOnHover" }, /* @__PURE__ */ React.createElement(Tooltip, { title: notification.payload.link }, /* @__PURE__ */ React.createElement(
362
+ IconButton,
363
+ {
364
+ className: styles.actionButton,
365
+ onClick: () => notificationsApi.updateNotifications({
366
+ ids: [notification.id],
367
+ read: true
368
+ }).then(() => navigate(notification.payload.link))
369
+ },
370
+ /* @__PURE__ */ React.createElement(ArrowForwardIcon, null)
371
+ )), /* @__PURE__ */ React.createElement(
372
+ Tooltip,
373
+ {
374
+ title: notification.read ? "Move to inbox" : "Mark as done"
375
+ },
376
+ /* @__PURE__ */ React.createElement(
377
+ IconButton,
378
+ {
379
+ className: styles.actionButton,
380
+ onClick: () => {
381
+ if (notification.read) {
382
+ notificationsApi.updateNotifications({
383
+ ids: [notification.id],
384
+ done: false
385
+ }).then(() => {
386
+ props.onUpdate();
387
+ });
388
+ } else {
389
+ notificationsApi.updateNotifications({
390
+ ids: [notification.id],
391
+ done: true
392
+ }).then(() => {
393
+ props.onUpdate();
394
+ });
395
+ }
396
+ }
397
+ },
398
+ notification.read ? /* @__PURE__ */ React.createElement(Inbox, { fontSize: "small" }) : /* @__PURE__ */ React.createElement(Check, { fontSize: "small" })
399
+ )
400
+ ), /* @__PURE__ */ React.createElement(
401
+ Tooltip,
402
+ {
403
+ title: notification.saved ? "Remove from saved" : "Save"
404
+ },
405
+ /* @__PURE__ */ React.createElement(
406
+ IconButton,
407
+ {
408
+ className: styles.actionButton,
409
+ onClick: () => {
410
+ if (notification.saved) {
411
+ notificationsApi.updateNotifications({
412
+ ids: [notification.id],
413
+ saved: false
414
+ }).then(() => {
415
+ props.onUpdate();
416
+ });
417
+ } else {
418
+ notificationsApi.updateNotifications({
419
+ ids: [notification.id],
420
+ saved: true
421
+ }).then(() => {
422
+ props.onUpdate();
423
+ });
424
+ }
425
+ }
426
+ },
427
+ notification.saved ? /* @__PURE__ */ React.createElement(CloseIcon, { fontSize: "small" }) : /* @__PURE__ */ React.createElement(Bookmark, { fontSize: "small" })
428
+ )
429
+ )))
430
+ );
431
+ })));
432
+ };
433
+
434
+ export { NotificationsClient, NotificationsPage, NotificationsSidebarItem, NotificationsTable, notificationsApiRef, notificationsPlugin, useNotificationsApi, useTitleCounter, useWebNotifications };
435
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/routes.ts","../src/api/NotificationsApi.ts","../src/api/NotificationsClient.ts","../src/plugin.ts","../src/hooks/useNotificationsApi.ts","../src/hooks/useWebNotifications.ts","../src/hooks/useTitleCounter.ts","../src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx","../src/components/NotificationsTable/NotificationsTable.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'notifications',\n});\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { createApiRef } from '@backstage/core-plugin-api';\nimport {\n Notification,\n NotificationStatus,\n NotificationType,\n} from '@backstage/plugin-notifications-common';\n\n/** @public */\nexport const notificationsApiRef = createApiRef<NotificationsApi>({\n id: 'plugin.notifications.service',\n});\n\n/** @public */\nexport type GetNotificationsOptions = {\n type?: NotificationType;\n offset?: number;\n limit?: number;\n search?: string;\n};\n\n/** @public */\nexport type UpdateNotificationsOptions = {\n ids: string[];\n done?: boolean;\n read?: boolean;\n saved?: boolean;\n};\n\n/** @public */\nexport interface NotificationsApi {\n getNotifications(options?: GetNotificationsOptions): Promise<Notification[]>;\n\n getStatus(): Promise<NotificationStatus>;\n\n updateNotifications(\n options: UpdateNotificationsOptions,\n ): Promise<Notification[]>;\n}\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n GetNotificationsOptions,\n NotificationsApi,\n UpdateNotificationsOptions,\n} from './NotificationsApi';\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport {\n Notification,\n NotificationStatus,\n} from '@backstage/plugin-notifications-common';\n\n/** @public */\nexport class NotificationsClient implements NotificationsApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n\n public constructor(options: {\n discoveryApi: DiscoveryApi;\n fetchApi: FetchApi;\n }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n }\n\n async getNotifications(\n options?: GetNotificationsOptions,\n ): Promise<Notification[]> {\n const queryString = new URLSearchParams();\n if (options?.type) {\n queryString.append('type', options.type);\n }\n if (options?.limit !== undefined) {\n queryString.append('limit', options.limit.toString(10));\n }\n if (options?.offset !== undefined) {\n queryString.append('offset', options.offset.toString(10));\n }\n if (options?.search) {\n queryString.append('search', options.search);\n }\n\n const urlSegment = `?${queryString}`;\n\n return await this.request<Notification[]>(urlSegment);\n }\n\n async getStatus(): Promise<NotificationStatus> {\n return await this.request<NotificationStatus>('status');\n }\n\n async updateNotifications(\n options: UpdateNotificationsOptions,\n ): Promise<Notification[]> {\n return await this.request<Notification[]>('update', {\n method: 'POST',\n body: JSON.stringify(options),\n headers: { 'Content-Type': 'application/json' },\n });\n }\n\n private async request<T>(path: string, init?: any): Promise<T> {\n const baseUrl = `${await this.discoveryApi.getBaseUrl('notifications')}/`;\n const url = new URL(path, baseUrl);\n\n const response = await this.fetchApi.fetch(url.toString(), init);\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json() as Promise<T>;\n }\n}\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n createApiFactory,\n createPlugin,\n createRoutableExtension,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { rootRouteRef } from './routes';\nimport { notificationsApiRef } from './api/NotificationsApi';\nimport { NotificationsClient } from './api';\n\n/** @public */\nexport const notificationsPlugin = createPlugin({\n id: 'notifications',\n routes: {\n root: rootRouteRef,\n },\n apis: [\n createApiFactory({\n api: notificationsApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new NotificationsClient({ discoveryApi, fetchApi }),\n }),\n ],\n});\n\n/** @public */\nexport const NotificationsPage = notificationsPlugin.provide(\n createRoutableExtension({\n name: 'NotificationsPage',\n component: () =>\n import('./components/NotificationsPage').then(m => m.NotificationsPage),\n mountPoint: rootRouteRef,\n }),\n);\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { NotificationsApi, notificationsApiRef } from '../api';\nimport { useApi } from '@backstage/core-plugin-api';\nimport useAsyncRetry from 'react-use/lib/useAsyncRetry';\n\n/** @public */\nexport function useNotificationsApi<T>(\n f: (api: NotificationsApi) => Promise<T>,\n deps: any[] = [],\n) {\n const notificationsApi = useApi(notificationsApiRef);\n\n return useAsyncRetry(async () => {\n return await f(notificationsApi);\n }, deps);\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useCallback, useEffect, useState } from 'react';\n\n/** @public */\nexport function useWebNotifications() {\n const [webNotificationPermission, setWebNotificationPermission] =\n useState('default');\n const [webNotifications, setWebNotifications] = useState<Notification[]>([]);\n\n useEffect(() => {\n if ('Notification' in window && webNotificationPermission === 'default') {\n window.Notification.requestPermission().then(permission => {\n setWebNotificationPermission(permission);\n });\n }\n }, [webNotificationPermission]);\n\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'visible') {\n webNotifications.forEach(n => n.close());\n setWebNotifications([]);\n }\n });\n\n const sendWebNotification = useCallback(\n (options: { title: string; description: string }) => {\n if (webNotificationPermission !== 'granted') {\n return null;\n }\n\n const notification = new Notification(options.title, {\n body: options.description,\n });\n return notification;\n },\n [webNotificationPermission],\n );\n\n return { sendWebNotification };\n}\n","/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useCallback, useEffect, useState } from 'react';\n\n/** @public */\nexport function useTitleCounter() {\n const [title, setTitle] = useState(document.title);\n const [count, setCount] = useState(0);\n\n const getPrefix = (value: number) => {\n return value === 0 ? '' : `(${value}) `;\n };\n\n const cleanTitle = (currentTitle: string) => {\n return currentTitle.replace(/^\\(\\d+\\)\\s/, '');\n };\n\n useEffect(() => {\n document.title = title;\n }, [title]);\n\n useEffect(() => {\n const baseTitle = cleanTitle(title);\n setTitle(`${getPrefix(count)}${baseTitle}`);\n return () => {\n document.title = cleanTitle(title);\n };\n }, [title, count]);\n\n const titleElement = document.querySelector('title');\n if (titleElement) {\n new MutationObserver(() => {\n setTitle(document.title);\n }).observe(titleElement, {\n subtree: true,\n characterData: true,\n childList: true,\n });\n }\n\n const setNotificationCount = useCallback(\n (newCount: number) => setCount(newCount),\n [],\n );\n\n return { setNotificationCount };\n}\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React, { useEffect } from 'react';\nimport { useNotificationsApi } from '../../hooks';\nimport { SidebarItem } from '@backstage/core-components';\nimport NotificationsIcon from '@material-ui/icons/Notifications';\nimport { useRouteRef } from '@backstage/core-plugin-api';\nimport { rootRouteRef } from '../../routes';\nimport { useSignal } from '@backstage/plugin-signals-react';\nimport { useWebNotifications } from '../../hooks/useWebNotifications';\nimport { useTitleCounter } from '../../hooks/useTitleCounter';\nimport { JsonObject } from '@backstage/types';\n\n/** @public */\nexport const NotificationsSidebarItem = (props?: {\n webNotificationsEnabled?: boolean;\n titleCounterEnabled?: boolean;\n}) => {\n const { webNotificationsEnabled = false, titleCounterEnabled = true } =\n props ?? { webNotificationsEnabled: false, titleCounterEnabled: true };\n\n const { loading, error, value, retry } = useNotificationsApi(api =>\n api.getStatus(),\n );\n const [unreadCount, setUnreadCount] = React.useState(0);\n const notificationsRoute = useRouteRef(rootRouteRef);\n // TODO: Add signal type support to `useSignal` to make it a bit easier to use\n // TODO: Do we want to add long polling in case signals are not available\n const { lastSignal } = useSignal('notifications');\n const { sendWebNotification } = useWebNotifications();\n const [refresh, setRefresh] = React.useState(false);\n const { setNotificationCount } = useTitleCounter();\n\n useEffect(() => {\n if (refresh) {\n retry();\n setRefresh(false);\n }\n }, [refresh, retry]);\n\n useEffect(() => {\n const handleWebNotification = (signal: JsonObject) => {\n if (!webNotificationsEnabled || !('notification' in signal)) {\n return;\n }\n\n const notificationData = signal.notification as JsonObject;\n\n if (\n !notificationData ||\n !('title' in notificationData) ||\n !('description' in notificationData) ||\n !('title' in notificationData)\n ) {\n return;\n }\n const notification = sendWebNotification({\n title: notificationData.title as string,\n description: notificationData.description as string,\n });\n if (notification) {\n notification.onclick = event => {\n event.preventDefault();\n notification.close();\n window.open(notificationData.link as string, '_blank');\n };\n }\n };\n\n if (lastSignal && lastSignal.action) {\n handleWebNotification(lastSignal);\n setRefresh(true);\n }\n }, [lastSignal, sendWebNotification, webNotificationsEnabled]);\n\n useEffect(() => {\n if (!loading && !error && value) {\n setUnreadCount(value.unread);\n if (titleCounterEnabled) {\n setNotificationCount(value.unread);\n }\n }\n }, [loading, error, value, titleCounterEnabled, setNotificationCount]);\n\n // TODO: Figure out if the count can be added to hasNotifications\n return (\n <SidebarItem\n icon={NotificationsIcon}\n to={notificationsRoute()}\n text=\"Notifications\"\n hasNotifications={!error && !!unreadCount}\n />\n );\n};\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport React, { useEffect, useState } from 'react';\nimport {\n Box,\n Button,\n IconButton,\n makeStyles,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tooltip,\n Typography,\n} from '@material-ui/core';\nimport {\n Notification,\n NotificationType,\n} from '@backstage/plugin-notifications-common';\nimport { useNavigate } from 'react-router-dom';\nimport Checkbox from '@material-ui/core/Checkbox';\nimport Check from '@material-ui/icons/Check';\nimport Bookmark from '@material-ui/icons/Bookmark';\nimport { notificationsApiRef } from '../../api';\nimport { useApi } from '@backstage/core-plugin-api';\nimport Inbox from '@material-ui/icons/Inbox';\nimport CloseIcon from '@material-ui/icons/Close';\n// @ts-ignore\nimport RelativeTime from 'react-relative-time';\nimport ArrowForwardIcon from '@material-ui/icons/ArrowForward';\n\nconst useStyles = makeStyles(theme => ({\n table: {\n border: `1px solid ${theme.palette.divider}`,\n },\n header: {\n borderBottom: `1px solid ${theme.palette.divider}`,\n },\n\n notificationRow: {\n cursor: 'pointer',\n '&.unread': {\n border: '1px solid rgba(255, 255, 255, .3)',\n },\n '& .hideOnHover': {\n display: 'initial',\n },\n '& .showOnHover': {\n display: 'none',\n },\n '&:hover': {\n '& .hideOnHover': {\n display: 'none',\n },\n '& .showOnHover': {\n display: 'initial',\n },\n },\n },\n actionButton: {\n padding: '9px',\n },\n checkBox: {\n padding: '0 10px 10px 0',\n },\n}));\n\n/** @public */\nexport const NotificationsTable = (props: {\n onUpdate: () => void;\n type: NotificationType;\n notifications?: Notification[];\n}) => {\n const { notifications, type } = props;\n const navigate = useNavigate();\n const styles = useStyles();\n const [selected, setSelected] = useState<string[]>([]);\n const notificationsApi = useApi(notificationsApiRef);\n\n const onCheckBoxClick = (id: string) => {\n const index = selected.indexOf(id);\n if (index !== -1) {\n setSelected(selected.filter(s => s !== id));\n } else {\n setSelected([...selected, id]);\n }\n };\n\n useEffect(() => {\n setSelected([]);\n }, [type]);\n\n const isChecked = (id: string) => {\n return selected.indexOf(id) !== -1;\n };\n\n const isAllSelected = () => {\n return (\n selected.length === notifications?.length && notifications.length > 0\n );\n };\n\n return (\n <Table size=\"small\" className={styles.table}>\n <TableHead>\n <TableRow>\n <TableCell colSpan={3}>\n {type !== 'saved' && !notifications?.length && 'No notifications'}\n {type !== 'saved' && !!notifications?.length && (\n <Checkbox\n size=\"small\"\n style={{ paddingLeft: 0 }}\n checked={isAllSelected()}\n onClick={() => {\n if (isAllSelected()) {\n setSelected([]);\n } else {\n setSelected(\n notifications ? notifications.map(n => n.id) : [],\n );\n }\n }}\n />\n )}\n {type === 'saved' &&\n `${notifications?.length ?? 0} saved notifications`}\n {selected.length === 0 &&\n !!notifications?.length &&\n type !== 'saved' &&\n 'Select all'}\n {selected.length > 0 && `${selected.length} selected`}\n {type === 'done' && selected.length > 0 && (\n <Button\n startIcon={<Inbox fontSize=\"small\" />}\n onClick={() => {\n notificationsApi\n .updateNotifications({ ids: selected, done: false })\n .then(() => props.onUpdate());\n setSelected([]);\n }}\n >\n Move to inbox\n </Button>\n )}\n\n {type === 'undone' && selected.length > 0 && (\n <Button\n startIcon={<Check fontSize=\"small\" />}\n onClick={() => {\n notificationsApi\n .updateNotifications({ ids: selected, done: true })\n .then(() => props.onUpdate());\n setSelected([]);\n }}\n >\n Mark as done\n </Button>\n )}\n </TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {props.notifications?.map(notification => {\n return (\n <TableRow\n key={notification.id}\n className={`${styles.notificationRow} ${\n !notification.read ? 'unread' : ''\n }`}\n hover\n >\n <TableCell\n width=\"60px\"\n style={{ verticalAlign: 'center', paddingRight: '0px' }}\n >\n <Checkbox\n className={styles.checkBox}\n size=\"small\"\n checked={isChecked(notification.id)}\n onClick={() => onCheckBoxClick(notification.id)}\n />\n </TableCell>\n <TableCell\n onClick={() =>\n notificationsApi\n .updateNotifications({ ids: [notification.id], read: true })\n .then(() => navigate(notification.payload.link))\n }\n style={{ paddingLeft: 0 }}\n >\n <Typography variant=\"subtitle2\">\n {notification.payload.title}\n </Typography>\n <Typography variant=\"body2\">\n {notification.payload.description}\n </Typography>\n </TableCell>\n <TableCell style={{ textAlign: 'right' }}>\n <Box className=\"hideOnHover\">\n <RelativeTime value={notification.created} />\n </Box>\n <Box className=\"showOnHover\">\n <Tooltip title={notification.payload.link}>\n <IconButton\n className={styles.actionButton}\n onClick={() =>\n notificationsApi\n .updateNotifications({\n ids: [notification.id],\n read: true,\n })\n .then(() => navigate(notification.payload.link))\n }\n >\n <ArrowForwardIcon />\n </IconButton>\n </Tooltip>\n <Tooltip\n title={notification.read ? 'Move to inbox' : 'Mark as done'}\n >\n <IconButton\n className={styles.actionButton}\n onClick={() => {\n if (notification.read) {\n notificationsApi\n .updateNotifications({\n ids: [notification.id],\n done: false,\n })\n .then(() => {\n props.onUpdate();\n });\n } else {\n notificationsApi\n .updateNotifications({\n ids: [notification.id],\n done: true,\n })\n .then(() => {\n props.onUpdate();\n });\n }\n }}\n >\n {notification.read ? (\n <Inbox fontSize=\"small\" />\n ) : (\n <Check fontSize=\"small\" />\n )}\n </IconButton>\n </Tooltip>\n <Tooltip\n title={notification.saved ? 'Remove from saved' : 'Save'}\n >\n <IconButton\n className={styles.actionButton}\n onClick={() => {\n if (notification.saved) {\n notificationsApi\n .updateNotifications({\n ids: [notification.id],\n saved: false,\n })\n .then(() => {\n props.onUpdate();\n });\n } else {\n notificationsApi\n .updateNotifications({\n ids: [notification.id],\n saved: true,\n })\n .then(() => {\n props.onUpdate();\n });\n }\n }}\n >\n {notification.saved ? (\n <CloseIcon fontSize=\"small\" />\n ) : (\n <Bookmark fontSize=\"small\" />\n )}\n </IconButton>\n </Tooltip>\n </Box>\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n );\n};\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@backstage/plugin-notifications",
3
+ "version": "0.0.0-nightly-20240207020916",
4
+ "main": "dist/index.esm.js",
5
+ "types": "dist/index.d.ts",
6
+ "license": "Apache-2.0",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "main": "dist/index.esm.js",
10
+ "types": "dist/index.d.ts"
11
+ },
12
+ "backstage": {
13
+ "role": "frontend-plugin"
14
+ },
15
+ "sideEffects": false,
16
+ "scripts": {
17
+ "start": "backstage-cli package start",
18
+ "build": "backstage-cli package build",
19
+ "lint": "backstage-cli package lint",
20
+ "test": "backstage-cli package test",
21
+ "clean": "backstage-cli package clean",
22
+ "prepack": "backstage-cli package prepack",
23
+ "postpack": "backstage-cli package postpack"
24
+ },
25
+ "dependencies": {
26
+ "@backstage/core-components": "^0.0.0-nightly-20240207020916",
27
+ "@backstage/core-plugin-api": "^0.0.0-nightly-20240207020916",
28
+ "@backstage/errors": "^1.2.3",
29
+ "@backstage/plugin-notifications-common": "^0.0.0-nightly-20240207020916",
30
+ "@backstage/plugin-signals-react": "^0.0.0-nightly-20240207020916",
31
+ "@backstage/theme": "^0.0.0-nightly-20240207020916",
32
+ "@backstage/types": "^1.1.1",
33
+ "@material-ui/core": "^4.9.13",
34
+ "@material-ui/icons": "^4.9.1",
35
+ "@material-ui/lab": "^4.0.0-alpha.61",
36
+ "@types/react": "^16.13.1 || ^17.0.0",
37
+ "react-relative-time": "^0.0.9",
38
+ "react-use": "^17.2.4"
39
+ },
40
+ "peerDependencies": {
41
+ "react": "^16.13.1 || ^17.0.0",
42
+ "react-router-dom": "6.0.0-beta.0 || ^6.3.0"
43
+ },
44
+ "devDependencies": {
45
+ "@backstage/cli": "^0.0.0-nightly-20240207020916",
46
+ "@backstage/core-app-api": "^0.0.0-nightly-20240207020916",
47
+ "@backstage/dev-utils": "^0.0.0-nightly-20240207020916",
48
+ "@backstage/test-utils": "^0.0.0-nightly-20240207020916",
49
+ "@testing-library/jest-dom": "^6.0.0",
50
+ "@testing-library/react": "^14.0.0",
51
+ "@testing-library/user-event": "^14.0.0",
52
+ "msw": "^1.0.0"
53
+ },
54
+ "files": [
55
+ "dist"
56
+ ],
57
+ "module": "./dist/index.esm.js"
58
+ }