From 6a79f9f1eed993d1294a52dd6ff30a121a43e83a Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 25 Jan 2024 09:07:16 +0200 Subject: [PATCH] feat: integrate notifications with signals Signed-off-by: Heikki Hellgren --- packages/backend/src/index.ts | 1 + packages/backend/src/plugins/notifications.ts | 1 + plugins/notifications-backend/README.md | 4 +++- plugins/notifications-backend/api-report.md | 7 +++++-- plugins/notifications-backend/package.json | 3 ++- plugins/notifications-backend/src/plugin.ts | 4 ++++ .../src/service/router.test.ts | 6 ++++++ .../src/service/router.ts | 17 +++++++++++---- .../src/service/standaloneServer.ts | 21 +++++++++++++++++++ plugins/notifications-node/api-report.md | 6 ++++-- plugins/notifications-node/package.json | 1 + plugins/notifications-node/src/lib.ts | 6 ++++-- .../src/service/DefaultNotificationService.ts | 13 ++++++------ plugins/notifications/package.json | 1 + .../NotificationsPage/NotificationsPage.tsx | 10 ++++++++- .../NotificationsSideBarItem.tsx | 19 ++++++++++------- yarn.lock | 14 +++++-------- 17 files changed, 98 insertions(+), 36 deletions(-) diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index c60d3d8feb..c140e6dcf5 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -109,6 +109,7 @@ function makeCreateEnv(config: Config) { logger: root.child({ type: 'plugin' }), discovery, tokenManager, + signalService, }); root.info(`Created UrlReader ${reader}`); diff --git a/packages/backend/src/plugins/notifications.ts b/packages/backend/src/plugins/notifications.ts index 583495cba5..74e1915cd4 100644 --- a/packages/backend/src/plugins/notifications.ts +++ b/packages/backend/src/plugins/notifications.ts @@ -36,5 +36,6 @@ export default async function createPlugin( tokenManager: env.tokenManager, database: env.database, discovery: env.discovery, + signalService: env.signalService, }); } diff --git a/plugins/notifications-backend/README.md b/plugins/notifications-backend/README.md index da1d64cf6d..ea317afefe 100644 --- a/plugins/notifications-backend/README.md +++ b/plugins/notifications-backend/README.md @@ -4,7 +4,8 @@ Welcome to the notifications backend plugin! ## Getting started -First you have to install the `@backstage/plugin-notifications-node` package. +First you have to install `@backstage/plugin-notifications-node` and `@backstage/plugin-signals-node` +packages. Then create a new file to `packages/backend/src/plugins/notifications.ts`: @@ -22,6 +23,7 @@ export default async function createPlugin( tokenManager: env.tokenManager, database: env.database, discovery: env.discovery, + signalService: env.signalService }); } ``` diff --git a/plugins/notifications-backend/api-report.md b/plugins/notifications-backend/api-report.md index b10bc65e8c..b11c2269bd 100644 --- a/plugins/notifications-backend/api-report.md +++ b/plugins/notifications-backend/api-report.md @@ -5,12 +5,13 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { DiscoveryService } from '@backstage/backend-plugin-api'; import express from 'express'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; +import { SignalService } from '@backstage/plugin-signals-node'; import { TokenManager } from '@backstage/backend-common'; // @public (undocumented) @@ -32,7 +33,7 @@ export interface RouterOptions { // (undocumented) database: PluginDatabaseManager; // (undocumented) - discovery: DiscoveryApi; + discovery: DiscoveryService; // (undocumented) identity: IdentityApi; // (undocumented) @@ -40,6 +41,8 @@ export interface RouterOptions { // (undocumented) processors?: NotificationProcessor[]; // (undocumented) + signalService?: SignalService; + // (undocumented) tokenManager: TokenManager; } diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 8d56034b47..810fa95085 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -27,11 +27,12 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", - "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", + "@backstage/plugin-signals-node": "workspace:^", "@types/express": "*", "express": "^4.17.1", "express-promise-router": "^4.1.0", diff --git a/plugins/notifications-backend/src/plugin.ts b/plugins/notifications-backend/src/plugin.ts index 6ab3c464db..3d9937525c 100644 --- a/plugins/notifications-backend/src/plugin.ts +++ b/plugins/notifications-backend/src/plugin.ts @@ -18,6 +18,7 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { createRouter } from './service/router'; +import { signalService } from '@backstage/plugin-signals-node'; /** * Notifications backend plugin @@ -35,6 +36,7 @@ export const notificationsPlugin = createBackendPlugin({ database: coreServices.database, tokenManager: coreServices.tokenManager, discovery: coreServices.discovery, + signals: signalService, }, async init({ httpRouter, @@ -43,6 +45,7 @@ export const notificationsPlugin = createBackendPlugin({ database, tokenManager, discovery, + signals, }) { httpRouter.use( await createRouter({ @@ -51,6 +54,7 @@ export const notificationsPlugin = createBackendPlugin({ database, tokenManager, discovery, + signalService: signals, }), ); }, diff --git a/plugins/notifications-backend/src/service/router.test.ts b/plugins/notifications-backend/src/service/router.test.ts index 3ad9b4c531..3152b1b07f 100644 --- a/plugins/notifications-backend/src/service/router.test.ts +++ b/plugins/notifications-backend/src/service/router.test.ts @@ -26,6 +26,7 @@ import request from 'supertest'; import { createRouter } from './router'; import { IdentityApi } from '@backstage/plugin-auth-node'; import { ConfigReader } from '@backstage/config'; +import { SignalService } from '@backstage/plugin-signals-node'; function createDatabase(): PluginDatabaseManager { return DatabaseManager.fromConfig( @@ -65,6 +66,10 @@ describe('createRouter', () => { getExternalBaseUrl: jest.fn(), }; + const signalService: jest.Mocked = { + publish: jest.fn(), + }; + beforeAll(async () => { const router = await createRouter({ logger: getVoidLogger(), @@ -72,6 +77,7 @@ describe('createRouter', () => { database: createDatabase(), tokenManager: mockedTokenManager, discovery, + signalService, }); app = express().use(router); }); diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index af5a5a826f..5246bc140a 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -39,8 +39,8 @@ import { } from '@backstage/catalog-model'; import { NotificationProcessor } from '../types'; import { AuthenticationError } from '@backstage/errors'; -import { DiscoveryApi } from '@backstage/core-plugin-api'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api'; +import { SignalService } from '@backstage/plugin-signals-node'; /** @public */ export interface RouterOptions { @@ -48,7 +48,8 @@ export interface RouterOptions { identity: IdentityApi; database: PluginDatabaseManager; tokenManager: TokenManager; - discovery: DiscoveryApi; + discovery: DiscoveryService; + signalService?: SignalService; catalog?: CatalogApi; processors?: NotificationProcessor[]; } @@ -65,6 +66,7 @@ export async function createRouter( catalog, tokenManager, processors, + signalService, } = options; const catalogClient = @@ -248,7 +250,14 @@ export async function createRouter( } } notifications.push(notification); - // TODO: Signal service + } + + if (signalService) { + await signalService.publish({ + recipients: users, + message: { action: 'refresh' }, + channel: 'notifications', + }); } res.send(notifications); diff --git a/plugins/notifications-backend/src/service/standaloneServer.ts b/plugins/notifications-backend/src/service/standaloneServer.ts index 4a8ba80f72..e2841543b1 100644 --- a/plugins/notifications-backend/src/service/standaloneServer.ts +++ b/plugins/notifications-backend/src/service/standaloneServer.ts @@ -31,6 +31,12 @@ import { CatalogRequestOptions, GetEntitiesByRefsRequest, } from '@backstage/catalog-client'; +import { DefaultSignalService } from '@backstage/plugin-signals-node'; +import { + EventBroker, + EventParams, + EventSubscriber, +} from '@backstage/plugin-events-node'; export interface ServerOptions { port: number; @@ -90,6 +96,20 @@ export async function startStandaloneServer( }, }; + const mockSubscribers: EventSubscriber[] = []; + const eventBroker: EventBroker = { + async publish(params: EventParams): Promise { + mockSubscribers.forEach(sub => sub.onEvent(params)); + }, + subscribe(...subscribers: EventSubscriber[]) { + subscribers.flat().forEach(subscriber => { + mockSubscribers.push(subscriber); + }); + }, + }; + + const signalService = DefaultSignalService.create({ eventBroker }); + const router = await createRouter({ logger, identity: identityMock, @@ -97,6 +117,7 @@ export async function startStandaloneServer( catalog: catalogApi, discovery, tokenManager, + signalService, }); let service = createServiceBuilder(module) diff --git a/plugins/notifications-node/api-report.md b/plugins/notifications-node/api-report.md index b7f8047983..f6b4987c90 100644 --- a/plugins/notifications-node/api-report.md +++ b/plugins/notifications-node/api-report.md @@ -3,10 +3,11 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { DiscoveryService } from '@backstage/backend-plugin-api'; import { LoggerService } from '@backstage/backend-plugin-api'; import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { ServiceRef } from '@backstage/backend-plugin-api'; +import { SignalService } from '@backstage/plugin-signals-node'; import { TokenManager } from '@backstage/backend-common'; // @public (undocumented) @@ -40,7 +41,8 @@ export const notificationService: ServiceRef; // @public (undocumented) export type NotificationServiceOptions = { logger: LoggerService; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; tokenManager: TokenManager; + signalService: SignalService; }; ``` diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index d5cb66ba3d..fd27d3c63c 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -33,6 +33,7 @@ "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", + "@backstage/plugin-signals-node": "workspace:^", "knex": "^3.0.0", "uuid": "^8.0.0" } diff --git a/plugins/notifications-node/src/lib.ts b/plugins/notifications-node/src/lib.ts index 2cf396bbc3..41773143d2 100644 --- a/plugins/notifications-node/src/lib.ts +++ b/plugins/notifications-node/src/lib.ts @@ -20,6 +20,7 @@ import { } from '@backstage/backend-plugin-api'; import { DefaultNotificationService } from './service'; import { NotificationService } from './service/NotificationService'; +import { signalService } from '@backstage/plugin-signals-node'; /** @public */ export const notificationService = createServiceRef({ @@ -32,13 +33,14 @@ export const notificationService = createServiceRef({ logger: coreServices.logger, discovery: coreServices.discovery, tokenManager: coreServices.tokenManager, - // TODO: Signals + signals: signalService, }, - factory({ logger, discovery, tokenManager }) { + factory({ logger, discovery, tokenManager, signals }) { return DefaultNotificationService.create({ logger, discovery, tokenManager, + signalService: signals, }); }, }), diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.ts b/plugins/notifications-node/src/service/DefaultNotificationService.ts index 5580773151..8be0f71799 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.ts @@ -14,18 +14,17 @@ * limitations under the License. */ import { Notification } from '@backstage/plugin-notifications-common'; -import { - PluginEndpointDiscovery, - TokenManager, -} from '@backstage/backend-common'; +import { TokenManager } from '@backstage/backend-common'; import { NotificationService } from './NotificationService'; -import { LoggerService } from '@backstage/backend-plugin-api'; +import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api'; +import { SignalService } from '@backstage/plugin-signals-node'; /** @public */ export type NotificationServiceOptions = { logger: LoggerService; - discovery: PluginEndpointDiscovery; + discovery: DiscoveryService; tokenManager: TokenManager; + signalService: SignalService; }; /** @public */ @@ -40,7 +39,7 @@ export type NotificationSendOptions = { export class DefaultNotificationService implements NotificationService { private constructor( private readonly logger: LoggerService, - private readonly discovery: PluginEndpointDiscovery, + private readonly discovery: DiscoveryService, private readonly tokenManager: TokenManager, ) {} diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 80804a68c4..57f0b38e82 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -27,6 +27,7 @@ "@backstage/core-plugin-api": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", + "@backstage/plugin-signals-react": "workspace:^", "@backstage/theme": "workspace:^", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index 4ebcaa6fc4..20b106f6f2 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Content, ErrorPanel, @@ -27,6 +27,7 @@ import Bookmark from '@material-ui/icons/Bookmark'; import Check from '@material-ui/icons/Check'; import Inbox from '@material-ui/icons/Inbox'; import { NotificationType } from '@backstage/plugin-notifications-common'; +import { useSignal } from '@backstage/plugin-signals-react'; const useStyles = makeStyles(_theme => ({ filterButton: { @@ -43,6 +44,13 @@ export const NotificationsPage = () => { [type], ); + const { lastSignal } = useSignal('notifications'); + useEffect(() => { + if (lastSignal && lastSignal.action === 'refresh') { + retry(); + } + }, [lastSignal, retry]); + const onUpdate = () => { retry(); }; diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 6a562b20c3..255105a499 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -19,25 +19,30 @@ import { SidebarItem } from '@backstage/core-components'; import NotificationsIcon from '@material-ui/icons/Notifications'; import { useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; +import { useSignal } from '@backstage/plugin-signals-react'; /** @public */ export const NotificationsSidebarItem = () => { - const { - loading, - error, - value, - retry: _retry, - } = useNotificationsApi(api => api.getStatus()); + const { loading, error, value, retry } = useNotificationsApi(api => + api.getStatus(), + ); const [unreadCount, setUnreadCount] = React.useState(0); const notificationsRoute = useRouteRef(rootRouteRef); + const { lastSignal } = useSignal('notifications'); + useEffect(() => { + if (lastSignal && lastSignal.action === 'refresh') { + retry(); + } + }, [lastSignal, retry]); + useEffect(() => { if (!loading && !error && value) { setUnreadCount(value.unread); } }, [loading, error, value]); - // TODO: Figure out if there count can be added to hasNotifications + // TODO: Figure out if the count can be added to hasNotifications return (