@backstage/plugin-notifications 0.5.21-next.2 → 0.6.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/alpha/extensions/unreadNotificationsHomeWidget.esm.js +57 -0
  3. package/dist/alpha/extensions/unreadNotificationsHomeWidget.esm.js.map +1 -0
  4. package/dist/alpha.d.ts +17 -0
  5. package/dist/alpha.esm.js +2 -1
  6. package/dist/alpha.esm.js.map +1 -1
  7. package/dist/components/NotificationsPage/NotificationsPage.esm.js +47 -3
  8. package/dist/components/NotificationsPage/NotificationsPage.esm.js.map +1 -1
  9. package/dist/components/NotificationsSideBarItem/NotificationsSideBarItem.esm.js +1 -1
  10. package/dist/components/NotificationsTable/NotificationIcon.esm.js +14 -8
  11. package/dist/components/NotificationsTable/NotificationIcon.esm.js.map +1 -1
  12. package/dist/components/NotificationsTable/NotificationsTable.esm.js +76 -41
  13. package/dist/components/NotificationsTable/NotificationsTable.esm.js.map +1 -1
  14. package/dist/components/NotificationsTable/NotificationsTable.module.css.esm.js +2 -2
  15. package/dist/components/UnreadNotificationsCard/UnreadNotificationsCard.esm.js +255 -0
  16. package/dist/components/UnreadNotificationsCard/UnreadNotificationsCard.esm.js.map +1 -0
  17. package/dist/components/UnreadNotificationsCard/UnreadNotificationsCard.module.css.esm.js +8 -0
  18. package/dist/components/UnreadNotificationsCard/UnreadNotificationsCard.module.css.esm.js.map +1 -0
  19. package/dist/components/UnreadNotificationsCard/index.esm.js +7 -0
  20. package/dist/components/UnreadNotificationsCard/index.esm.js.map +1 -0
  21. package/dist/components/UserNotificationSettingsCard/OriginRow.esm.js +3 -3
  22. package/dist/components/UserNotificationSettingsCard/OriginRow.esm.js.map +1 -1
  23. package/dist/components/UserNotificationSettingsCard/TopicRow.esm.js +1 -3
  24. package/dist/components/UserNotificationSettingsCard/TopicRow.esm.js.map +1 -1
  25. package/dist/components/UserNotificationSettingsCard/UserNotificationSettingsCard.esm.js +1 -0
  26. package/dist/components/UserNotificationSettingsCard/UserNotificationSettingsCard.esm.js.map +1 -1
  27. package/dist/hooks/useNotificationsRefresh.esm.js +24 -0
  28. package/dist/hooks/useNotificationsRefresh.esm.js.map +1 -0
  29. package/dist/index.d.ts +9 -1
  30. package/dist/index.esm.js +1 -0
  31. package/dist/index.esm.js.map +1 -1
  32. package/dist/plugins/notifications/package.json.esm.js +2 -1
  33. package/dist/plugins/notifications/package.json.esm.js.map +1 -1
  34. package/dist/translation.esm.js +9 -0
  35. package/dist/translation.esm.js.map +1 -1
  36. package/dist/utils/notificationLinks.esm.js +11 -0
  37. package/dist/utils/notificationLinks.esm.js.map +1 -0
  38. package/dist/utils/plainText.esm.js +30 -0
  39. package/dist/utils/plainText.esm.js.map +1 -0
  40. package/package.json +14 -13
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @backstage/plugin-notifications
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cd4a9c7: Added an unread notifications home page card and NFS home widget.
8
+
9
+ ### Patch Changes
10
+
11
+ - 736d84e: Use locale-insensitive Unicode casing for consistent string handling across environments.
12
+ - b31afcf: Fixed circular dependency warnings when building the notification settings UI.
13
+ - Updated dependencies
14
+ - @backstage/core-components@0.18.14
15
+ - @backstage/ui@0.18.0
16
+ - @backstage/frontend-plugin-api@0.18.1
17
+ - @backstage/plugin-home-react@0.1.42
18
+ - @backstage/core-plugin-api@1.12.10
19
+ - @backstage/plugin-notifications-common@0.2.4
20
+ - @backstage/plugin-signals-react@0.0.26
21
+
3
22
  ## 0.5.21-next.2
4
23
 
5
24
  ### Patch Changes
@@ -0,0 +1,57 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha';
3
+
4
+ const defaultCardLayout = {
5
+ width: {
6
+ minColumns: 4,
7
+ maxColumns: 12,
8
+ defaultColumns: 6
9
+ },
10
+ height: {
11
+ minRows: 5,
12
+ maxRows: 14,
13
+ defaultRows: 10
14
+ }
15
+ };
16
+ function BorderlessHomeWidgetRenderer({ Content }) {
17
+ return /* @__PURE__ */ jsx(Content, {});
18
+ }
19
+ const unreadNotificationsHomeWidget = HomePageWidgetBlueprint.make({
20
+ name: "unread-notifications",
21
+ params: {
22
+ name: "UnreadNotifications",
23
+ description: "Shows the latest unread notifications",
24
+ layout: defaultCardLayout,
25
+ componentProps: {
26
+ Renderer: BorderlessHomeWidgetRenderer
27
+ },
28
+ components: () => import('../../components/UnreadNotificationsCard/index.esm.js').then((m) => ({
29
+ Content: m.UnreadNotificationsCardWithProvider
30
+ })),
31
+ settings: {
32
+ schema: {
33
+ title: "Unread notifications",
34
+ type: "object",
35
+ properties: {
36
+ maxMessages: {
37
+ title: "Maximum messages",
38
+ type: "number",
39
+ default: 5,
40
+ minimum: 1,
41
+ maximum: 20
42
+ },
43
+ charLimit: {
44
+ title: "Title character limit",
45
+ type: "number",
46
+ default: 80,
47
+ minimum: 20,
48
+ maximum: 200
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ });
55
+
56
+ export { unreadNotificationsHomeWidget };
57
+ //# sourceMappingURL=unreadNotificationsHomeWidget.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unreadNotificationsHomeWidget.esm.js","sources":["../../../src/alpha/extensions/unreadNotificationsHomeWidget.tsx"],"sourcesContent":["/*\n * Copyright 2026 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 { type RendererProps } from '@backstage/plugin-home-react';\nimport { HomePageWidgetBlueprint } from '@backstage/plugin-home-react/alpha';\n\nconst defaultCardLayout = {\n width: {\n minColumns: 4,\n maxColumns: 12,\n defaultColumns: 6,\n },\n height: {\n minRows: 5,\n maxRows: 14,\n defaultRows: 10,\n },\n} as const;\n\nfunction BorderlessHomeWidgetRenderer({ Content }: RendererProps) {\n return <Content />;\n}\n\n/**\n * NFS home page widget for unread notifications.\n *\n * @alpha\n */\nexport const unreadNotificationsHomeWidget = HomePageWidgetBlueprint.make({\n name: 'unread-notifications',\n params: {\n name: 'UnreadNotifications',\n description: 'Shows the latest unread notifications',\n layout: defaultCardLayout,\n componentProps: {\n Renderer: BorderlessHomeWidgetRenderer,\n },\n components: () =>\n import('../../components/UnreadNotificationsCard').then(m => ({\n Content: m.UnreadNotificationsCardWithProvider,\n })),\n settings: {\n schema: {\n title: 'Unread notifications',\n type: 'object',\n properties: {\n maxMessages: {\n title: 'Maximum messages',\n type: 'number',\n default: 5,\n minimum: 1,\n maximum: 20,\n },\n charLimit: {\n title: 'Title character limit',\n type: 'number',\n default: 80,\n minimum: 20,\n maximum: 200,\n },\n },\n },\n },\n },\n});\n"],"names":[],"mappings":";;;AAmBA,MAAM,iBAAA,GAAoB;AAAA,EACxB,KAAA,EAAO;AAAA,IACL,UAAA,EAAY,CAAA;AAAA,IACZ,UAAA,EAAY,EAAA;AAAA,IACZ,cAAA,EAAgB;AAAA,GAClB;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,OAAA,EAAS,CAAA;AAAA,IACT,OAAA,EAAS,EAAA;AAAA,IACT,WAAA,EAAa;AAAA;AAEjB,CAAA;AAEA,SAAS,4BAAA,CAA6B,EAAE,OAAA,EAAQ,EAAkB;AAChE,EAAA,2BAAQ,OAAA,EAAA,EAAQ,CAAA;AAClB;AAOO,MAAM,6BAAA,GAAgC,wBAAwB,IAAA,CAAK;AAAA,EACxE,IAAA,EAAM,sBAAA;AAAA,EACN,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,qBAAA;AAAA,IACN,WAAA,EAAa,uCAAA;AAAA,IACb,MAAA,EAAQ,iBAAA;AAAA,IACR,cAAA,EAAgB;AAAA,MACd,QAAA,EAAU;AAAA,KACZ;AAAA,IACA,YAAY,MACV,OAAO,uDAA0C,CAAA,CAAE,KAAK,CAAA,CAAA,MAAM;AAAA,MAC5D,SAAS,CAAA,CAAE;AAAA,KACb,CAAE,CAAA;AAAA,IACJ,QAAA,EAAU;AAAA,MACR,MAAA,EAAQ;AAAA,QACN,KAAA,EAAO,sBAAA;AAAA,QACP,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,WAAA,EAAa;AAAA,YACX,KAAA,EAAO,kBAAA;AAAA,YACP,IAAA,EAAM,QAAA;AAAA,YACN,OAAA,EAAS,CAAA;AAAA,YACT,OAAA,EAAS,CAAA;AAAA,YACT,OAAA,EAAS;AAAA,WACX;AAAA,UACA,SAAA,EAAW;AAAA,YACT,KAAA,EAAO,uBAAA;AAAA,YACP,IAAA,EAAM,QAAA;AAAA,YACN,OAAA,EAAS,EAAA;AAAA,YACT,OAAA,EAAS,EAAA;AAAA,YACT,OAAA,EAAS;AAAA;AACX;AACF;AACF;AACF;AAEJ,CAAC;;;;"}
package/dist/alpha.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react from 'react';
2
+ import * as _backstage_plugin_home_react_alpha from '@backstage/plugin-home-react/alpha';
2
3
  import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
3
4
  import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
4
5
 
@@ -15,6 +16,15 @@ declare const _default: _backstage_frontend_plugin_api.OverridableFrontendPlugin
15
16
  inputs: {};
16
17
  params: <TApi, TImpl extends TApi, TDeps extends { [name in string]: unknown; }>(params: _backstage_frontend_plugin_api.ApiFactory<TApi, TImpl, TDeps>) => _backstage_frontend_plugin_api.ExtensionBlueprintParams<_backstage_frontend_plugin_api.AnyApiFactory>;
17
18
  }>;
19
+ "home-page-widget:notifications/unread-notifications": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
20
+ kind: "home-page-widget";
21
+ name: "unread-notifications";
22
+ config: {};
23
+ configInput: {};
24
+ output: _backstage_frontend_plugin_api.ExtensionDataRef<_backstage_plugin_home_react_alpha.HomePageWidgetData, "home.widget.data", {}>;
25
+ inputs: {};
26
+ params: _backstage_plugin_home_react_alpha.HomePageWidgetBlueprintParams;
27
+ }>;
18
28
  "page:notifications": _backstage_frontend_plugin_api.OverridableExtensionDefinition<{
19
29
  kind: "page";
20
30
  name: undefined;
@@ -121,6 +131,13 @@ declare const notificationsTranslationRef: _backstage_frontend_plugin_api.Transl
121
131
  readonly "notificationsPage.tableTitle.unread_other": "Unread notifications ({{count}})";
122
132
  readonly "notificationsPage.tableTitle.read_one": "Read notifications ({{count}})";
123
133
  readonly "notificationsPage.tableTitle.read_other": "Read notifications ({{count}})";
134
+ readonly "unreadCard.emptyState": "All caught up!";
135
+ readonly "unreadCard.title_one": "Last unread notifications ({{count}})";
136
+ readonly "unreadCard.title_other": "Last unread notifications ({{count}})";
137
+ readonly "unreadCard.viewAll": "View All";
138
+ readonly "unreadCard.columnNotification": "Notification";
139
+ readonly "unreadCard.columnActions": "Actions";
140
+ readonly "unreadCard.resultsCount": "{{displayed}} results out of {{total}}";
124
141
  }>;
125
142
 
126
143
  export { _default as default, notificationsTranslationRef };
package/dist/alpha.esm.js CHANGED
@@ -4,6 +4,7 @@ import { RiNotification3Line } from '@remixicon/react';
4
4
  import { rootRouteRef } from './routes.esm.js';
5
5
  import { notificationsApiRef } from './api/NotificationsApi.esm.js';
6
6
  import { NotificationsClient } from './api/NotificationsClient.esm.js';
7
+ import { unreadNotificationsHomeWidget } from './alpha/extensions/unreadNotificationsHomeWidget.esm.js';
7
8
  import { notificationsTranslationRef as notificationsTranslationRef$1 } from './translation.esm.js';
8
9
 
9
10
  const page = PageBlueprint.make({
@@ -29,7 +30,7 @@ var alpha = createFrontendPlugin({
29
30
  root: rootRouteRef
30
31
  },
31
32
  // TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually
32
- extensions: [page, api]
33
+ extensions: [page, api, unreadNotificationsHomeWidget]
33
34
  });
34
35
  const notificationsTranslationRef = notificationsTranslationRef$1;
35
36
 
@@ -1 +1 @@
1
- {"version":3,"file":"alpha.esm.js","sources":["../src/alpha.tsx"],"sourcesContent":["/*\n * Copyright 2025 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 {\n ApiBlueprint,\n PageBlueprint,\n createFrontendPlugin,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RiNotification3Line } from '@remixicon/react';\nimport { rootRouteRef } from './routes';\nimport { NotificationsClient, notificationsApiRef } from './api';\n\nconst page = PageBlueprint.make({\n params: {\n path: '/notifications',\n routeRef: rootRouteRef,\n loader: () =>\n import('./components/NotificationsPage').then(m => (\n <m.NfsNotificationsPage />\n )),\n },\n});\n\nconst api = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: notificationsApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new NotificationsClient({ discoveryApi, fetchApi }),\n }),\n});\n\n/** @alpha */\nexport default createFrontendPlugin({\n pluginId: 'notifications',\n title: 'Notifications',\n icon: <RiNotification3Line />,\n info: { packageJson: () => import('../package.json') },\n routes: {\n root: rootRouteRef,\n },\n // TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually\n extensions: [page, api],\n});\n\nimport { notificationsTranslationRef as _notificationsTranslationRef } from './translation';\n\n/**\n * @alpha\n * @deprecated Import from `@backstage/plugin-notifications` instead.\n */\nexport const notificationsTranslationRef = _notificationsTranslationRef;\n"],"names":["_notificationsTranslationRef"],"mappings":";;;;;;;;AA2BA,MAAM,IAAA,GAAO,cAAc,IAAA,CAAK;AAAA,EAC9B,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,gBAAA;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,MAAA,EAAQ,MACN,OAAO,6CAAgC,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC5C,GAAA,CAAC,CAAA,CAAE,oBAAA,EAAF,EAAuB,CACzB;AAAA;AAEP,CAAC,CAAA;AAED,MAAM,GAAA,GAAM,aAAa,IAAA,CAAK;AAAA,EAC5B,MAAA,EAAQ,kBACN,YAAA,CAAa;AAAA,IACX,GAAA,EAAK,mBAAA;AAAA,IACL,IAAA,EAAM,EAAE,YAAA,EAAc,eAAA,EAAiB,UAAU,WAAA,EAAY;AAAA,IAC7D,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,QAAA,EAAS,KACjC,IAAI,mBAAA,CAAoB,EAAE,YAAA,EAAc,QAAA,EAAU;AAAA,GACrD;AACL,CAAC,CAAA;AAGD,YAAe,oBAAA,CAAqB;AAAA,EAClC,QAAA,EAAU,eAAA;AAAA,EACV,KAAA,EAAO,eAAA;AAAA,EACP,IAAA,sBAAO,mBAAA,EAAA,EAAoB,CAAA;AAAA,EAC3B,MAAM,EAAE,WAAA,EAAa,MAAM,OAAO,6CAAiB,CAAA,EAAE;AAAA,EACrD,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAEA,UAAA,EAAY,CAAC,IAAA,EAAM,GAAG;AACxB,CAAC,CAAA;AAQM,MAAM,2BAAA,GAA8BA;;;;"}
1
+ {"version":3,"file":"alpha.esm.js","sources":["../src/alpha.tsx"],"sourcesContent":["/*\n * Copyright 2025 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 {\n ApiBlueprint,\n PageBlueprint,\n createFrontendPlugin,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RiNotification3Line } from '@remixicon/react';\nimport { rootRouteRef } from './routes';\nimport { NotificationsClient, notificationsApiRef } from './api';\nimport { unreadNotificationsHomeWidget } from './alpha/extensions/unreadNotificationsHomeWidget';\n\nconst page = PageBlueprint.make({\n params: {\n path: '/notifications',\n routeRef: rootRouteRef,\n loader: () =>\n import('./components/NotificationsPage').then(m => (\n <m.NfsNotificationsPage />\n )),\n },\n});\n\nconst api = ApiBlueprint.make({\n params: defineParams =>\n defineParams({\n api: notificationsApiRef,\n deps: { discoveryApi: discoveryApiRef, fetchApi: fetchApiRef },\n factory: ({ discoveryApi, fetchApi }) =>\n new NotificationsClient({ discoveryApi, fetchApi }),\n }),\n});\n\n/** @alpha */\nexport default createFrontendPlugin({\n pluginId: 'notifications',\n title: 'Notifications',\n icon: <RiNotification3Line />,\n info: { packageJson: () => import('../package.json') },\n routes: {\n root: rootRouteRef,\n },\n // TODO(Rugvip): Nav item (i.e. NotificationsSidebarItem) currently needs to be installed manually\n extensions: [page, api, unreadNotificationsHomeWidget],\n});\n\nimport { notificationsTranslationRef as _notificationsTranslationRef } from './translation';\n\n/**\n * @alpha\n * @deprecated Import from `@backstage/plugin-notifications` instead.\n */\nexport const notificationsTranslationRef = _notificationsTranslationRef;\n"],"names":["_notificationsTranslationRef"],"mappings":";;;;;;;;;AA4BA,MAAM,IAAA,GAAO,cAAc,IAAA,CAAK;AAAA,EAC9B,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,gBAAA;AAAA,IACN,QAAA,EAAU,YAAA;AAAA,IACV,MAAA,EAAQ,MACN,OAAO,6CAAgC,CAAA,CAAE,IAAA,CAAK,CAAA,CAAA,qBAC5C,GAAA,CAAC,CAAA,CAAE,oBAAA,EAAF,EAAuB,CACzB;AAAA;AAEP,CAAC,CAAA;AAED,MAAM,GAAA,GAAM,aAAa,IAAA,CAAK;AAAA,EAC5B,MAAA,EAAQ,kBACN,YAAA,CAAa;AAAA,IACX,GAAA,EAAK,mBAAA;AAAA,IACL,IAAA,EAAM,EAAE,YAAA,EAAc,eAAA,EAAiB,UAAU,WAAA,EAAY;AAAA,IAC7D,OAAA,EAAS,CAAC,EAAE,YAAA,EAAc,QAAA,EAAS,KACjC,IAAI,mBAAA,CAAoB,EAAE,YAAA,EAAc,QAAA,EAAU;AAAA,GACrD;AACL,CAAC,CAAA;AAGD,YAAe,oBAAA,CAAqB;AAAA,EAClC,QAAA,EAAU,eAAA;AAAA,EACV,KAAA,EAAO,eAAA;AAAA,EACP,IAAA,sBAAO,mBAAA,EAAA,EAAoB,CAAA;AAAA,EAC3B,MAAM,EAAE,WAAA,EAAa,MAAM,OAAO,6CAAiB,CAAA,EAAE;AAAA,EACrD,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM;AAAA,GACR;AAAA;AAAA,EAEA,UAAA,EAAY,CAAC,IAAA,EAAM,GAAA,EAAK,6BAA6B;AACvD,CAAC,CAAA;AAQM,MAAM,2BAAA,GAA8BA;;;;"}
@@ -5,6 +5,7 @@ import { ResponseErrorPanel, Content, PageWithHeader } from '@backstage/core-com
5
5
  import { Grid } from '@backstage/ui';
6
6
  import { useSignal } from '@backstage/plugin-signals-react';
7
7
  import { useTranslationRef } from '@backstage/core-plugin-api/alpha';
8
+ import { useSearchParams } from 'react-router-dom';
8
9
  import { notificationsTranslationRef } from '../../translation.esm.js';
9
10
  import { NotificationsTable } from '../NotificationsTable/NotificationsTable.esm.js';
10
11
  import '../NotificationsTable/NotificationDescription.esm.js';
@@ -37,6 +38,9 @@ function NotificationsPageContent(props) {
37
38
  } = props;
38
39
  const [refresh, setRefresh] = useState(false);
39
40
  const { lastSignal } = useSignal("notifications");
41
+ const [searchParams] = useSearchParams();
42
+ const highlightedNotificationId = searchParams.get("id") || void 0;
43
+ const [highlightedNotification, setHighlightedNotification] = useState();
40
44
  const [unreadOnly, setUnreadOnly] = useState(true);
41
45
  const [saved, setSaved] = useState(void 0);
42
46
  const [pageNumber, setPageNumber] = useState(0);
@@ -101,13 +105,52 @@ function NotificationsPageContent(props) {
101
105
  throttledSetRefresh(true);
102
106
  }
103
107
  }, [lastSignal, throttledSetRefresh]);
108
+ useEffect(() => {
109
+ if (!highlightedNotificationId) {
110
+ setHighlightedNotification(void 0);
111
+ return;
112
+ }
113
+ setPageNumber(0);
114
+ }, [highlightedNotificationId]);
115
+ const { value: fetchedHighlightedNotification } = useNotificationsApi(
116
+ (api) => {
117
+ if (!highlightedNotificationId) {
118
+ return Promise.resolve(void 0);
119
+ }
120
+ return api.getNotification(highlightedNotificationId);
121
+ },
122
+ [highlightedNotificationId]
123
+ );
124
+ useEffect(() => {
125
+ if (!fetchedHighlightedNotification) {
126
+ return;
127
+ }
128
+ setHighlightedNotification(fetchedHighlightedNotification);
129
+ if (!fetchedHighlightedNotification.read) {
130
+ setUnreadOnly(true);
131
+ } else {
132
+ setUnreadOnly(false);
133
+ }
134
+ }, [fetchedHighlightedNotification]);
104
135
  const onUpdate = () => {
105
136
  throttledSetRefresh(true);
106
137
  };
138
+ const notifications = value?.[0]?.notifications;
139
+ const displayedNotifications = useMemo(() => {
140
+ if (!highlightedNotification || notifications?.some(
141
+ (notification) => notification.id === highlightedNotification.id
142
+ )) {
143
+ return notifications;
144
+ }
145
+ return [highlightedNotification, ...notifications ?? []];
146
+ }, [notifications, highlightedNotification]);
147
+ const injectedHighlightedNotification = highlightedNotification && !notifications?.some(
148
+ (notification) => notification.id === highlightedNotification.id
149
+ );
150
+ const displayedPageSize = injectedHighlightedNotification ? pageSize + 1 : pageSize;
107
151
  if (error) {
108
152
  return /* @__PURE__ */ jsx(ResponseErrorPanel, { error });
109
153
  }
110
- const notifications = value?.[0]?.notifications;
111
154
  const totalCount = value?.[0]?.totalCount;
112
155
  const isUnread = !!value?.[1]?.unread;
113
156
  const allTopics = value?.[2]?.topics;
@@ -153,13 +196,14 @@ function NotificationsPageContent(props) {
153
196
  isLoading: loading,
154
197
  isUnread,
155
198
  markAsReadOnLinkOpen,
156
- notifications,
199
+ notifications: displayedNotifications,
200
+ highlightedNotificationId,
157
201
  onUpdate,
158
202
  setContainsText,
159
203
  onPageChange: setPageNumber,
160
204
  onRowsPerPageChange: setPageSize,
161
205
  page: pageNumber,
162
- pageSize,
206
+ pageSize: displayedPageSize,
163
207
  totalCount
164
208
  }
165
209
  ) })
@@ -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 { useEffect, useMemo, useState } from 'react';\nimport throttle from 'lodash/throttle';\nimport {\n Content,\n PageWithHeader,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { Grid } from '@backstage/ui';\nimport { useSignal } from '@backstage/plugin-signals-react';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { notificationsTranslationRef } from '../../translation';\n\nconst TableTitleKeys = {\n all: 'notificationsPage.tableTitle.all',\n saved: 'notificationsPage.tableTitle.saved',\n unread: 'notificationsPage.tableTitle.unread',\n read: 'notificationsPage.tableTitle.read',\n} as const;\n\nimport { NotificationsTable } from '../NotificationsTable';\nimport { useNotificationsApi } from '../../hooks';\nimport {\n CreatedAfterOptions,\n NotificationsFilters,\n SortBy,\n SortByOptions,\n} from '../NotificationsFilters';\nimport {\n GetNotificationsOptions,\n GetNotificationsResponse,\n GetTopicsResponse,\n} 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\nfunction NotificationsPageContent(\n props: NotificationsPageProps & { headerVariant: 'legacy' | 'bui' },\n) {\n const { t } = useTranslationRef(notificationsTranslationRef);\n const {\n title = t('notificationsPage.title'),\n themeId = 'tool',\n subtitle,\n tooltip,\n type,\n typeLink,\n markAsReadOnLinkOpen,\n headerVariant,\n } = props;\n\n const [refresh, setRefresh] = useState(false);\n const { lastSignal } = useSignal('notifications');\n const [unreadOnly, setUnreadOnly] = useState<boolean | undefined>(true);\n const [saved, setSaved] = useState<boolean | undefined>(undefined);\n const [pageNumber, setPageNumber] = useState(0);\n const [pageSize, setPageSize] = useState(5);\n const [containsText, setContainsText] = useState<string>();\n const [createdAfter, setCreatedAfter] = useState<string>('all');\n const [sorting, setSorting] = useState<SortBy>(SortByOptions.newest.sortBy);\n const [severity, setSeverity] = useState<NotificationSeverity>('low');\n const [topic, setTopic] = useState<string>();\n\n const { error, value, retry, loading } = useNotificationsApi<\n [GetNotificationsResponse, NotificationStatus, GetTopicsResponse]\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 if (topic !== undefined) {\n options.topic = topic;\n }\n\n const createdAfterDate =\n CreatedAfterOptions[\n createdAfter as keyof typeof CreatedAfterOptions\n ].getDate();\n if (createdAfterDate.valueOf() > 0) {\n options.createdAfter = createdAfterDate;\n }\n\n return Promise.all([\n api.getNotifications(options),\n api.getStatus(),\n api.getTopics(options),\n ]);\n },\n [\n containsText,\n unreadOnly,\n createdAfter,\n pageNumber,\n pageSize,\n sorting,\n saved,\n severity,\n topic,\n ],\n );\n\n const throttledSetRefresh = 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 const allTopics = value?.[2]?.topics;\n\n let tableTitle: string = t(TableTitleKeys.all, {\n count: totalCount ?? 0,\n });\n if (saved) {\n tableTitle = t(TableTitleKeys.saved, {\n count: totalCount ?? 0,\n });\n } else if (unreadOnly === true) {\n tableTitle = t(TableTitleKeys.unread, {\n count: totalCount ?? 0,\n });\n } else if (unreadOnly === false) {\n tableTitle = t(TableTitleKeys.read, {\n count: totalCount ?? 0,\n });\n }\n\n const pageContent = (\n <Content>\n <Grid.Root columns=\"12\" gap=\"6\">\n <Grid.Item colSpan=\"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 topic={topic}\n onTopicChanged={setTopic}\n allTopics={allTopics}\n />\n </Grid.Item>\n <Grid.Item colSpan=\"10\">\n <NotificationsTable\n title={tableTitle}\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.Item>\n </Grid.Root>\n </Content>\n );\n\n if (headerVariant === 'bui') {\n return pageContent;\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 {pageContent}\n </PageWithHeader>\n );\n}\n\nexport const NotificationsPage = (props?: NotificationsPageProps) => (\n <NotificationsPageContent {...(props ?? {})} headerVariant=\"legacy\" />\n);\n\nexport const NfsNotificationsPage = (props?: NotificationsPageProps) => (\n <NotificationsPageContent {...(props ?? {})} headerVariant=\"bui\" />\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 { useEffect, useMemo, useState } from 'react';\nimport throttle from 'lodash/throttle';\nimport {\n Content,\n PageWithHeader,\n ResponseErrorPanel,\n} from '@backstage/core-components';\nimport { Grid } from '@backstage/ui';\nimport { useSignal } from '@backstage/plugin-signals-react';\nimport { useTranslationRef } from '@backstage/core-plugin-api/alpha';\nimport { useSearchParams } from 'react-router-dom';\nimport {\n Notification,\n NotificationSeverity,\n NotificationStatus,\n} from '@backstage/plugin-notifications-common';\nimport { notificationsTranslationRef } from '../../translation';\n\nconst TableTitleKeys = {\n all: 'notificationsPage.tableTitle.all',\n saved: 'notificationsPage.tableTitle.saved',\n unread: 'notificationsPage.tableTitle.unread',\n read: 'notificationsPage.tableTitle.read',\n} as const;\n\nimport { NotificationsTable } from '../NotificationsTable';\nimport { useNotificationsApi } from '../../hooks';\nimport {\n CreatedAfterOptions,\n NotificationsFilters,\n SortBy,\n SortByOptions,\n} from '../NotificationsFilters';\nimport {\n GetNotificationsOptions,\n GetNotificationsResponse,\n GetTopicsResponse,\n} from '../../api';\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\nfunction NotificationsPageContent(\n props: NotificationsPageProps & { headerVariant: 'legacy' | 'bui' },\n) {\n const { t } = useTranslationRef(notificationsTranslationRef);\n const {\n title = t('notificationsPage.title'),\n themeId = 'tool',\n subtitle,\n tooltip,\n type,\n typeLink,\n markAsReadOnLinkOpen,\n headerVariant,\n } = props;\n\n const [refresh, setRefresh] = useState(false);\n // TODO: Reuse useNotificationsRefresh instead of duplicating signals +\n // polling refresh logic here.\n const { lastSignal } = useSignal('notifications');\n const [searchParams] = useSearchParams();\n const highlightedNotificationId = searchParams.get('id') || undefined;\n const [highlightedNotification, setHighlightedNotification] = useState<\n Notification | undefined\n >();\n const [unreadOnly, setUnreadOnly] = useState<boolean | undefined>(true);\n const [saved, setSaved] = useState<boolean | undefined>(undefined);\n const [pageNumber, setPageNumber] = useState(0);\n const [pageSize, setPageSize] = useState(5);\n const [containsText, setContainsText] = useState<string>();\n const [createdAfter, setCreatedAfter] = useState<string>('all');\n const [sorting, setSorting] = useState<SortBy>(SortByOptions.newest.sortBy);\n const [severity, setSeverity] = useState<NotificationSeverity>('low');\n const [topic, setTopic] = useState<string>();\n\n const { error, value, retry, loading } = useNotificationsApi<\n [GetNotificationsResponse, NotificationStatus, GetTopicsResponse]\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 if (topic !== undefined) {\n options.topic = topic;\n }\n\n const createdAfterDate =\n CreatedAfterOptions[\n createdAfter as keyof typeof CreatedAfterOptions\n ].getDate();\n if (createdAfterDate.valueOf() > 0) {\n options.createdAfter = createdAfterDate;\n }\n\n return Promise.all([\n api.getNotifications(options),\n api.getStatus(),\n api.getTopics(options),\n ]);\n },\n [\n containsText,\n unreadOnly,\n createdAfter,\n pageNumber,\n pageSize,\n sorting,\n saved,\n severity,\n topic,\n ],\n );\n\n const throttledSetRefresh = 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 useEffect(() => {\n if (!highlightedNotificationId) {\n setHighlightedNotification(undefined);\n return;\n }\n\n setPageNumber(0);\n }, [highlightedNotificationId]);\n\n const { value: fetchedHighlightedNotification } = useNotificationsApi(\n api => {\n if (!highlightedNotificationId) {\n return Promise.resolve(undefined);\n }\n return api.getNotification(highlightedNotificationId);\n },\n [highlightedNotificationId],\n );\n\n useEffect(() => {\n if (!fetchedHighlightedNotification) {\n return;\n }\n\n setHighlightedNotification(fetchedHighlightedNotification);\n if (!fetchedHighlightedNotification.read) {\n setUnreadOnly(true);\n } else {\n setUnreadOnly(false);\n }\n }, [fetchedHighlightedNotification]);\n\n const onUpdate = () => {\n throttledSetRefresh(true);\n };\n\n const notifications = value?.[0]?.notifications;\n const displayedNotifications = useMemo(() => {\n if (\n !highlightedNotification ||\n notifications?.some(\n notification => notification.id === highlightedNotification.id,\n )\n ) {\n return notifications;\n }\n\n // Allow pageSize + 1 so a deep-linked notification can be injected\n // without silently dropping a row that belongs on this page.\n return [highlightedNotification, ...(notifications ?? [])];\n }, [notifications, highlightedNotification]);\n\n const injectedHighlightedNotification =\n highlightedNotification &&\n !notifications?.some(\n notification => notification.id === highlightedNotification.id,\n );\n const displayedPageSize = injectedHighlightedNotification\n ? pageSize + 1\n : pageSize;\n\n if (error) {\n return <ResponseErrorPanel error={error} />;\n }\n\n const totalCount = value?.[0]?.totalCount;\n const isUnread = !!value?.[1]?.unread;\n const allTopics = value?.[2]?.topics;\n\n let tableTitle: string = t(TableTitleKeys.all, {\n count: totalCount ?? 0,\n });\n if (saved) {\n tableTitle = t(TableTitleKeys.saved, {\n count: totalCount ?? 0,\n });\n } else if (unreadOnly === true) {\n tableTitle = t(TableTitleKeys.unread, {\n count: totalCount ?? 0,\n });\n } else if (unreadOnly === false) {\n tableTitle = t(TableTitleKeys.read, {\n count: totalCount ?? 0,\n });\n }\n\n const pageContent = (\n <Content>\n <Grid.Root columns=\"12\" gap=\"6\">\n <Grid.Item colSpan=\"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 topic={topic}\n onTopicChanged={setTopic}\n allTopics={allTopics}\n />\n </Grid.Item>\n <Grid.Item colSpan=\"10\">\n <NotificationsTable\n title={tableTitle}\n isLoading={loading}\n isUnread={isUnread}\n markAsReadOnLinkOpen={markAsReadOnLinkOpen}\n notifications={displayedNotifications}\n highlightedNotificationId={highlightedNotificationId}\n onUpdate={onUpdate}\n setContainsText={setContainsText}\n onPageChange={setPageNumber}\n onRowsPerPageChange={setPageSize}\n page={pageNumber}\n pageSize={displayedPageSize}\n totalCount={totalCount}\n />\n </Grid.Item>\n </Grid.Root>\n </Content>\n );\n\n if (headerVariant === 'bui') {\n return pageContent;\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 {pageContent}\n </PageWithHeader>\n );\n}\n\nexport const NotificationsPage = (props?: NotificationsPageProps) => (\n <NotificationsPageContent {...(props ?? {})} headerVariant=\"legacy\" />\n);\n\nexport const NfsNotificationsPage = (props?: NotificationsPageProps) => (\n <NotificationsPageContent {...(props ?? {})} headerVariant=\"bui\" />\n);\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,13 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { forwardRef, useMemo, useState, useCallback, useEffect } from 'react';
3
3
  import { useNotificationsApi } from '../../hooks/useNotificationsApi.esm.js';
4
+ import { useSignal } from '@backstage/plugin-signals-react';
4
5
  import { useWebNotifications } from '../../hooks/useWebNotifications.esm.js';
5
6
  import { useTitleCounter } from '../../hooks/useTitleCounter.esm.js';
6
7
  import { SidebarItem } from '@backstage/core-components';
7
8
  import { useApi, useRouteRef } from '@backstage/core-plugin-api';
8
9
  import { toastApiRef } from '@backstage/frontend-plugin-api';
9
10
  import { rootRouteRef } from '../../routes.esm.js';
10
- import { useSignal } from '@backstage/plugin-signals-react';
11
11
  import { notificationsApiRef } from '../../api/NotificationsApi.esm.js';
12
12
  import '@backstage/errors';
13
13
  import { MaterialDesignContent, SnackbarProvider, closeSnackbar, enqueueSnackbar } from 'notistack';
@@ -1,18 +1,24 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { useApp } from '@backstage/core-plugin-api';
2
+ import { useApi, iconsApiRef } from '@backstage/frontend-plugin-api';
3
3
  import { SeverityIcon } from './SeverityIcon.esm.js';
4
4
 
5
+ function NotificationIconWithCustomIcon({
6
+ notification
7
+ }) {
8
+ const iconsApi = useApi(iconsApiRef);
9
+ const Icon = iconsApi.getIcon(notification.payload.icon);
10
+ if (Icon) {
11
+ return /* @__PURE__ */ jsx(Icon, {});
12
+ }
13
+ return /* @__PURE__ */ jsx(SeverityIcon, { severity: notification.payload.severity });
14
+ }
5
15
  const NotificationIcon = ({
6
16
  notification
7
17
  }) => {
8
- const app = useApp();
9
- if (notification.payload.icon) {
10
- const Icon = app.getSystemIcon(notification.payload.icon);
11
- if (Icon) {
12
- return /* @__PURE__ */ jsx(Icon, {});
13
- }
18
+ if (!notification.payload.icon) {
19
+ return /* @__PURE__ */ jsx(SeverityIcon, { severity: notification.payload.severity });
14
20
  }
15
- return /* @__PURE__ */ jsx(SeverityIcon, { severity: notification.payload.severity });
21
+ return /* @__PURE__ */ jsx(NotificationIconWithCustomIcon, { notification });
16
22
  };
17
23
 
18
24
  export { NotificationIcon };
@@ -1 +1 @@
1
- {"version":3,"file":"NotificationIcon.esm.js","sources":["../../../src/components/NotificationsTable/NotificationIcon.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 { Notification } from '@backstage/plugin-notifications-common';\nimport { useApp } from '@backstage/core-plugin-api';\nimport { SeverityIcon } from './SeverityIcon';\n\nexport const NotificationIcon = ({\n notification,\n}: {\n notification: Notification;\n}) => {\n const app = useApp();\n\n if (notification.payload.icon) {\n const Icon = app.getSystemIcon(notification.payload.icon);\n if (Icon) {\n return <Icon />;\n }\n }\n return <SeverityIcon severity={notification.payload.severity} />;\n};\n"],"names":[],"mappings":";;;;AAmBO,MAAM,mBAAmB,CAAC;AAAA,EAC/B;AACF,CAAA,KAEM;AACJ,EAAA,MAAM,MAAM,MAAA,EAAO;AAEnB,EAAA,IAAI,YAAA,CAAa,QAAQ,IAAA,EAAM;AAC7B,IAAA,MAAM,IAAA,GAAO,GAAA,CAAI,aAAA,CAAc,YAAA,CAAa,QAAQ,IAAI,CAAA;AACxD,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,2BAAQ,IAAA,EAAA,EAAK,CAAA;AAAA,IACf;AAAA,EACF;AACA,EAAA,uBAAO,GAAA,CAAC,YAAA,EAAA,EAAa,QAAA,EAAU,YAAA,CAAa,QAAQ,QAAA,EAAU,CAAA;AAChE;;;;"}
1
+ {"version":3,"file":"NotificationIcon.esm.js","sources":["../../../src/components/NotificationsTable/NotificationIcon.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 { Notification } from '@backstage/plugin-notifications-common';\nimport { iconsApiRef, useApi } from '@backstage/frontend-plugin-api';\nimport { SeverityIcon } from './SeverityIcon';\n\nfunction NotificationIconWithCustomIcon({\n notification,\n}: {\n notification: Notification;\n}) {\n const iconsApi = useApi(iconsApiRef);\n const Icon = iconsApi.getIcon(notification.payload.icon!);\n if (Icon) {\n return <Icon />;\n }\n return <SeverityIcon severity={notification.payload.severity} />;\n}\n\nexport const NotificationIcon = ({\n notification,\n}: {\n notification: Notification;\n}) => {\n if (!notification.payload.icon) {\n return <SeverityIcon severity={notification.payload.severity} />;\n }\n\n return <NotificationIconWithCustomIcon notification={notification} />;\n};\n"],"names":[],"mappings":";;;;AAmBA,SAAS,8BAAA,CAA+B;AAAA,EACtC;AACF,CAAA,EAEG;AACD,EAAA,MAAM,QAAA,GAAW,OAAO,WAAW,CAAA;AACnC,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,YAAA,CAAa,QAAQ,IAAK,CAAA;AACxD,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,2BAAQ,IAAA,EAAA,EAAK,CAAA;AAAA,EACf;AACA,EAAA,uBAAO,GAAA,CAAC,YAAA,EAAA,EAAa,QAAA,EAAU,YAAA,CAAa,QAAQ,QAAA,EAAU,CAAA;AAChE;AAEO,MAAM,mBAAmB,CAAC;AAAA,EAC/B;AACF,CAAA,KAEM;AACJ,EAAA,IAAI,CAAC,YAAA,CAAa,OAAA,CAAQ,IAAA,EAAM;AAC9B,IAAA,uBAAO,GAAA,CAAC,YAAA,EAAA,EAAa,QAAA,EAAU,YAAA,CAAa,QAAQ,QAAA,EAAU,CAAA;AAAA,EAChE;AAEA,EAAA,uBAAO,GAAA,CAAC,kCAA+B,YAAA,EAA4B,CAAA;AACrE;;;;"}
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { useState, useCallback, useMemo, useEffect } from 'react';
2
+ import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
3
3
  import throttle from 'lodash/throttle';
4
4
  import RelativeTime from 'react-relative-time';
5
5
  import { Checkbox, Flex, Text, Dialog, DialogHeader, DialogBody, DialogFooter, Button } from '@backstage/ui';
@@ -23,6 +23,7 @@ const NotificationsTable = ({
23
23
  markAsReadOnLinkOpen,
24
24
  isLoading,
25
25
  notifications = [],
26
+ highlightedNotificationId,
26
27
  isUnread,
27
28
  onUpdate,
28
29
  setContainsText,
@@ -101,6 +102,30 @@ const NotificationsTable = ({
101
102
  setSelectedNotifications(new Set(intersect));
102
103
  }
103
104
  }, [notifications, selectedNotifications]);
105
+ const focusedHighlightedIdRef = useRef(void 0);
106
+ useEffect(() => {
107
+ if (!highlightedNotificationId) {
108
+ focusedHighlightedIdRef.current = void 0;
109
+ return;
110
+ }
111
+ if (focusedHighlightedIdRef.current === highlightedNotificationId) {
112
+ return;
113
+ }
114
+ const highlightedElement = document.getElementById(
115
+ `notification-${highlightedNotificationId}`
116
+ );
117
+ if (!highlightedElement) {
118
+ return;
119
+ }
120
+ if (typeof highlightedElement.scrollIntoView === "function") {
121
+ highlightedElement.scrollIntoView({
122
+ behavior: "smooth",
123
+ block: "center"
124
+ });
125
+ }
126
+ highlightedElement.focus({ preventScroll: true });
127
+ focusedHighlightedIdRef.current = highlightedNotificationId;
128
+ }, [highlightedNotificationId, notifications]);
104
129
  const compactColumns = useMemo(() => {
105
130
  const showToolbar = notifications.length > 0;
106
131
  return [
@@ -133,47 +158,56 @@ const NotificationsTable = ({
133
158
  customFilterAndSearch: () => true,
134
159
  cellStyle: { paddingLeft: 0 },
135
160
  render: (notification) => {
136
- return /* @__PURE__ */ jsxs(Flex, { gap: "4", align: "center", children: [
137
- /* @__PURE__ */ jsx("div", { className: styles.severityItem, children: /* @__PURE__ */ jsx(NotificationIcon, { notification }) }),
138
- /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "1", children: [
139
- /* @__PURE__ */ jsx(Text, { variant: "body-medium", children: notification.payload.link ? /* @__PURE__ */ jsx(
140
- Link,
141
- {
142
- to: notification.payload.link,
143
- onClick: () => {
144
- if (markAsReadOnLinkOpen && !notification.read) {
145
- onSwitchReadStatus([notification.id], true);
161
+ const isHighlighted = highlightedNotificationId === notification.id;
162
+ return /* @__PURE__ */ jsx(
163
+ "div",
164
+ {
165
+ id: `notification-${notification.id}`,
166
+ tabIndex: isHighlighted ? -1 : void 0,
167
+ className: isHighlighted ? styles.highlightedNotification : void 0,
168
+ children: /* @__PURE__ */ jsxs(Flex, { gap: "4", align: "center", children: [
169
+ /* @__PURE__ */ jsx("div", { className: styles.severityItem, children: /* @__PURE__ */ jsx(NotificationIcon, { notification }) }),
170
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", gap: "1", children: [
171
+ /* @__PURE__ */ jsx(Text, { variant: "body-medium", children: notification.payload.link ? /* @__PURE__ */ jsx(
172
+ Link,
173
+ {
174
+ to: notification.payload.link,
175
+ onClick: () => {
176
+ if (markAsReadOnLinkOpen && !notification.read) {
177
+ onSwitchReadStatus([notification.id], true);
178
+ }
179
+ },
180
+ children: notification.payload.title
181
+ }
182
+ ) : notification.payload.title }),
183
+ notification.payload.description ? /* @__PURE__ */ jsx(
184
+ NotificationDescription,
185
+ {
186
+ description: notification.payload.description
146
187
  }
147
- },
148
- children: notification.payload.title
149
- }
150
- ) : notification.payload.title }),
151
- notification.payload.description ? /* @__PURE__ */ jsx(
152
- NotificationDescription,
153
- {
154
- description: notification.payload.description
155
- }
156
- ) : null,
157
- /* @__PURE__ */ jsxs(Text, { variant: "body-small", color: "secondary", children: [
158
- !notification.user && /* @__PURE__ */ jsx(RiRssFill, { size: 14, className: styles.broadcastIcon }),
159
- notification.origin && /* @__PURE__ */ jsxs(Fragment, { children: [
160
- /* @__PURE__ */ jsx("span", { className: styles.notificationInfoRow, children: notification.origin }),
161
- "\u2022"
162
- ] }),
163
- notification.payload.topic && /* @__PURE__ */ jsxs(Fragment, { children: [
164
- /* @__PURE__ */ jsx("span", { className: styles.notificationInfoRow, children: notification.payload.topic }),
165
- "\u2022"
166
- ] }),
167
- notification.created && /* @__PURE__ */ jsx(
168
- RelativeTime,
169
- {
170
- value: notification.created,
171
- className: styles.notificationInfoRow
172
- }
173
- )
188
+ ) : null,
189
+ /* @__PURE__ */ jsxs(Text, { variant: "body-small", color: "secondary", children: [
190
+ !notification.user && /* @__PURE__ */ jsx(RiRssFill, { size: 14, className: styles.broadcastIcon }),
191
+ notification.origin && /* @__PURE__ */ jsxs(Fragment, { children: [
192
+ /* @__PURE__ */ jsx("span", { className: styles.notificationInfoRow, children: notification.origin }),
193
+ "\u2022"
194
+ ] }),
195
+ notification.payload.topic && /* @__PURE__ */ jsxs(Fragment, { children: [
196
+ /* @__PURE__ */ jsx("span", { className: styles.notificationInfoRow, children: notification.payload.topic }),
197
+ "\u2022"
198
+ ] }),
199
+ notification.created && /* @__PURE__ */ jsx(
200
+ RelativeTime,
201
+ {
202
+ value: notification.created,
203
+ className: styles.notificationInfoRow
204
+ }
205
+ )
206
+ ] })
207
+ ] })
174
208
  ] })
175
- ] })
176
- ] });
209
+ }
210
+ );
177
211
  }
178
212
  },
179
213
  {
@@ -209,7 +243,8 @@ const NotificationsTable = ({
209
243
  onSwitchSavedStatus,
210
244
  onMarkAllRead,
211
245
  onNotificationsSelectChange,
212
- markAsReadOnLinkOpen
246
+ markAsReadOnLinkOpen,
247
+ highlightedNotificationId
213
248
  ]);
214
249
  return /* @__PURE__ */ jsxs(Fragment, { children: [
215
250
  /* @__PURE__ */ jsx(