feat: integrate notifications with signals
Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
@@ -109,6 +109,7 @@ function makeCreateEnv(config: Config) {
|
||||
logger: root.child({ type: 'plugin' }),
|
||||
discovery,
|
||||
tokenManager,
|
||||
signalService,
|
||||
});
|
||||
|
||||
root.info(`Created UrlReader ${reader}`);
|
||||
|
||||
@@ -36,5 +36,6 @@ export default async function createPlugin(
|
||||
tokenManager: env.tokenManager,
|
||||
database: env.database,
|
||||
discovery: env.discovery,
|
||||
signalService: env.signalService,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -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<SignalService> = {
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<void> {
|
||||
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)
|
||||
|
||||
@@ -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<NotificationService, 'plugin'>;
|
||||
// @public (undocumented)
|
||||
export type NotificationServiceOptions = {
|
||||
logger: LoggerService;
|
||||
discovery: PluginEndpointDiscovery;
|
||||
discovery: DiscoveryService;
|
||||
tokenManager: TokenManager;
|
||||
signalService: SignalService;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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<NotificationService>({
|
||||
@@ -32,13 +33,14 @@ export const notificationService = createServiceRef<NotificationService>({
|
||||
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,
|
||||
});
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -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,
|
||||
) {}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
+12
-7
@@ -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 (
|
||||
<SidebarItem
|
||||
icon={NotificationsIcon}
|
||||
|
||||
@@ -7789,11 +7789,12 @@ __metadata:
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "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": "*"
|
||||
"@types/supertest": ^2.0.8
|
||||
express: ^4.17.1
|
||||
@@ -7827,6 +7828,7 @@ __metadata:
|
||||
"@backstage/catalog-model": "workspace:^"
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/plugin-notifications-common": "workspace:^"
|
||||
"@backstage/plugin-signals-node": "workspace:^"
|
||||
knex: ^3.0.0
|
||||
uuid: ^8.0.0
|
||||
languageName: unknown
|
||||
@@ -7843,6 +7845,7 @@ __metadata:
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/plugin-notifications-common": "workspace:^"
|
||||
"@backstage/plugin-signals-react": "workspace:^"
|
||||
"@backstage/test-utils": "workspace:^"
|
||||
"@backstage/theme": "workspace:^"
|
||||
"@material-ui/core": ^4.9.13
|
||||
@@ -25320,20 +25323,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dom-accessibility-api@npm:^0.5.6":
|
||||
"dom-accessibility-api@npm:^0.5.6, dom-accessibility-api@npm:^0.5.9":
|
||||
version: 0.5.16
|
||||
resolution: "dom-accessibility-api@npm:0.5.16"
|
||||
checksum: 005eb283caef57fc1adec4d5df4dd49189b628f2f575af45decb210e04d634459e3f1ee64f18b41e2dcf200c844bc1d9279d80807e686a30d69a4756151ad248
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dom-accessibility-api@npm:^0.5.9":
|
||||
version: 0.5.13
|
||||
resolution: "dom-accessibility-api@npm:0.5.13"
|
||||
checksum: a5a5f14c01e466d424750aaac9225f1dc43cf16d101a1c40e01a554abce63c48084707002c39b805f2ce212273c179dd6d2258175997cd06d5f79851bf52dd40
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dom-accessibility-api@npm:^0.6.3":
|
||||
version: 0.6.3
|
||||
resolution: "dom-accessibility-api@npm:0.6.3"
|
||||
|
||||
Reference in New Issue
Block a user