feat: Debounce notification emails by 5 minutes to avoid duplicate notifications where possible (#1598)

This commit is contained in:
Tom Moor
2020-10-25 15:06:07 -07:00
committed by GitHub
parent dba5dd14e7
commit bfdfa3ee4b
2 changed files with 53 additions and 24 deletions

View File

@ -2,7 +2,7 @@
import Router from "koa-router";
import auth from "../middlewares/authentication";
import { NotificationSetting } from "../models";
import { Team, NotificationSetting } from "../models";
import policy from "../policies";
import { presentNotificationSetting } from "../presenters";
@ -62,16 +62,23 @@ router.post("notificationSettings.unsubscribe", async (ctx) => {
ctx.assertUuid(id, "id is required");
ctx.assertPresent(token, "token is required");
const setting = await NotificationSetting.findByPk(id);
if (setting) {
if (token !== setting.unsubscribeToken) {
ctx.redirect(`${process.env.URL}?notice=invalid-auth`);
}
const setting = await NotificationSetting.findByPk(id, {
include: [
{
model: Team,
required: true,
as: "team",
},
],
});
if (setting && setting.unsubscribeToken === token) {
await setting.destroy();
ctx.redirect(`${setting.team.url}/settings/notifications?success`);
return;
}
ctx.redirect(`${process.env.URL}/settings/notifications?success`);
ctx.redirect(`${process.env.URL}?notice=invalid-auth`);
});
export default router;

View File

@ -1,4 +1,5 @@
// @flow
import * as Sentry from "@sentry/node";
import type { DocumentEvent, CollectionEvent, Event } from "../events";
import mailer from "../mailer";
import {
@ -9,29 +10,21 @@ import {
NotificationSetting,
} from "../models";
import { Op } from "../sequelize";
import { createQueue } from "../utils/queue";
export default class Notifications {
async on(event: Event) {
switch (event.name) {
case "documents.publish":
case "documents.update":
return this.documentUpdated(event);
case "collections.create":
return this.collectionCreated(event);
default:
}
}
const notificationsQueue = createQueue("notifications");
async documentUpdated(event: DocumentEvent) {
// lets not send a notification on every autosave update
if (event.data && event.data.autosave) return;
// wait until the user has finished editing
if (event.data && !event.data.done) return;
notificationsQueue.process(async (job) => {
const event = job.data;
try {
const document = await Document.findByPk(event.documentId);
if (!document) return;
// If the document has been updated since we initially queued a notification
// abort sending a notification this functions as a debounce.
if (document.updatedAt > new Date(event.createdAt)) return;
const { collection } = document;
if (!collection) return;
@ -79,6 +72,35 @@ export default class Notifications {
unsubscribeUrl: setting.unsubscribeUrl,
});
});
} catch (error) {
if (process.env.SENTRY_DSN) {
Sentry.withScope(function (scope) {
scope.setExtra("event", event);
Sentry.captureException(error);
});
} else {
throw error;
}
}
});
export default class Notifications {
async on(event: Event) {
switch (event.name) {
case "documents.publish":
case "documents.update":
return this.documentUpdated(event);
case "collections.create":
return this.collectionCreated(event);
default:
}
}
async documentUpdated(event: DocumentEvent) {
notificationsQueue.add(event, {
delay: 5 * 60 * 1000,
removeOnComplete: true,
});
}
async collectionCreated(event: CollectionEvent) {