feat: notification processor improvements

Notification processor functions are now renamed to `preProcess` and `postProcess`.
Additionally, processor name is now required to be returned by `getName`.
A new processor functionality `processOptions` was added to process options before sending the notification.

Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
Heikki Hellgren
2024-04-08 08:15:13 +03:00
parent 0d60f65572
commit 0d99528300
6 changed files with 178 additions and 75 deletions
@@ -31,7 +31,10 @@ import {
RELATION_PARENT_OF,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { NotificationProcessor } from '@backstage/plugin-notifications-node';
import {
NotificationProcessor,
NotificationSendOptions,
} from '@backstage/plugin-notifications-node';
import { InputError } from '@backstage/errors';
import {
AuthService,
@@ -45,6 +48,7 @@ import {
NewNotificationSignal,
Notification,
NotificationReadSignal,
NotificationStatus,
} from '@backstage/plugin-notifications-common';
import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams';
@@ -73,7 +77,7 @@ export async function createRouter(
userInfo,
discovery,
catalog,
processors,
processors = [],
signals,
} = options;
@@ -173,18 +177,54 @@ export async function createRouter(
return users;
};
const decorateNotification = async (notification: Notification) => {
let ret = notification;
for (const processor of processors ?? []) {
ret = processor.decorate ? await processor.decorate(ret) : ret;
const processOptions = async (opts: NotificationSendOptions) => {
let ret = opts;
for (const processor of processors) {
try {
ret = processor.processOptions
? await processor.processOptions(ret)
: ret;
} catch (e) {
logger.error(
`Error while processing notification options with ${processor.getName()}: ${e}`,
);
}
}
return ret;
};
const processorSendNotification = async (notification: Notification) => {
for (const processor of processors ?? []) {
if (processor.send) {
processor.send(notification);
const preProcessNotification = async (
notification: Notification,
opts: NotificationSendOptions,
) => {
let ret = notification;
for (const processor of processors) {
try {
ret = processor.preProcess
? await processor.preProcess(ret, opts)
: ret;
} catch (e) {
logger.error(
`Error while pre processing notification with ${processor.getName()}: ${e}`,
);
}
}
return ret;
};
const postProcessNotification = async (
notification: Notification,
opts: NotificationSendOptions,
) => {
for (const processor of processors) {
if (processor.postProcess) {
try {
await processor.postProcess(notification, opts);
} catch (e) {
logger.error(
`Error while post processing notification with ${processor.getName()}: ${e}`,
);
}
}
}
};
@@ -250,7 +290,7 @@ export async function createRouter(
});
});
router.get('/status', async (req, res) => {
router.get('/status', async (req: Request<any, NotificationStatus>, res) => {
const user = await getUser(req);
const status = await store.getStatus({ user });
res.send(status);
@@ -313,17 +353,19 @@ export async function createRouter(
const sendBroadcastNotification = async (
baseNotification: Omit<Notification, 'user' | 'id'>,
opts: { scope?: string; origin: string },
opts: NotificationSendOptions,
origin: string,
) => {
const { scope, origin } = opts;
const { scope } = opts.payload;
const broadcastNotification = {
...baseNotification,
user: null,
id: uuid(),
};
const notification = await decorateNotification({
...broadcastNotification,
user: '',
});
const notification = await preProcessNotification(
broadcastNotification,
opts,
);
let existingNotification;
if (scope) {
existingNotification = await store.getExistingScopeBroadcast({
@@ -342,7 +384,6 @@ export async function createRouter(
} else {
await store.saveBroadcast(notification);
}
processorSendNotification(ret);
if (signals) {
await signals.publish<NewNotificationSignal>({
@@ -353,6 +394,7 @@ export async function createRouter(
},
channel: 'notifications',
});
postProcessNotification(ret, opts);
}
return notification;
};
@@ -360,10 +402,11 @@ export async function createRouter(
const sendUserNotifications = async (
baseNotification: Omit<Notification, 'user' | 'id'>,
users: string[],
opts: { scope?: string; origin: string },
opts: NotificationSendOptions,
origin: string,
) => {
const notifications = [];
const { scope, origin } = opts;
const { scope } = opts.payload;
const uniqueUsers = [...new Set(users)];
for (const user of uniqueUsers) {
const userNotification = {
@@ -371,7 +414,7 @@ export async function createRouter(
id: uuid(),
user,
};
const notification = await decorateNotification(userNotification);
const notification = await preProcessNotification(userNotification, opts);
let existingNotification;
if (scope) {
@@ -393,7 +436,6 @@ export async function createRouter(
await store.saveNotification(notification);
}
processorSendNotification(ret);
notifications.push(ret);
if (signals) {
@@ -406,59 +448,68 @@ export async function createRouter(
channel: 'notifications',
});
}
postProcessNotification(ret, opts);
}
return notifications;
};
// Add new notification
router.post('/', async (req, res) => {
const { recipients, payload } = req.body;
const notifications = [];
let users = [];
router.post(
'/',
async (req: Request<any, Notification[], NotificationSendOptions>, res) => {
const opts = await processOptions(req.body);
const { recipients, payload } = opts;
const notifications: Notification[] = [];
let users = [];
const credentials = await httpAuth.credentials(req, { allow: ['service'] });
const { title, scope } = payload;
if (!recipients || !title) {
logger.error(`Invalid notification request received`);
throw new InputError();
}
const origin = credentials.principal.subject;
const baseNotification = {
payload: {
...payload,
severity: payload.severity ?? 'normal',
},
origin,
created: new Date(),
};
if (recipients.type === 'broadcast') {
const broadcast = await sendBroadcastNotification(baseNotification, {
scope,
origin,
const credentials = await httpAuth.credentials(req, {
allow: ['service'],
});
notifications.push(broadcast);
} else {
const entityRef = recipients.entityRef;
try {
users = await getUsersForEntityRef(entityRef);
} catch (e) {
const { title } = payload;
if (!recipients || !title) {
logger.error(`Invalid notification request received`);
throw new InputError();
}
const userNotifications = await sendUserNotifications(
baseNotification,
users,
{ scope, origin },
);
notifications.push(...userNotifications);
}
res.json(notifications);
});
const origin = credentials.principal.subject;
const baseNotification = {
payload: {
...payload,
severity: payload.severity ?? 'normal',
},
origin,
created: new Date(),
};
if (recipients.type === 'broadcast') {
const broadcast = await sendBroadcastNotification(
baseNotification,
opts,
origin,
);
notifications.push(broadcast);
} else {
const entityRef = recipients.entityRef;
try {
users = await getUsersForEntityRef(entityRef);
} catch (e) {
throw new InputError();
}
const userNotifications = await sendUserNotifications(
baseNotification,
users,
opts,
origin,
);
notifications.push(...userNotifications);
}
res.json(notifications);
},
);
router.use(errorHandler());
return router;