feat: support for topic in notification
using same topic twice will restore existing notification in that topic if any. this can be useful if you want to remind users about some specific thing over time Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
@@ -21,12 +21,14 @@ import { PluginEnvironment } from '../types';
|
||||
export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
// TODO: Remove this test code
|
||||
setInterval(() => {
|
||||
env.notificationService.send({
|
||||
receivers: { type: 'broadcast' },
|
||||
title: 'Test',
|
||||
description: 'Test',
|
||||
link: '/catalog',
|
||||
topic: 'topic',
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ exports.up = async function up(knex) {
|
||||
table.string('title').notNullable();
|
||||
table.text('description').notNullable();
|
||||
table.text('link').notNullable();
|
||||
table.text('icon').nullable();
|
||||
table.text('image').nullable();
|
||||
table.datetime('created').notNullable();
|
||||
table.text('topic').nullable();
|
||||
table.datetime('created').defaultTo(knex.fn.now()).notNullable();
|
||||
table.datetime('updated').nullable();
|
||||
table.datetime('read').nullable();
|
||||
table.datetime('done').nullable();
|
||||
table.boolean('saved').defaultTo(false).notNullable();
|
||||
|
||||
@@ -108,6 +108,58 @@ export class DatabaseNotificationsStore implements NotificationsStore {
|
||||
};
|
||||
}
|
||||
|
||||
async getExistingTopicNotification(options: {
|
||||
user_ref: string;
|
||||
topic: string;
|
||||
}) {
|
||||
const query = this.db('notifications')
|
||||
.where('userRef', options.user_ref)
|
||||
.where('topic', options.topic)
|
||||
.select('*')
|
||||
.limit(1);
|
||||
|
||||
const rows = await query;
|
||||
if (!rows || rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return rows[0] as Notification;
|
||||
}
|
||||
|
||||
async restoreExistingNotification(options: {
|
||||
id: string;
|
||||
notification: Notification;
|
||||
}) {
|
||||
const query = this.db('notifications')
|
||||
.where('id', options.id)
|
||||
.where('userRef', options.notification.userRef);
|
||||
const rows = await query.update({
|
||||
title: options.notification.title,
|
||||
description: options.notification.description,
|
||||
link: options.notification.link,
|
||||
topic: options.notification.topic,
|
||||
updated: options.notification.created,
|
||||
read: null,
|
||||
done: null,
|
||||
});
|
||||
|
||||
if (!rows) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.getNotification(options);
|
||||
}
|
||||
|
||||
async getNotification(options: { id: string }) {
|
||||
const rows = await this.db('notifications')
|
||||
.where('id', options.id)
|
||||
.select('*')
|
||||
.limit(1);
|
||||
if (!rows || rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return rows[0] as Notification;
|
||||
}
|
||||
|
||||
async markRead(options: NotificationModifyOptions): Promise<void> {
|
||||
const notificationQuery = this.getNotificationsBaseQuery(options);
|
||||
await notificationQuery.update({ read: new Date() });
|
||||
|
||||
@@ -37,6 +37,18 @@ export interface NotificationsStore {
|
||||
|
||||
saveNotification(notification: Notification): Promise<void>;
|
||||
|
||||
getExistingTopicNotification(options: {
|
||||
user_ref: string;
|
||||
topic: string;
|
||||
}): Promise<Notification | null>;
|
||||
|
||||
restoreExistingNotification(options: {
|
||||
id: string;
|
||||
notification: Notification;
|
||||
}): Promise<Notification | null>;
|
||||
|
||||
getNotification(options: { id: string }): Promise<Notification | null>;
|
||||
|
||||
getStatus(options: NotificationGetOptions): Promise<NotificationStatus>;
|
||||
|
||||
markRead(options: NotificationModifyOptions): Promise<void>;
|
||||
|
||||
@@ -41,6 +41,7 @@ import { NotificationProcessor } from '../types';
|
||||
import { AuthenticationError } from '@backstage/errors';
|
||||
import { DiscoveryService, LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SignalService } from '@backstage/plugin-signals-node';
|
||||
import { Notification } from '@backstage/plugin-notifications-common';
|
||||
|
||||
/** @public */
|
||||
export interface RouterOptions {
|
||||
@@ -145,6 +146,22 @@ export async function createRouter(
|
||||
return users;
|
||||
};
|
||||
|
||||
const decorateNotification = async (notification: Notification) => {
|
||||
let ret: Notification = notification;
|
||||
for (const processor of processors ?? []) {
|
||||
ret = processor.decorate ? await processor.decorate(ret) : ret;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
const processorSendNotification = async (notification: Notification) => {
|
||||
for (const processor of processors ?? []) {
|
||||
if (processor.send) {
|
||||
processor.send(notification);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const router = Router();
|
||||
router.use(express.json());
|
||||
|
||||
@@ -269,7 +286,7 @@ export async function createRouter(
|
||||
});
|
||||
|
||||
router.post('/notifications', async (req, res) => {
|
||||
const { receivers, title, description, link } = req.body;
|
||||
const { receivers, title, description, link, topic } = req.body;
|
||||
const notifications = [];
|
||||
let users = [];
|
||||
|
||||
@@ -298,25 +315,40 @@ export async function createRouter(
|
||||
title,
|
||||
description,
|
||||
link,
|
||||
topic,
|
||||
created: new Date(),
|
||||
saved: false,
|
||||
};
|
||||
|
||||
for (const user of users) {
|
||||
let notification = { ...baseNotification, id: uuid(), userRef: user };
|
||||
for (const processor of processors ?? []) {
|
||||
notification = processor.decorate
|
||||
? await processor.decorate(notification)
|
||||
: notification;
|
||||
const userNotification = {
|
||||
...baseNotification,
|
||||
id: uuid(),
|
||||
userRef: user,
|
||||
};
|
||||
const notification = await decorateNotification(userNotification);
|
||||
|
||||
let existingNotification;
|
||||
if (topic) {
|
||||
existingNotification = await store.getExistingTopicNotification({
|
||||
user_ref: user,
|
||||
topic,
|
||||
});
|
||||
}
|
||||
|
||||
await store.saveNotification(notification);
|
||||
for (const processor of processors ?? []) {
|
||||
if (processor.send) {
|
||||
processor.send(notification);
|
||||
}
|
||||
let ret = notification;
|
||||
if (existingNotification) {
|
||||
const restored = await store.restoreExistingNotification({
|
||||
id: existingNotification.id,
|
||||
notification,
|
||||
});
|
||||
ret = restored ?? notification;
|
||||
} else {
|
||||
await store.saveNotification(notification);
|
||||
}
|
||||
notifications.push(notification);
|
||||
|
||||
processorSendNotification(ret);
|
||||
notifications.push(ret);
|
||||
}
|
||||
|
||||
if (signalService) {
|
||||
|
||||
@@ -10,8 +10,11 @@ type Notification_2 = {
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
topic?: string;
|
||||
created: Date;
|
||||
updated?: Date;
|
||||
read?: Date;
|
||||
done?: Date;
|
||||
saved: boolean;
|
||||
};
|
||||
export { Notification_2 as Notification };
|
||||
|
||||
@@ -24,8 +24,11 @@ export type Notification = {
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
topic?: string;
|
||||
created: Date;
|
||||
updated?: Date;
|
||||
read?: Date;
|
||||
done?: Date;
|
||||
saved: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -53,3 +53,6 @@ save the notification and optionally signal the frontend to show the latest stat
|
||||
When sending notifications, you can specify the entity reference of the notification. If the entity reference is
|
||||
a user, the notification will be sent to only that user. If it's a group, the notification will be sent to all
|
||||
members of the group. If it's some other entity, the notification will be sent to the owner of that entity.
|
||||
|
||||
If the notification has `topic` set and user already has notification with that topic, the existing notification
|
||||
will be updated with the new notification values and moved to inbox as unread.
|
||||
|
||||
@@ -38,6 +38,7 @@ export type NotificationSendOptions = {
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
topic?: string;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
|
||||
@@ -38,6 +38,7 @@ export type NotificationSendOptions = {
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
topic?: string;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
|
||||
Reference in New Issue
Block a user