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:
Heikki Hellgren
2024-01-26 14:44:30 +02:00
parent dced4bbc96
commit 50c7dbde2f
10 changed files with 124 additions and 15 deletions
@@ -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) {