@backstage/plugin-notifications 0.2.1-next.0 → 0.2.1-next.1

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # @backstage/plugin-notifications
2
2
 
3
+ ## 0.2.1-next.1
4
+
5
+ ### Patch Changes
6
+
7
+ - f730c0b: The user can newly mark all unread messages as read at one click.
8
+ - bfcb2f1: Allow showing notifications as snackbars in the UI
9
+ - f6633ca: Add option to set the notification as read automatically when the notification link is opened
10
+ - Updated dependencies
11
+ - @backstage/core-components@0.14.6-next.1
12
+
3
13
  ## 0.2.1-next.0
4
14
 
5
15
  ### Patch Changes
@@ -2,6 +2,7 @@ import React, { useEffect } from 'react';
2
2
  import throttle from 'lodash/throttle';
3
3
  import { ResponseErrorPanel, PageWithHeader, Content } from '@backstage/core-components';
4
4
  import Grid from '@material-ui/core/Grid';
5
+ import { ConfirmProvider } from 'material-ui-confirm';
5
6
  import { useSignal } from '@backstage/plugin-signals-react';
6
7
  import { NotificationsTable } from '../NotificationsTable/NotificationsTable.esm.js';
7
8
  import { useNotificationsApi } from '../../hooks/useNotificationsApi.esm.js';
@@ -12,13 +13,15 @@ import { SortByOptions, NotificationsFilters, CreatedAfterOptions } from '../Not
12
13
 
13
14
  const ThrottleDelayMs = 2e3;
14
15
  const NotificationsPage = (props) => {
16
+ var _a, _b, _c;
15
17
  const {
16
18
  title = "Notifications",
17
19
  themeId = "tool",
18
20
  subtitle,
19
21
  tooltip,
20
22
  type,
21
- typeLink
23
+ typeLink,
24
+ markAsReadOnLinkOpen
22
25
  } = props != null ? props : {};
23
26
  const [refresh, setRefresh] = React.useState(false);
24
27
  const { lastSignal } = useSignal("notifications");
@@ -51,7 +54,7 @@ const NotificationsPage = (props) => {
51
54
  if (createdAfterDate.valueOf() > 0) {
52
55
  options.createdAfter = createdAfterDate;
53
56
  }
54
- return api.getNotifications(options);
57
+ return Promise.all([api.getNotifications(options), api.getStatus()]);
55
58
  },
56
59
  [
57
60
  containsText,
@@ -85,6 +88,9 @@ const NotificationsPage = (props) => {
85
88
  if (error) {
86
89
  return /* @__PURE__ */ React.createElement(ResponseErrorPanel, { error });
87
90
  }
91
+ const notifications = (_a = value == null ? void 0 : value[0]) == null ? void 0 : _a.notifications;
92
+ const totalCount = (_b = value == null ? void 0 : value[0]) == null ? void 0 : _b.totalCount;
93
+ const isUnread = !!((_c = value == null ? void 0 : value[1]) == null ? void 0 : _c.unread);
88
94
  return /* @__PURE__ */ React.createElement(
89
95
  PageWithHeader,
90
96
  {
@@ -95,7 +101,7 @@ const NotificationsPage = (props) => {
95
101
  type,
96
102
  typeLink
97
103
  },
98
- /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 2 }, /* @__PURE__ */ React.createElement(
104
+ /* @__PURE__ */ React.createElement(Content, null, /* @__PURE__ */ React.createElement(ConfirmProvider, null, /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 2 }, /* @__PURE__ */ React.createElement(
99
105
  NotificationsFilters,
100
106
  {
101
107
  unreadOnly,
@@ -113,16 +119,18 @@ const NotificationsPage = (props) => {
113
119
  NotificationsTable,
114
120
  {
115
121
  isLoading: loading,
116
- notifications: value == null ? void 0 : value.notifications,
122
+ isUnread,
123
+ markAsReadOnLinkOpen,
124
+ notifications,
117
125
  onUpdate,
118
126
  setContainsText,
119
127
  onPageChange: setPageNumber,
120
128
  onRowsPerPageChange: setPageSize,
121
129
  page: pageNumber,
122
130
  pageSize,
123
- totalCount: value == null ? void 0 : value.totalCount
131
+ totalCount
124
132
  }
125
- ))))
133
+ )))))
126
134
  );
127
135
  };
128
136
 
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationsPage.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 } from 'react';\nimport throttle from 'lodash/throttle';\nimport {\n Content,\n PageWithHeader,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport Grid from '@material-ui/core/Grid';\nimport { useSignal } from '@backstage/plugin-signals-react';\n\nimport { NotificationsTable } from '../NotificationsTable';\nimport { useNotificationsApi } from '../../hooks';\nimport {\n CreatedAfterOptions,\n NotificationsFilters,\n SortBy,\n SortByOptions,\n} from '../NotificationsFilters';\nimport { GetNotificationsOptions } from '../../api';\nimport { NotificationSeverity } from '@backstage/plugin-notifications-common';\n\nconst ThrottleDelayMs = 2000;\n\n/** @public */\nexport type NotificationsPageProps = {\n title?: string;\n themeId?: string;\n subtitle?: string;\n tooltip?: string;\n type?: string;\n typeLink?: string;\n};\n\nexport const NotificationsPage = (props?: NotificationsPageProps) => {\n const {\n title = 'Notifications',\n themeId = 'tool',\n subtitle,\n tooltip,\n type,\n typeLink,\n } = props ?? {};\n\n const [refresh, setRefresh] = React.useState(false);\n const { lastSignal } = useSignal('notifications');\n const [unreadOnly, setUnreadOnly] = React.useState<boolean | undefined>(true);\n const [saved, setSaved] = React.useState<boolean | undefined>(undefined);\n const [pageNumber, setPageNumber] = React.useState(0);\n const [pageSize, setPageSize] = React.useState(5);\n const [containsText, setContainsText] = React.useState<string>();\n const [createdAfter, setCreatedAfter] = React.useState<string>('lastWeek');\n const [sorting, setSorting] = React.useState<SortBy>(\n SortByOptions.newest.sortBy,\n );\n const [severity, setSeverity] = React.useState<NotificationSeverity>('low');\n\n const { error, value, retry, loading } = useNotificationsApi(\n api => {\n const options: GetNotificationsOptions = {\n search: containsText,\n limit: pageSize,\n offset: pageNumber * pageSize,\n minimumSeverity: severity,\n ...(sorting || {}),\n };\n if (unreadOnly !== undefined) {\n options.read = !unreadOnly;\n }\n if (saved !== undefined) {\n options.saved = saved;\n }\n\n const createdAfterDate = CreatedAfterOptions[createdAfter].getDate();\n if (createdAfterDate.valueOf() > 0) {\n options.createdAfter = createdAfterDate;\n }\n\n return api.getNotifications(options);\n },\n [\n containsText,\n unreadOnly,\n createdAfter,\n pageNumber,\n pageSize,\n sorting,\n saved,\n severity,\n ],\n );\n\n const throttledSetRefresh = React.useMemo(\n () => throttle(setRefresh, ThrottleDelayMs),\n [setRefresh],\n );\n\n useEffect(() => {\n if (refresh && !loading) {\n retry();\n setRefresh(false);\n }\n }, [refresh, setRefresh, retry, loading]);\n\n useEffect(() => {\n if (lastSignal && lastSignal.action) {\n throttledSetRefresh(true);\n }\n }, [lastSignal, throttledSetRefresh]);\n\n const onUpdate = () => {\n throttledSetRefresh(true);\n };\n\n if (error) {\n return <ResponseErrorPanel error={error} />;\n }\n\n return (\n <PageWithHeader\n title={title}\n themeId={themeId}\n tooltip={tooltip}\n subtitle={subtitle}\n type={type}\n typeLink={typeLink}\n >\n <Content>\n <Grid container>\n <Grid item xs={2}>\n <NotificationsFilters\n unreadOnly={unreadOnly}\n onUnreadOnlyChanged={setUnreadOnly}\n createdAfter={createdAfter}\n onCreatedAfterChanged={setCreatedAfter}\n onSortingChanged={setSorting}\n sorting={sorting}\n saved={saved}\n onSavedChanged={setSaved}\n severity={severity}\n onSeverityChanged={setSeverity}\n />\n </Grid>\n <Grid item xs={10}>\n <NotificationsTable\n isLoading={loading}\n notifications={value?.notifications}\n onUpdate={onUpdate}\n setContainsText={setContainsText}\n onPageChange={setPageNumber}\n onRowsPerPageChange={setPageSize}\n page={pageNumber}\n pageSize={pageSize}\n totalCount={value?.totalCount}\n />\n </Grid>\n </Grid>\n </Content>\n </PageWithHeader>\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NotificationsPage.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 } from 'react';\nimport throttle from 'lodash/throttle';\nimport {\n Content,\n PageWithHeader,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport Grid from '@material-ui/core/Grid';\nimport { ConfirmProvider } from 'material-ui-confirm';\nimport { useSignal } from '@backstage/plugin-signals-react';\n\nimport { NotificationsTable } from '../NotificationsTable';\nimport { useNotificationsApi } from '../../hooks';\nimport {\n CreatedAfterOptions,\n NotificationsFilters,\n SortBy,\n SortByOptions,\n} from '../NotificationsFilters';\nimport { GetNotificationsOptions, GetNotificationsResponse } from '../../api';\nimport {\n NotificationSeverity,\n NotificationStatus,\n} from '@backstage/plugin-notifications-common';\n\nconst ThrottleDelayMs = 2000;\n\n/** @public */\nexport type NotificationsPageProps = {\n /** Mark notification as read when opening the link it contains, defaults to false */\n markAsReadOnLinkOpen?: boolean;\n title?: string;\n themeId?: string;\n subtitle?: string;\n tooltip?: string;\n type?: string;\n typeLink?: string;\n};\n\nexport const NotificationsPage = (props?: NotificationsPageProps) => {\n const {\n title = 'Notifications',\n themeId = 'tool',\n subtitle,\n tooltip,\n type,\n typeLink,\n markAsReadOnLinkOpen,\n } = props ?? {};\n\n const [refresh, setRefresh] = React.useState(false);\n const { lastSignal } = useSignal('notifications');\n const [unreadOnly, setUnreadOnly] = React.useState<boolean | undefined>(true);\n const [saved, setSaved] = React.useState<boolean | undefined>(undefined);\n const [pageNumber, setPageNumber] = React.useState(0);\n const [pageSize, setPageSize] = React.useState(5);\n const [containsText, setContainsText] = React.useState<string>();\n const [createdAfter, setCreatedAfter] = React.useState<string>('lastWeek');\n const [sorting, setSorting] = React.useState<SortBy>(\n SortByOptions.newest.sortBy,\n );\n const [severity, setSeverity] = React.useState<NotificationSeverity>('low');\n\n const { error, value, retry, loading } = useNotificationsApi<\n [GetNotificationsResponse, NotificationStatus]\n >(\n api => {\n const options: GetNotificationsOptions = {\n search: containsText,\n limit: pageSize,\n offset: pageNumber * pageSize,\n minimumSeverity: severity,\n ...(sorting || {}),\n };\n if (unreadOnly !== undefined) {\n options.read = !unreadOnly;\n }\n if (saved !== undefined) {\n options.saved = saved;\n }\n\n const createdAfterDate = CreatedAfterOptions[createdAfter].getDate();\n if (createdAfterDate.valueOf() > 0) {\n options.createdAfter = createdAfterDate;\n }\n\n return Promise.all([api.getNotifications(options), api.getStatus()]);\n },\n [\n containsText,\n unreadOnly,\n createdAfter,\n pageNumber,\n pageSize,\n sorting,\n saved,\n severity,\n ],\n );\n\n const throttledSetRefresh = React.useMemo(\n () => throttle(setRefresh, ThrottleDelayMs),\n [setRefresh],\n );\n\n useEffect(() => {\n if (refresh && !loading) {\n retry();\n setRefresh(false);\n }\n }, [refresh, setRefresh, retry, loading]);\n\n useEffect(() => {\n if (lastSignal && lastSignal.action) {\n throttledSetRefresh(true);\n }\n }, [lastSignal, throttledSetRefresh]);\n\n const onUpdate = () => {\n throttledSetRefresh(true);\n };\n\n if (error) {\n return <ResponseErrorPanel error={error} />;\n }\n\n const notifications = value?.[0]?.notifications;\n const totalCount = value?.[0]?.totalCount;\n const isUnread = !!value?.[1]?.unread;\n\n return (\n <PageWithHeader\n title={title}\n themeId={themeId}\n tooltip={tooltip}\n subtitle={subtitle}\n type={type}\n typeLink={typeLink}\n >\n <Content>\n <ConfirmProvider>\n <Grid container>\n <Grid item xs={2}>\n <NotificationsFilters\n unreadOnly={unreadOnly}\n onUnreadOnlyChanged={setUnreadOnly}\n createdAfter={createdAfter}\n onCreatedAfterChanged={setCreatedAfter}\n onSortingChanged={setSorting}\n sorting={sorting}\n saved={saved}\n onSavedChanged={setSaved}\n severity={severity}\n onSeverityChanged={setSeverity}\n />\n </Grid>\n <Grid item xs={10}>\n <NotificationsTable\n isLoading={loading}\n isUnread={isUnread}\n markAsReadOnLinkOpen={markAsReadOnLinkOpen}\n notifications={notifications}\n onUpdate={onUpdate}\n setContainsText={setContainsText}\n onPageChange={setPageNumber}\n onRowsPerPageChange={setPageSize}\n page={pageNumber}\n pageSize={pageSize}\n totalCount={totalCount}\n />\n </Grid>\n </Grid>\n </ConfirmProvider>\n </Content>\n </PageWithHeader>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -1,33 +1,103 @@
1
- import React, { useEffect } from 'react';
1
+ import React, { useCallback, useEffect } from 'react';
2
2
  import { useNotificationsApi } from '../../hooks/useNotificationsApi.esm.js';
3
3
  import { useWebNotifications } from '../../hooks/useWebNotifications.esm.js';
4
4
  import { useTitleCounter } from '../../hooks/useTitleCounter.esm.js';
5
- import { SidebarItem } from '@backstage/core-components';
5
+ import { SidebarItem, Link } from '@backstage/core-components';
6
6
  import NotificationsIcon from '@material-ui/icons/Notifications';
7
- import { useApi, useRouteRef } from '@backstage/core-plugin-api';
7
+ import { useApi, alertApiRef, useRouteRef } from '@backstage/core-plugin-api';
8
8
  import { rootRouteRef } from '../../routes.esm.js';
9
9
  import { useSignal } from '@backstage/plugin-signals-react';
10
10
  import { notificationsApiRef } from '../../api/NotificationsApi.esm.js';
11
11
  import '@backstage/errors';
12
+ import { MaterialDesignContent, SnackbarProvider, closeSnackbar, enqueueSnackbar } from 'notistack';
13
+ import { SeverityIcon } from '../NotificationsTable/SeverityIcon.esm.js';
14
+ import OpenInNew from '@material-ui/icons/OpenInNew';
15
+ import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
16
+ import IconButton from '@material-ui/core/IconButton';
17
+ import { styled } from '@material-ui/core/styles';
12
18
 
19
+ const StyledMaterialDesignContent = styled(MaterialDesignContent)(
20
+ ({ theme }) => ({
21
+ "&.notistack-MuiContent-low": {
22
+ backgroundColor: theme.palette.background.default,
23
+ color: theme.palette.text.primary
24
+ },
25
+ "&.notistack-MuiContent-normal": {
26
+ backgroundColor: theme.palette.background.default,
27
+ color: theme.palette.text.primary
28
+ },
29
+ "&.notistack-MuiContent-high": {
30
+ backgroundColor: theme.palette.background.default,
31
+ color: theme.palette.text.primary
32
+ },
33
+ "&.notistack-MuiContent-critical": {
34
+ backgroundColor: theme.palette.background.default,
35
+ color: theme.palette.text.primary
36
+ }
37
+ })
38
+ );
13
39
  const NotificationsSidebarItem = (props) => {
14
40
  const {
15
41
  webNotificationsEnabled = false,
16
42
  titleCounterEnabled = true,
43
+ snackbarEnabled = true,
17
44
  icon = NotificationsIcon,
18
45
  text = "Notifications",
19
46
  ...restProps
20
- } = props != null ? props : { webNotificationsEnabled: false, titleCounterEnabled: true };
47
+ } = props != null ? props : {
48
+ webNotificationsEnabled: false,
49
+ titleCounterEnabled: true,
50
+ snackbarEnabled: true
51
+ };
21
52
  const { loading, error, value, retry } = useNotificationsApi(
22
53
  (api) => api.getStatus()
23
54
  );
24
55
  const notificationsApi = useApi(notificationsApiRef);
56
+ const alertApi = useApi(alertApiRef);
25
57
  const [unreadCount, setUnreadCount] = React.useState(0);
26
58
  const notificationsRoute = useRouteRef(rootRouteRef);
27
59
  const { lastSignal } = useSignal("notifications");
28
60
  const { sendWebNotification } = useWebNotifications(webNotificationsEnabled);
29
61
  const [refresh, setRefresh] = React.useState(false);
30
62
  const { setNotificationCount } = useTitleCounter();
63
+ const getSnackbarProperties = useCallback(
64
+ (notification) => {
65
+ const action = (snackBarId) => {
66
+ var _a;
67
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
68
+ IconButton,
69
+ {
70
+ component: Link,
71
+ to: (_a = notification.payload.link) != null ? _a : notificationsRoute(),
72
+ onClick: () => {
73
+ closeSnackbar(snackBarId);
74
+ }
75
+ },
76
+ /* @__PURE__ */ React.createElement(OpenInNew, { fontSize: "small" })
77
+ ), /* @__PURE__ */ React.createElement(
78
+ IconButton,
79
+ {
80
+ onClick: () => {
81
+ notificationsApi.updateNotifications({
82
+ ids: [notification.id],
83
+ read: true
84
+ }).then(() => {
85
+ closeSnackbar(snackBarId);
86
+ }).catch(() => {
87
+ alertApi.post({
88
+ message: "Failed to mark notification as read",
89
+ severity: "error"
90
+ });
91
+ });
92
+ }
93
+ },
94
+ /* @__PURE__ */ React.createElement(MarkAsReadIcon, { fontSize: "small" })
95
+ ));
96
+ };
97
+ return { action };
98
+ },
99
+ [notificationsRoute, notificationsApi, alertApi]
100
+ );
31
101
  useEffect(() => {
32
102
  if (refresh) {
33
103
  retry();
@@ -35,8 +105,8 @@ const NotificationsSidebarItem = (props) => {
35
105
  }
36
106
  }, [refresh, retry]);
37
107
  useEffect(() => {
38
- const handleWebNotification = (signal) => {
39
- if (!webNotificationsEnabled || signal.action !== "new_notification") {
108
+ const handleNotificationSignal = (signal) => {
109
+ if (!webNotificationsEnabled && !snackbarEnabled || signal.action !== "new_notification") {
40
110
  return;
41
111
  }
42
112
  notificationsApi.getNotification(signal.notification_id).then((notification) => {
@@ -44,23 +114,42 @@ const NotificationsSidebarItem = (props) => {
44
114
  if (!notification) {
45
115
  return;
46
116
  }
47
- sendWebNotification({
48
- id: notification.id,
49
- title: notification.payload.title,
50
- description: (_a = notification.payload.description) != null ? _a : "",
51
- link: notification.payload.link
117
+ if (webNotificationsEnabled) {
118
+ sendWebNotification({
119
+ id: notification.id,
120
+ title: notification.payload.title,
121
+ description: (_a = notification.payload.description) != null ? _a : "",
122
+ link: notification.payload.link
123
+ });
124
+ }
125
+ if (snackbarEnabled) {
126
+ const { action } = getSnackbarProperties(notification);
127
+ const snackBarText = notification.payload.title.length > 50 ? `${notification.payload.title.substring(0, 50)}...` : notification.payload.title;
128
+ enqueueSnackbar(snackBarText, {
129
+ variant: notification.payload.severity,
130
+ anchorOrigin: { vertical: "bottom", horizontal: "right" },
131
+ action
132
+ });
133
+ }
134
+ }).catch(() => {
135
+ alertApi.post({
136
+ message: "Failed to fetch notification",
137
+ severity: "error"
52
138
  });
53
139
  });
54
140
  };
55
141
  if (lastSignal && lastSignal.action) {
56
- handleWebNotification(lastSignal);
142
+ handleNotificationSignal(lastSignal);
57
143
  setRefresh(true);
58
144
  }
59
145
  }, [
60
146
  lastSignal,
61
147
  sendWebNotification,
62
148
  webNotificationsEnabled,
63
- notificationsApi
149
+ snackbarEnabled,
150
+ notificationsApi,
151
+ alertApi,
152
+ getSnackbarProperties
64
153
  ]);
65
154
  useEffect(() => {
66
155
  if (!loading && !error && value) {
@@ -72,7 +161,23 @@ const NotificationsSidebarItem = (props) => {
72
161
  setNotificationCount(unreadCount);
73
162
  }
74
163
  }, [titleCounterEnabled, unreadCount, setNotificationCount]);
75
- return /* @__PURE__ */ React.createElement(
164
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, snackbarEnabled && /* @__PURE__ */ React.createElement(
165
+ SnackbarProvider,
166
+ {
167
+ iconVariant: {
168
+ normal: /* @__PURE__ */ React.createElement(SeverityIcon, { severity: "normal" }),
169
+ critical: /* @__PURE__ */ React.createElement(SeverityIcon, { severity: "critical" }),
170
+ high: /* @__PURE__ */ React.createElement(SeverityIcon, { severity: "high" }),
171
+ low: /* @__PURE__ */ React.createElement(SeverityIcon, { severity: "low" })
172
+ },
173
+ Components: {
174
+ normal: StyledMaterialDesignContent,
175
+ critical: StyledMaterialDesignContent,
176
+ high: StyledMaterialDesignContent,
177
+ low: StyledMaterialDesignContent
178
+ }
179
+ }
180
+ ), /* @__PURE__ */ React.createElement(
76
181
  SidebarItem,
77
182
  {
78
183
  to: notificationsRoute(),
@@ -81,7 +186,7 @@ const NotificationsSidebarItem = (props) => {
81
186
  icon,
82
187
  ...restProps
83
188
  }
84
- );
189
+ ));
85
190
  };
86
191
 
87
192
  export { NotificationsSidebarItem };
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationsSideBarItem.esm.js","sources":["../../../src/components/NotificationsSideBarItem/NotificationsSideBarItem.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 React, { useEffect } from 'react';\nimport { useNotificationsApi } from '../../hooks';\nimport { SidebarItem } from '@backstage/core-components';\nimport NotificationsIcon from '@material-ui/icons/Notifications';\nimport { IconComponent, useApi, useRouteRef } from '@backstage/core-plugin-api';\nimport { rootRouteRef } from '../../routes';\nimport { useSignal } from '@backstage/plugin-signals-react';\nimport { NotificationSignal } from '@backstage/plugin-notifications-common';\nimport { useWebNotifications } from '../../hooks/useWebNotifications';\nimport { useTitleCounter } from '../../hooks/useTitleCounter';\nimport { notificationsApiRef } from '../../api';\n\n/** @public */\nexport const NotificationsSidebarItem = (props?: {\n webNotificationsEnabled?: boolean;\n titleCounterEnabled?: boolean;\n className?: string;\n icon?: IconComponent;\n text?: string;\n disableHighlight?: boolean;\n noTrack?: boolean;\n}) => {\n const {\n webNotificationsEnabled = false,\n titleCounterEnabled = true,\n icon = NotificationsIcon,\n text = 'Notifications',\n ...restProps\n } = props ?? { webNotificationsEnabled: false, titleCounterEnabled: true };\n\n const { loading, error, value, retry } = useNotificationsApi(api =>\n api.getStatus(),\n );\n const notificationsApi = useApi(notificationsApiRef);\n const [unreadCount, setUnreadCount] = React.useState(0);\n const notificationsRoute = useRouteRef(rootRouteRef);\n // TODO: Do we want to add long polling in case signals are not available\n const { lastSignal } = useSignal<NotificationSignal>('notifications');\n const { sendWebNotification } = useWebNotifications(webNotificationsEnabled);\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: NotificationSignal) => {\n if (!webNotificationsEnabled || signal.action !== 'new_notification') {\n return;\n }\n\n notificationsApi\n .getNotification(signal.notification_id)\n .then(notification => {\n if (!notification) {\n return;\n }\n sendWebNotification({\n id: notification.id,\n title: notification.payload.title,\n description: notification.payload.description ?? '',\n link: notification.payload.link,\n });\n });\n };\n\n if (lastSignal && lastSignal.action) {\n handleWebNotification(lastSignal);\n setRefresh(true);\n }\n }, [\n lastSignal,\n sendWebNotification,\n webNotificationsEnabled,\n notificationsApi,\n ]);\n\n useEffect(() => {\n if (!loading && !error && value) {\n setUnreadCount(value.unread);\n }\n }, [loading, error, value]);\n\n useEffect(() => {\n if (titleCounterEnabled) {\n setNotificationCount(unreadCount);\n }\n }, [titleCounterEnabled, unreadCount, setNotificationCount]);\n\n // TODO: Figure out if the count can be added to hasNotifications\n return (\n <SidebarItem\n to={notificationsRoute()}\n hasNotifications={!error && !!unreadCount}\n text={text}\n icon={icon}\n {...restProps}\n />\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NotificationsSideBarItem.esm.js","sources":["../../../src/components/NotificationsSideBarItem/NotificationsSideBarItem.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 React, { useCallback, useEffect } from 'react';\nimport { useNotificationsApi } from '../../hooks';\nimport { Link, SidebarItem } from '@backstage/core-components';\nimport NotificationsIcon from '@material-ui/icons/Notifications';\nimport {\n alertApiRef,\n IconComponent,\n useApi,\n useRouteRef,\n} from '@backstage/core-plugin-api';\nimport { rootRouteRef } from '../../routes';\nimport { useSignal } from '@backstage/plugin-signals-react';\nimport {\n Notification,\n NotificationSignal,\n} from '@backstage/plugin-notifications-common';\nimport { useWebNotifications } from '../../hooks/useWebNotifications';\nimport { useTitleCounter } from '../../hooks/useTitleCounter';\nimport { notificationsApiRef } from '../../api';\nimport {\n closeSnackbar,\n enqueueSnackbar,\n MaterialDesignContent,\n OptionsWithExtraProps,\n SnackbarKey,\n SnackbarProvider,\n VariantType,\n} from 'notistack';\nimport { SeverityIcon } from '../NotificationsTable/SeverityIcon';\nimport OpenInNew from '@material-ui/icons/OpenInNew';\nimport MarkAsReadIcon from '@material-ui/icons/CheckCircle';\nimport IconButton from '@material-ui/core/IconButton';\nimport { styled } from '@material-ui/core/styles';\n\nconst StyledMaterialDesignContent = styled(MaterialDesignContent)(\n ({ theme }) => ({\n '&.notistack-MuiContent-low': {\n backgroundColor: theme.palette.background.default,\n color: theme.palette.text.primary,\n },\n '&.notistack-MuiContent-normal': {\n backgroundColor: theme.palette.background.default,\n color: theme.palette.text.primary,\n },\n '&.notistack-MuiContent-high': {\n backgroundColor: theme.palette.background.default,\n color: theme.palette.text.primary,\n },\n '&.notistack-MuiContent-critical': {\n backgroundColor: theme.palette.background.default,\n color: theme.palette.text.primary,\n },\n }),\n);\n\ndeclare module 'notistack' {\n interface VariantOverrides {\n // Custom variants for the snackbar\n low: true;\n normal: true;\n high: true;\n critical: true;\n }\n}\n\n/** @public */\nexport const NotificationsSidebarItem = (props?: {\n webNotificationsEnabled?: boolean;\n titleCounterEnabled?: boolean;\n snackbarEnabled?: boolean;\n className?: string;\n icon?: IconComponent;\n text?: string;\n disableHighlight?: boolean;\n noTrack?: boolean;\n}) => {\n const {\n webNotificationsEnabled = false,\n titleCounterEnabled = true,\n snackbarEnabled = true,\n icon = NotificationsIcon,\n text = 'Notifications',\n ...restProps\n } = props ?? {\n webNotificationsEnabled: false,\n titleCounterEnabled: true,\n snackbarEnabled: true,\n };\n\n const { loading, error, value, retry } = useNotificationsApi(api =>\n api.getStatus(),\n );\n const notificationsApi = useApi(notificationsApiRef);\n const alertApi = useApi(alertApiRef);\n const [unreadCount, setUnreadCount] = React.useState(0);\n const notificationsRoute = useRouteRef(rootRouteRef);\n // TODO: Do we want to add long polling in case signals are not available\n const { lastSignal } = useSignal<NotificationSignal>('notifications');\n const { sendWebNotification } = useWebNotifications(webNotificationsEnabled);\n const [refresh, setRefresh] = React.useState(false);\n const { setNotificationCount } = useTitleCounter();\n\n const getSnackbarProperties = useCallback(\n (notification: Notification) => {\n const action = (snackBarId: SnackbarKey) => (\n <>\n <IconButton\n component={Link}\n to={notification.payload.link ?? notificationsRoute()}\n onClick={() => {\n closeSnackbar(snackBarId);\n }}\n >\n <OpenInNew fontSize=\"small\" />\n </IconButton>\n <IconButton\n onClick={() => {\n notificationsApi\n .updateNotifications({\n ids: [notification.id],\n read: true,\n })\n .then(() => {\n closeSnackbar(snackBarId);\n })\n .catch(() => {\n alertApi.post({\n message: 'Failed to mark notification as read',\n severity: 'error',\n });\n });\n }}\n >\n <MarkAsReadIcon fontSize=\"small\" />\n </IconButton>\n </>\n );\n\n return { action };\n },\n [notificationsRoute, notificationsApi, alertApi],\n );\n\n useEffect(() => {\n if (refresh) {\n retry();\n setRefresh(false);\n }\n }, [refresh, retry]);\n\n useEffect(() => {\n const handleNotificationSignal = (signal: NotificationSignal) => {\n if (\n (!webNotificationsEnabled && !snackbarEnabled) ||\n signal.action !== 'new_notification'\n ) {\n return;\n }\n\n notificationsApi\n .getNotification(signal.notification_id)\n .then(notification => {\n if (!notification) {\n return;\n }\n if (webNotificationsEnabled) {\n sendWebNotification({\n id: notification.id,\n title: notification.payload.title,\n description: notification.payload.description ?? '',\n link: notification.payload.link,\n });\n }\n if (snackbarEnabled) {\n const { action } = getSnackbarProperties(notification);\n const snackBarText =\n notification.payload.title.length > 50\n ? `${notification.payload.title.substring(0, 50)}...`\n : notification.payload.title;\n enqueueSnackbar(snackBarText, {\n variant: notification.payload.severity,\n anchorOrigin: { vertical: 'bottom', horizontal: 'right' },\n action,\n } as OptionsWithExtraProps<VariantType>);\n }\n })\n .catch(() => {\n alertApi.post({\n message: 'Failed to fetch notification',\n severity: 'error',\n });\n });\n };\n\n if (lastSignal && lastSignal.action) {\n handleNotificationSignal(lastSignal);\n setRefresh(true);\n }\n }, [\n lastSignal,\n sendWebNotification,\n webNotificationsEnabled,\n snackbarEnabled,\n notificationsApi,\n alertApi,\n getSnackbarProperties,\n ]);\n\n useEffect(() => {\n if (!loading && !error && value) {\n setUnreadCount(value.unread);\n }\n }, [loading, error, value]);\n\n useEffect(() => {\n if (titleCounterEnabled) {\n setNotificationCount(unreadCount);\n }\n }, [titleCounterEnabled, unreadCount, setNotificationCount]);\n\n // TODO: Figure out if the count can be added to hasNotifications\n return (\n <>\n {snackbarEnabled && (\n <SnackbarProvider\n iconVariant={{\n normal: <SeverityIcon severity=\"normal\" />,\n critical: <SeverityIcon severity=\"critical\" />,\n high: <SeverityIcon severity=\"high\" />,\n low: <SeverityIcon severity=\"low\" />,\n }}\n Components={{\n normal: StyledMaterialDesignContent,\n critical: StyledMaterialDesignContent,\n high: StyledMaterialDesignContent,\n low: StyledMaterialDesignContent,\n }}\n />\n )}\n <SidebarItem\n to={notificationsRoute()}\n hasNotifications={!error && !!unreadCount}\n text={text}\n icon={icon}\n {...restProps}\n />\n </>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -6,12 +6,15 @@ import MarkAsUnreadIcon from '@material-ui/icons/Markunread';
6
6
  import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
7
7
  import MarkAsUnsavedIcon from '@material-ui/icons/LabelOff';
8
8
  import MarkAsSavedIcon from '@material-ui/icons/Label';
9
+ import MarkAllReadIcon from '@material-ui/icons/DoneAll';
9
10
 
10
11
  const BulkActions = ({
11
12
  selectedNotifications,
12
13
  notifications,
14
+ isUnread,
13
15
  onSwitchReadStatus,
14
- onSwitchSavedStatus
16
+ onSwitchSavedStatus,
17
+ onMarkAllRead
15
18
  }) => {
16
19
  const isDisabled = selectedNotifications.size === 0;
17
20
  const bulkNotifications = notifications.filter(
@@ -27,7 +30,7 @@ const BulkActions = ({
27
30
  const IconComponent = isOneRead ? MarkAsUnreadIcon : MarkAsReadIcon;
28
31
  const markAsSavedText = isOneSaved ? "Undo save for selected" : "Save selected for later";
29
32
  const SavedIconComponent = isOneSaved ? MarkAsUnsavedIcon : MarkAsSavedIcon;
30
- return /* @__PURE__ */ React.createElement(Grid, { container: true, wrap: "nowrap" }, /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Tooltip, { title: markAsSavedText }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
33
+ return /* @__PURE__ */ React.createElement(Grid, { container: true, wrap: "nowrap" }, /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 3 }, onMarkAllRead ? /* @__PURE__ */ React.createElement(Tooltip, { title: "Mark all read" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(IconButton, { disabled: !isUnread, onClick: onMarkAllRead }, /* @__PURE__ */ React.createElement(MarkAllReadIcon, { "aria-label": markAsSavedText })))) : /* @__PURE__ */ React.createElement("div", null)), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 3 }, /* @__PURE__ */ React.createElement(Tooltip, { title: markAsSavedText }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
31
34
  IconButton,
32
35
  {
33
36
  disabled: isDisabled,
@@ -36,7 +39,7 @@ const BulkActions = ({
36
39
  }
37
40
  },
38
41
  /* @__PURE__ */ React.createElement(SavedIconComponent, { "aria-label": markAsSavedText })
39
- )))), /* @__PURE__ */ React.createElement(Grid, { item: true }, /* @__PURE__ */ React.createElement(Tooltip, { title: markAsReadText }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
42
+ )))), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 3 }, /* @__PURE__ */ React.createElement(Tooltip, { title: markAsReadText }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
40
43
  IconButton,
41
44
  {
42
45
  disabled: isDisabled,
@@ -1 +1 @@
1
- {"version":3,"file":"BulkActions.esm.js","sources":["../../../src/components/NotificationsTable/BulkActions.tsx"],"sourcesContent":["/*\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 React from 'react';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport Grid from '@material-ui/core/Grid';\nimport IconButton from '@material-ui/core/IconButton';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport MarkAsUnreadIcon from '@material-ui/icons/Markunread' /* TODO: use Drafts and MarkAsUnread once we have mui 5 icons */;\nimport MarkAsReadIcon from '@material-ui/icons/CheckCircle';\nimport MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */;\nimport MarkAsSavedIcon from '@material-ui/icons/Label';\n\nexport const BulkActions = ({\n selectedNotifications,\n notifications,\n onSwitchReadStatus,\n onSwitchSavedStatus,\n}: {\n selectedNotifications: Set<Notification['id']>;\n notifications: Notification[];\n onSwitchReadStatus: (ids: Notification['id'][], newStatus: boolean) => void;\n onSwitchSavedStatus: (ids: Notification['id'][], newStatus: boolean) => void;\n}) => {\n const isDisabled = selectedNotifications.size === 0;\n const bulkNotifications = notifications.filter(notification =>\n selectedNotifications.has(notification.id),\n );\n\n const isOneRead = !!bulkNotifications.find(\n (notification: Notification) => !!notification.read,\n );\n const isOneSaved = !!bulkNotifications.find(\n (notification: Notification) => !!notification.saved,\n );\n\n const markAsReadText = isOneRead\n ? 'Return selected among unread'\n : 'Mark selected as read';\n const IconComponent = isOneRead ? MarkAsUnreadIcon : MarkAsReadIcon;\n\n const markAsSavedText = isOneSaved\n ? 'Undo save for selected'\n : 'Save selected for later';\n const SavedIconComponent = isOneSaved ? MarkAsUnsavedIcon : MarkAsSavedIcon;\n\n return (\n <Grid container wrap=\"nowrap\">\n <Grid item>\n <Tooltip title={markAsSavedText}>\n <div>\n {/* The <div> here is a workaround for the Tooltip which does not work for a \"disabled\" child */}\n <IconButton\n disabled={isDisabled}\n onClick={() => {\n onSwitchSavedStatus([...selectedNotifications], !isOneSaved);\n }}\n >\n <SavedIconComponent aria-label={markAsSavedText} />\n </IconButton>\n </div>\n </Tooltip>\n </Grid>\n\n <Grid item>\n <Tooltip title={markAsReadText}>\n <div>\n <IconButton\n disabled={isDisabled}\n onClick={() => {\n onSwitchReadStatus([...selectedNotifications], !isOneRead);\n }}\n >\n <IconComponent aria-label={markAsReadText} />\n </IconButton>\n </div>\n </Tooltip>\n </Grid>\n </Grid>\n );\n};\n"],"names":[],"mappings":";;;;;;;;;AAyBO,MAAM,cAAc,CAAC;AAAA,EAC1B,qBAAA;AAAA,EACA,aAAA;AAAA,EACA,kBAAA;AAAA,EACA,mBAAA;AACF,CAKM,KAAA;AACJ,EAAM,MAAA,UAAA,GAAa,sBAAsB,IAAS,KAAA,CAAA,CAAA;AAClD,EAAA,MAAM,oBAAoB,aAAc,CAAA,MAAA;AAAA,IAAO,CAC7C,YAAA,KAAA,qBAAA,CAAsB,GAAI,CAAA,YAAA,CAAa,EAAE,CAAA;AAAA,GAC3C,CAAA;AAEA,EAAM,MAAA,SAAA,GAAY,CAAC,CAAC,iBAAkB,CAAA,IAAA;AAAA,IACpC,CAAC,YAAA,KAA+B,CAAC,CAAC,YAAa,CAAA,IAAA;AAAA,GACjD,CAAA;AACA,EAAM,MAAA,UAAA,GAAa,CAAC,CAAC,iBAAkB,CAAA,IAAA;AAAA,IACrC,CAAC,YAAA,KAA+B,CAAC,CAAC,YAAa,CAAA,KAAA;AAAA,GACjD,CAAA;AAEA,EAAM,MAAA,cAAA,GAAiB,YACnB,8BACA,GAAA,uBAAA,CAAA;AACJ,EAAM,MAAA,aAAA,GAAgB,YAAY,gBAAmB,GAAA,cAAA,CAAA;AAErD,EAAM,MAAA,eAAA,GAAkB,aACpB,wBACA,GAAA,yBAAA,CAAA;AACJ,EAAM,MAAA,kBAAA,GAAqB,aAAa,iBAAoB,GAAA,eAAA,CAAA;AAE5D,EAAA,uBACG,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,SAAS,EAAA,IAAA,EAAC,MAAK,QACnB,EAAA,kBAAA,KAAA,CAAA,aAAA,CAAC,IAAK,EAAA,EAAA,IAAA,EAAI,wBACP,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAQ,KAAO,EAAA,eAAA,EAAA,sCACb,KAEC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,QAAU,EAAA,UAAA;AAAA,MACV,SAAS,MAAM;AACb,QAAA,mBAAA,CAAoB,CAAC,GAAG,qBAAqB,CAAA,EAAG,CAAC,UAAU,CAAA,CAAA;AAAA,OAC7D;AAAA,KAAA;AAAA,oBAEA,KAAA,CAAA,aAAA,CAAC,kBAAmB,EAAA,EAAA,YAAA,EAAY,eAAiB,EAAA,CAAA;AAAA,GAErD,CACF,CACF,CAAA,kBAEC,KAAA,CAAA,aAAA,CAAA,IAAA,EAAA,EAAK,IAAI,EAAA,IAAA,EAAA,kBACP,KAAA,CAAA,aAAA,CAAA,OAAA,EAAA,EAAQ,KAAO,EAAA,cAAA,EAAA,sCACb,KACC,EAAA,IAAA,kBAAA,KAAA,CAAA,aAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,QAAU,EAAA,UAAA;AAAA,MACV,SAAS,MAAM;AACb,QAAA,kBAAA,CAAmB,CAAC,GAAG,qBAAqB,CAAA,EAAG,CAAC,SAAS,CAAA,CAAA;AAAA,OAC3D;AAAA,KAAA;AAAA,oBAEA,KAAA,CAAA,aAAA,CAAC,aAAc,EAAA,EAAA,YAAA,EAAY,cAAgB,EAAA,CAAA;AAAA,GAE/C,CACF,CACF,CACF,CAAA,CAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"BulkActions.esm.js","sources":["../../../src/components/NotificationsTable/BulkActions.tsx"],"sourcesContent":["/*\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 React from 'react';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport Grid from '@material-ui/core/Grid';\nimport IconButton from '@material-ui/core/IconButton';\nimport Tooltip from '@material-ui/core/Tooltip';\nimport MarkAsUnreadIcon from '@material-ui/icons/Markunread' /* TODO: use Drafts and MarkAsUnread once we have mui 5 icons */;\nimport MarkAsReadIcon from '@material-ui/icons/CheckCircle';\nimport MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */;\nimport MarkAsSavedIcon from '@material-ui/icons/Label';\nimport MarkAllReadIcon from '@material-ui/icons/DoneAll';\n\nexport const BulkActions = ({\n selectedNotifications,\n notifications,\n isUnread,\n onSwitchReadStatus,\n onSwitchSavedStatus,\n onMarkAllRead,\n}: {\n selectedNotifications: Set<Notification['id']>;\n notifications: Notification[];\n isUnread?: boolean;\n onSwitchReadStatus: (ids: Notification['id'][], newStatus: boolean) => void;\n onSwitchSavedStatus: (ids: Notification['id'][], newStatus: boolean) => void;\n onMarkAllRead?: () => void;\n}) => {\n const isDisabled = selectedNotifications.size === 0;\n const bulkNotifications = notifications.filter(notification =>\n selectedNotifications.has(notification.id),\n );\n\n const isOneRead = !!bulkNotifications.find(\n (notification: Notification) => !!notification.read,\n );\n const isOneSaved = !!bulkNotifications.find(\n (notification: Notification) => !!notification.saved,\n );\n\n const markAsReadText = isOneRead\n ? 'Return selected among unread'\n : 'Mark selected as read';\n const IconComponent = isOneRead ? MarkAsUnreadIcon : MarkAsReadIcon;\n\n const markAsSavedText = isOneSaved\n ? 'Undo save for selected'\n : 'Save selected for later';\n const SavedIconComponent = isOneSaved ? MarkAsUnsavedIcon : MarkAsSavedIcon;\n\n return (\n <Grid container wrap=\"nowrap\">\n <Grid item xs={3}>\n {onMarkAllRead ? (\n <Tooltip title=\"Mark all read\">\n <div>\n {/* The <div> here is a workaround for the Tooltip which does not work for a \"disabled\" child */}\n <IconButton disabled={!isUnread} onClick={onMarkAllRead}>\n <MarkAllReadIcon aria-label={markAsSavedText} />\n </IconButton>\n </div>\n </Tooltip>\n ) : (\n <div />\n )}\n </Grid>\n\n <Grid item xs={3}>\n <Tooltip title={markAsSavedText}>\n <div>\n {/* The <div> here is a workaround for the Tooltip which does not work for a \"disabled\" child */}\n <IconButton\n disabled={isDisabled}\n onClick={() => {\n onSwitchSavedStatus([...selectedNotifications], !isOneSaved);\n }}\n >\n <SavedIconComponent aria-label={markAsSavedText} />\n </IconButton>\n </div>\n </Tooltip>\n </Grid>\n\n <Grid item xs={3}>\n <Tooltip title={markAsReadText}>\n <div>\n <IconButton\n disabled={isDisabled}\n onClick={() => {\n onSwitchReadStatus([...selectedNotifications], !isOneRead);\n }}\n >\n <IconComponent aria-label={markAsReadText} />\n </IconButton>\n </div>\n </Tooltip>\n </Grid>\n </Grid>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -6,10 +6,11 @@ import Grid from '@material-ui/core/Grid';
6
6
  import Checkbox from '@material-ui/core/Checkbox';
7
7
  import Typography from '@material-ui/core/Typography';
8
8
  import { makeStyles } from '@material-ui/core/styles';
9
+ import { useConfirm } from 'material-ui-confirm';
10
+ import { useApi, alertApiRef } from '@backstage/core-plugin-api';
11
+ import { Link, Table } from '@backstage/core-components';
9
12
  import { notificationsApiRef } from '../../api/NotificationsApi.esm.js';
10
13
  import '@backstage/errors';
11
- import { useApi } from '@backstage/core-plugin-api';
12
- import { Link, Table } from '@backstage/core-components';
13
14
  import { SeverityIcon } from './SeverityIcon.esm.js';
14
15
  import { SelectAll } from './SelectAll.esm.js';
15
16
  import { BulkActions } from './BulkActions.esm.js';
@@ -25,8 +26,10 @@ const useStyles = makeStyles({
25
26
  }
26
27
  });
27
28
  const NotificationsTable = ({
29
+ markAsReadOnLinkOpen,
28
30
  isLoading,
29
31
  notifications = [],
32
+ isUnread,
30
33
  onUpdate,
31
34
  setContainsText,
32
35
  onPageChange,
@@ -37,6 +40,8 @@ const NotificationsTable = ({
37
40
  }) => {
38
41
  const classes = useStyles();
39
42
  const notificationsApi = useApi(notificationsApiRef);
43
+ const alertApi = useApi(alertApiRef);
44
+ const confirm = useConfirm();
40
45
  const [selectedNotifications, setSelectedNotifications] = React.useState(
41
46
  /* @__PURE__ */ new Set()
42
47
  );
@@ -71,6 +76,27 @@ const NotificationsTable = ({
71
76
  },
72
77
  [notificationsApi, onUpdate]
73
78
  );
79
+ const onMarkAllRead = React.useCallback(() => {
80
+ confirm({
81
+ title: "Are you sure?",
82
+ description: /* @__PURE__ */ React.createElement(React.Fragment, null, "Mark ", /* @__PURE__ */ React.createElement("b", null, "all"), " notifications as ", /* @__PURE__ */ React.createElement("b", null, "read"), "."),
83
+ confirmationText: "Mark All"
84
+ }).then(async () => {
85
+ var _a;
86
+ const ids = (_a = (await notificationsApi.getNotifications({ read: false })).notifications) == null ? void 0 : _a.map((notification) => notification.id);
87
+ return notificationsApi.updateNotifications({
88
+ ids,
89
+ read: true
90
+ }).then(onUpdate);
91
+ }).catch((e) => {
92
+ if (e) {
93
+ alertApi.post({
94
+ message: "Failed to mark all notifications as read",
95
+ severity: "error"
96
+ });
97
+ }
98
+ });
99
+ }, [alertApi, confirm, notificationsApi, onUpdate]);
74
100
  const throttledContainsTextHandler = React.useMemo(
75
101
  () => throttle(setContainsText, ThrottleDelayMs),
76
102
  [setContainsText]
@@ -114,7 +140,18 @@ const NotificationsTable = ({
114
140
  customFilterAndSearch: () => true,
115
141
  render: (notification) => {
116
142
  var _a;
117
- return /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, className: classes.severityItem }, /* @__PURE__ */ React.createElement(SeverityIcon, { severity: (_a = notification.payload) == null ? void 0 : _a.severity })), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 11 }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2" }, notification.payload.link ? /* @__PURE__ */ React.createElement(Link, { to: notification.payload.link }, notification.payload.title) : notification.payload.title), notification.payload.description ? /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.description }, notification.payload.description) : null, /* @__PURE__ */ React.createElement(Typography, { variant: "caption" }, notification.origin && /* @__PURE__ */ React.createElement(React.Fragment, null, notification.origin, "\xA0\u2022\xA0"), notification.payload.topic && /* @__PURE__ */ React.createElement(React.Fragment, null, notification.payload.topic, "\xA0\u2022\xA0"), notification.created && /* @__PURE__ */ React.createElement(RelativeTime, { value: notification.created })))));
143
+ return /* @__PURE__ */ React.createElement(Grid, { container: true }, /* @__PURE__ */ React.createElement(Grid, { item: true, className: classes.severityItem }, /* @__PURE__ */ React.createElement(SeverityIcon, { severity: (_a = notification.payload) == null ? void 0 : _a.severity })), /* @__PURE__ */ React.createElement(Grid, { item: true, xs: 11 }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Typography, { variant: "subtitle2" }, notification.payload.link ? /* @__PURE__ */ React.createElement(
144
+ Link,
145
+ {
146
+ to: notification.payload.link,
147
+ onClick: () => {
148
+ if (markAsReadOnLinkOpen && !notification.read) {
149
+ onSwitchReadStatus([notification.id], true);
150
+ }
151
+ }
152
+ },
153
+ notification.payload.title
154
+ ) : notification.payload.title), notification.payload.description ? /* @__PURE__ */ React.createElement(Typography, { variant: "body2", className: classes.description }, notification.payload.description) : null, /* @__PURE__ */ React.createElement(Typography, { variant: "caption" }, notification.origin && /* @__PURE__ */ React.createElement(React.Fragment, null, notification.origin, "\xA0\u2022\xA0"), notification.payload.topic && /* @__PURE__ */ React.createElement(React.Fragment, null, notification.payload.topic, "\xA0\u2022\xA0"), notification.created && /* @__PURE__ */ React.createElement(RelativeTime, { value: notification.created })))));
118
155
  }
119
156
  },
120
157
  {
@@ -125,8 +162,10 @@ const NotificationsTable = ({
125
162
  {
126
163
  notifications,
127
164
  selectedNotifications,
165
+ isUnread,
128
166
  onSwitchReadStatus,
129
- onSwitchSavedStatus
167
+ onSwitchSavedStatus,
168
+ onMarkAllRead
130
169
  }
131
170
  ),
132
171
  render: (notification) => /* @__PURE__ */ React.createElement(
@@ -141,10 +180,13 @@ const NotificationsTable = ({
141
180
  }
142
181
  ],
143
182
  [
183
+ markAsReadOnLinkOpen,
144
184
  selectedNotifications,
145
185
  notifications,
186
+ isUnread,
146
187
  onSwitchReadStatus,
147
188
  onSwitchSavedStatus,
189
+ onMarkAllRead,
148
190
  onNotificationsSelectChange,
149
191
  classes.severityItem,
150
192
  classes.description
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationsTable.esm.js","sources":["../../../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 React from 'react';\nimport throttle from 'lodash/throttle';\n// @ts-ignore\nimport RelativeTime from 'react-relative-time';\nimport Box from '@material-ui/core/Box';\nimport Grid from '@material-ui/core/Grid';\nimport CheckBox from '@material-ui/core/Checkbox';\nimport Typography from '@material-ui/core/Typography';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { Notification } from '@backstage/plugin-notifications-common';\n\nimport { notificationsApiRef } from '../../api';\nimport { useApi } from '@backstage/core-plugin-api';\nimport {\n Link,\n Table,\n TableProps,\n TableColumn,\n} from '@backstage/core-components';\n\nimport { SeverityIcon } from './SeverityIcon';\nimport { SelectAll } from './SelectAll';\nimport { BulkActions } from './BulkActions';\n\nconst ThrottleDelayMs = 1000;\n\nconst useStyles = makeStyles({\n description: {\n maxHeight: '5rem',\n overflow: 'scroll',\n },\n severityItem: {\n alignContent: 'center',\n },\n});\n\n/** @public */\nexport type NotificationsTableProps = Pick<\n TableProps,\n 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount'\n> & {\n isLoading?: boolean;\n notifications?: Notification[];\n onUpdate: () => void;\n setContainsText: (search: string) => void;\n pageSize: number;\n};\n\n/** @public */\nexport const NotificationsTable = ({\n isLoading,\n notifications = [],\n onUpdate,\n setContainsText,\n onPageChange,\n onRowsPerPageChange,\n page,\n pageSize,\n totalCount,\n}: NotificationsTableProps) => {\n const classes = useStyles();\n const notificationsApi = useApi(notificationsApiRef);\n const [selectedNotifications, setSelectedNotifications] = React.useState(\n new Set<Notification['id']>(),\n );\n\n const onNotificationsSelectChange = React.useCallback(\n (ids: Notification['id'][], checked: boolean) => {\n let newSelect: Set<Notification['id']>;\n if (checked) {\n newSelect = new Set([...selectedNotifications, ...ids]);\n } else {\n newSelect = new Set(selectedNotifications);\n ids.forEach(id => newSelect.delete(id));\n }\n setSelectedNotifications(newSelect);\n },\n [selectedNotifications, setSelectedNotifications],\n );\n\n const onSwitchReadStatus = React.useCallback(\n (ids: Notification['id'][], newStatus: boolean) => {\n notificationsApi\n .updateNotifications({\n ids,\n read: newStatus,\n })\n .then(onUpdate);\n },\n [notificationsApi, onUpdate],\n );\n\n const onSwitchSavedStatus = React.useCallback(\n (ids: Notification['id'][], newStatus: boolean) => {\n notificationsApi\n .updateNotifications({\n ids,\n saved: newStatus,\n })\n .then(onUpdate);\n },\n [notificationsApi, onUpdate],\n );\n\n const throttledContainsTextHandler = React.useMemo(\n () => throttle(setContainsText, ThrottleDelayMs),\n [setContainsText],\n );\n\n React.useEffect(() => {\n const allShownIds = new Set(notifications.map(n => n.id));\n const intersect = [...selectedNotifications].filter(id =>\n allShownIds.has(id),\n );\n if (selectedNotifications.size !== intersect.length) {\n setSelectedNotifications(new Set(intersect));\n }\n }, [notifications, selectedNotifications]);\n\n const compactColumns = React.useMemo(\n (): TableColumn<Notification>[] => [\n {\n /* selection column */\n width: '1rem',\n title: (\n <SelectAll\n count={selectedNotifications.size}\n totalCount={notifications.length}\n onSelectAll={() =>\n onNotificationsSelectChange(\n notifications.map(notification => notification.id),\n selectedNotifications.size !== notifications.length,\n )\n }\n />\n ),\n render: (notification: Notification) => (\n <CheckBox\n color=\"primary\"\n checked={selectedNotifications.has(notification.id)}\n onChange={(_, checked) =>\n onNotificationsSelectChange([notification.id], checked)\n }\n />\n ),\n },\n {\n /* compact-data column */\n customFilterAndSearch: () =>\n true /* Keep sorting&filtering on backend due to pagination. */,\n render: (notification: Notification) => {\n // Compact content\n return (\n <Grid container>\n <Grid item className={classes.severityItem}>\n <SeverityIcon severity={notification.payload?.severity} />\n </Grid>\n <Grid item xs={11}>\n <Box>\n <Typography variant=\"subtitle2\">\n {notification.payload.link ? (\n <Link to={notification.payload.link}>\n {notification.payload.title}\n </Link>\n ) : (\n notification.payload.title\n )}\n </Typography>\n {notification.payload.description ? (\n <Typography variant=\"body2\" className={classes.description}>\n {notification.payload.description}\n </Typography>\n ) : null}\n <Typography variant=\"caption\">\n {notification.origin && (\n <>{notification.origin}&nbsp;&bull;&nbsp;</>\n )}\n {notification.payload.topic && (\n <>{notification.payload.topic}&nbsp;&bull;&nbsp;</>\n )}\n {notification.created && (\n <RelativeTime value={notification.created} />\n )}\n </Typography>\n </Box>\n </Grid>\n </Grid>\n );\n },\n },\n {\n /* actions column */\n width: '1rem',\n title: (\n <BulkActions\n notifications={notifications}\n selectedNotifications={selectedNotifications}\n onSwitchReadStatus={onSwitchReadStatus}\n onSwitchSavedStatus={onSwitchSavedStatus}\n />\n ),\n render: (notification: Notification) => (\n <BulkActions\n notifications={[notification]}\n selectedNotifications={new Set([notification.id])}\n onSwitchReadStatus={onSwitchReadStatus}\n onSwitchSavedStatus={onSwitchSavedStatus}\n />\n ),\n },\n ],\n [\n selectedNotifications,\n notifications,\n onSwitchReadStatus,\n onSwitchSavedStatus,\n onNotificationsSelectChange,\n classes.severityItem,\n classes.description,\n ],\n );\n\n return (\n <Table<Notification>\n isLoading={isLoading}\n options={{\n padding: 'dense',\n search: true,\n paging: true,\n pageSize,\n header: true,\n sorting: false,\n }}\n onPageChange={onPageChange}\n onRowsPerPageChange={onRowsPerPageChange}\n page={page}\n totalCount={totalCount}\n onSearchChange={throttledContainsTextHandler}\n data={notifications}\n columns={compactColumns}\n />\n );\n};\n"],"names":["CheckBox"],"mappings":"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"}
1
+ {"version":3,"file":"NotificationsTable.esm.js","sources":["../../../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 React from 'react';\nimport throttle from 'lodash/throttle';\n// @ts-ignore\nimport RelativeTime from 'react-relative-time';\nimport Box from '@material-ui/core/Box';\nimport Grid from '@material-ui/core/Grid';\nimport CheckBox from '@material-ui/core/Checkbox';\nimport Typography from '@material-ui/core/Typography';\nimport { makeStyles } from '@material-ui/core/styles';\nimport { Notification } from '@backstage/plugin-notifications-common';\nimport { useConfirm } from 'material-ui-confirm';\nimport { alertApiRef, useApi } from '@backstage/core-plugin-api';\nimport {\n Link,\n Table,\n TableColumn,\n TableProps,\n} from '@backstage/core-components';\n\nimport { notificationsApiRef } from '../../api';\n\nimport { SeverityIcon } from './SeverityIcon';\nimport { SelectAll } from './SelectAll';\nimport { BulkActions } from './BulkActions';\n\nconst ThrottleDelayMs = 1000;\n\nconst useStyles = makeStyles({\n description: {\n maxHeight: '5rem',\n overflow: 'scroll',\n },\n severityItem: {\n alignContent: 'center',\n },\n});\n\n/** @public */\nexport type NotificationsTableProps = Pick<\n TableProps,\n 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount'\n> & {\n markAsReadOnLinkOpen?: boolean;\n isLoading?: boolean;\n isUnread: boolean;\n notifications?: Notification[];\n onUpdate: () => void;\n setContainsText: (search: string) => void;\n pageSize: number;\n};\n\n/** @public */\nexport const NotificationsTable = ({\n markAsReadOnLinkOpen,\n isLoading,\n notifications = [],\n isUnread,\n onUpdate,\n setContainsText,\n onPageChange,\n onRowsPerPageChange,\n page,\n pageSize,\n totalCount,\n}: NotificationsTableProps) => {\n const classes = useStyles();\n const notificationsApi = useApi(notificationsApiRef);\n const alertApi = useApi(alertApiRef);\n const confirm = useConfirm();\n\n const [selectedNotifications, setSelectedNotifications] = React.useState(\n new Set<Notification['id']>(),\n );\n\n const onNotificationsSelectChange = React.useCallback(\n (ids: Notification['id'][], checked: boolean) => {\n let newSelect: Set<Notification['id']>;\n if (checked) {\n newSelect = new Set([...selectedNotifications, ...ids]);\n } else {\n newSelect = new Set(selectedNotifications);\n ids.forEach(id => newSelect.delete(id));\n }\n setSelectedNotifications(newSelect);\n },\n [selectedNotifications, setSelectedNotifications],\n );\n\n const onSwitchReadStatus = React.useCallback(\n (ids: Notification['id'][], newStatus: boolean) => {\n notificationsApi\n .updateNotifications({\n ids,\n read: newStatus,\n })\n .then(onUpdate);\n },\n [notificationsApi, onUpdate],\n );\n\n const onSwitchSavedStatus = React.useCallback(\n (ids: Notification['id'][], newStatus: boolean) => {\n notificationsApi\n .updateNotifications({\n ids,\n saved: newStatus,\n })\n .then(onUpdate);\n },\n [notificationsApi, onUpdate],\n );\n\n const onMarkAllRead = React.useCallback(() => {\n confirm({\n title: 'Are you sure?',\n description: (\n <>\n Mark <b>all</b> notifications as <b>read</b>.\n </>\n ),\n confirmationText: 'Mark All',\n })\n .then(async () => {\n const ids = (\n await notificationsApi.getNotifications({ read: false })\n ).notifications?.map(notification => notification.id);\n\n return notificationsApi\n .updateNotifications({\n ids,\n read: true,\n })\n .then(onUpdate);\n })\n .catch(e => {\n if (e) {\n // if e === undefined, the Cancel button has been hit\n alertApi.post({\n message: 'Failed to mark all notifications as read',\n severity: 'error',\n });\n }\n });\n }, [alertApi, confirm, notificationsApi, onUpdate]);\n\n const throttledContainsTextHandler = React.useMemo(\n () => throttle(setContainsText, ThrottleDelayMs),\n [setContainsText],\n );\n\n React.useEffect(() => {\n const allShownIds = new Set(notifications.map(n => n.id));\n const intersect = [...selectedNotifications].filter(id =>\n allShownIds.has(id),\n );\n if (selectedNotifications.size !== intersect.length) {\n setSelectedNotifications(new Set(intersect));\n }\n }, [notifications, selectedNotifications]);\n\n const compactColumns = React.useMemo(\n (): TableColumn<Notification>[] => [\n {\n /* selection column */\n width: '1rem',\n title: (\n <SelectAll\n count={selectedNotifications.size}\n totalCount={notifications.length}\n onSelectAll={() =>\n onNotificationsSelectChange(\n notifications.map(notification => notification.id),\n selectedNotifications.size !== notifications.length,\n )\n }\n />\n ),\n render: (notification: Notification) => (\n <CheckBox\n color=\"primary\"\n checked={selectedNotifications.has(notification.id)}\n onChange={(_, checked) =>\n onNotificationsSelectChange([notification.id], checked)\n }\n />\n ),\n },\n {\n /* compact-data column */\n customFilterAndSearch: () =>\n true /* Keep sorting&filtering on backend due to pagination. */,\n render: (notification: Notification) => {\n // Compact content\n return (\n <Grid container>\n <Grid item className={classes.severityItem}>\n <SeverityIcon severity={notification.payload?.severity} />\n </Grid>\n <Grid item xs={11}>\n <Box>\n <Typography variant=\"subtitle2\">\n {notification.payload.link ? (\n <Link\n to={notification.payload.link}\n onClick={() => {\n if (markAsReadOnLinkOpen && !notification.read) {\n onSwitchReadStatus([notification.id], true);\n }\n }}\n >\n {notification.payload.title}\n </Link>\n ) : (\n notification.payload.title\n )}\n </Typography>\n {notification.payload.description ? (\n <Typography variant=\"body2\" className={classes.description}>\n {notification.payload.description}\n </Typography>\n ) : null}\n <Typography variant=\"caption\">\n {notification.origin && (\n <>{notification.origin}&nbsp;&bull;&nbsp;</>\n )}\n {notification.payload.topic && (\n <>{notification.payload.topic}&nbsp;&bull;&nbsp;</>\n )}\n {notification.created && (\n <RelativeTime value={notification.created} />\n )}\n </Typography>\n </Box>\n </Grid>\n </Grid>\n );\n },\n },\n {\n /* actions column */\n width: '1rem',\n title: (\n <BulkActions\n notifications={notifications}\n selectedNotifications={selectedNotifications}\n isUnread={isUnread}\n onSwitchReadStatus={onSwitchReadStatus}\n onSwitchSavedStatus={onSwitchSavedStatus}\n onMarkAllRead={onMarkAllRead}\n />\n ),\n render: (notification: Notification) => (\n <BulkActions\n notifications={[notification]}\n selectedNotifications={new Set([notification.id])}\n onSwitchReadStatus={onSwitchReadStatus}\n onSwitchSavedStatus={onSwitchSavedStatus}\n //\n />\n ),\n },\n ],\n [\n markAsReadOnLinkOpen,\n selectedNotifications,\n notifications,\n isUnread,\n onSwitchReadStatus,\n onSwitchSavedStatus,\n onMarkAllRead,\n onNotificationsSelectChange,\n classes.severityItem,\n classes.description,\n ],\n );\n\n return (\n <Table<Notification>\n isLoading={isLoading}\n options={{\n padding: 'dense',\n search: true,\n paging: true,\n pageSize,\n header: true,\n sorting: false,\n }}\n onPageChange={onPageChange}\n onRowsPerPageChange={onRowsPerPageChange}\n page={page}\n totalCount={totalCount}\n onSearchChange={throttledContainsTextHandler}\n data={notifications}\n columns={compactColumns}\n />\n );\n};\n"],"names":["CheckBox"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ import { TableProps } from '@backstage/core-components';
8
8
 
9
9
  /** @public */
10
10
  type NotificationsPageProps = {
11
+ /** Mark notification as read when opening the link it contains, defaults to false */
12
+ markAsReadOnLinkOpen?: boolean;
11
13
  title?: string;
12
14
  themeId?: string;
13
15
  subtitle?: string;
@@ -109,10 +111,19 @@ declare function useTitleCounter(): {
109
111
  setNotificationCount: (newCount: number) => void;
110
112
  };
111
113
 
114
+ declare module 'notistack' {
115
+ interface VariantOverrides {
116
+ low: true;
117
+ normal: true;
118
+ high: true;
119
+ critical: true;
120
+ }
121
+ }
112
122
  /** @public */
113
123
  declare const NotificationsSidebarItem: (props?: {
114
124
  webNotificationsEnabled?: boolean;
115
125
  titleCounterEnabled?: boolean;
126
+ snackbarEnabled?: boolean;
116
127
  className?: string;
117
128
  icon?: IconComponent;
118
129
  text?: string;
@@ -122,13 +133,15 @@ declare const NotificationsSidebarItem: (props?: {
122
133
 
123
134
  /** @public */
124
135
  type NotificationsTableProps = Pick<TableProps, 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount'> & {
136
+ markAsReadOnLinkOpen?: boolean;
125
137
  isLoading?: boolean;
138
+ isUnread: boolean;
126
139
  notifications?: Notification$1[];
127
140
  onUpdate: () => void;
128
141
  setContainsText: (search: string) => void;
129
142
  pageSize: number;
130
143
  };
131
144
  /** @public */
132
- declare const NotificationsTable: ({ isLoading, notifications, onUpdate, setContainsText, onPageChange, onRowsPerPageChange, page, pageSize, totalCount, }: NotificationsTableProps) => React__default.JSX.Element;
145
+ declare const NotificationsTable: ({ markAsReadOnLinkOpen, isLoading, notifications, isUnread, onUpdate, setContainsText, onPageChange, onRowsPerPageChange, page, pageSize, totalCount, }: NotificationsTableProps) => React__default.JSX.Element;
133
146
 
134
147
  export { type GetNotificationsOptions, type GetNotificationsResponse, type NotificationsApi, NotificationsClient, NotificationsPage, type NotificationsPageProps, NotificationsSidebarItem, NotificationsTable, type NotificationsTableProps, type UpdateNotificationsOptions, notificationsApiRef, notificationsPlugin, useNotificationsApi, useTitleCounter, useWebNotifications };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-notifications",
3
- "version": "0.2.1-next.0",
3
+ "version": "0.2.1-next.1",
4
4
  "backstage": {
5
5
  "role": "frontend-plugin"
6
6
  },
@@ -31,7 +31,7 @@
31
31
  "test": "backstage-cli package test"
32
32
  },
33
33
  "dependencies": {
34
- "@backstage/core-components": "^0.14.5-next.0",
34
+ "@backstage/core-components": "^0.14.6-next.1",
35
35
  "@backstage/core-plugin-api": "^1.9.2",
36
36
  "@backstage/errors": "^1.2.4",
37
37
  "@backstage/plugin-notifications-common": "^0.0.3",
@@ -43,14 +43,16 @@
43
43
  "@material-ui/lab": "^4.0.0-alpha.61",
44
44
  "@types/react": "^16.13.1 || ^17.0.0",
45
45
  "lodash": "^4.17.21",
46
+ "material-ui-confirm": "^3.0.12",
47
+ "notistack": "^3.0.1",
46
48
  "react-relative-time": "^0.0.9",
47
49
  "react-use": "^17.2.4"
48
50
  },
49
51
  "devDependencies": {
50
52
  "@backstage/cli": "^0.26.5-next.0",
51
53
  "@backstage/core-app-api": "^1.12.4",
52
- "@backstage/dev-utils": "^1.0.32-next.0",
53
- "@backstage/plugin-signals": "^0.0.6-next.0",
54
+ "@backstage/dev-utils": "^1.0.32-next.1",
55
+ "@backstage/plugin-signals": "^0.0.6-next.1",
54
56
  "@backstage/test-utils": "^1.5.5-next.0",
55
57
  "@testing-library/jest-dom": "^6.0.0",
56
58
  "@testing-library/react": "^15.0.0",