chore: Refactor backlinks and revisions (#1611)

* Update backlinks service to not rely on revisions

* fix: Add missing index for finding backlinks

* Debounce revision creation (#1616)

* refactor debounce logic to service

* Debounce slack notification

* Revisions created by service

* fix: Revision sidebar latest

* test: Add tests for notifications
This commit is contained in:
Tom Moor
2020-11-01 10:26:39 -08:00
committed by GitHub
parent 7735aa12d7
commit 3d09c8f655
20 changed files with 487 additions and 246 deletions

View File

@@ -1,8 +1,9 @@
// @flow
import * as Sentry from "@sentry/node";
import debug from "debug";
import type { DocumentEvent, CollectionEvent, Event } from "../events";
import mailer from "../mailer";
import {
View,
Document,
Team,
Collection,
@@ -10,21 +11,25 @@ import {
NotificationSetting,
} from "../models";
import { Op } from "../sequelize";
import { createQueue } from "../utils/queue";
const notificationsQueue = createQueue("notifications");
const log = debug("services");
notificationsQueue.process(async (job) => {
const event = job.data;
export default class Notifications {
async on(event: Event) {
switch (event.name) {
case "documents.publish":
case "documents.update.debounced":
return this.documentUpdated(event);
case "collections.create":
return this.collectionCreated(event);
default:
}
}
try {
async documentUpdated(event: DocumentEvent) {
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;
@@ -37,7 +42,10 @@ notificationsQueue.process(async (job) => {
[Op.ne]: document.lastModifiedById,
},
teamId: document.teamId,
event: event.name,
event:
event.name === "documents.publish"
? "documents.publish"
: "documents.update",
},
include: [
{
@@ -51,17 +59,36 @@ notificationsQueue.process(async (job) => {
const eventName =
event.name === "documents.publish" ? "published" : "updated";
notificationSettings.forEach((setting) => {
for (const setting of notificationSettings) {
// For document updates we only want to send notifications if
// the document has been edited by the user with this notification setting
// This could be replaced with ability to "follow" in the future
if (
event.name === "documents.update" &&
eventName === "updated" &&
!document.collaboratorIds.includes(setting.userId)
) {
return;
}
// If this user has viewed the document since the last update was made
// then we can avoid sending them a useless notification, yay.
const view = await View.findOne({
where: {
userId: setting.userId,
documentId: event.documentId,
updatedAt: {
[Op.gt]: document.updatedAt,
},
},
});
if (view) {
log(
`suppressing notification to ${setting.userId} because update viewed`
);
return;
}
mailer.documentNotification({
to: setting.user.email,
eventName,
@@ -71,37 +98,8 @@ notificationsQueue.process(async (job) => {
actor: document.updatedBy,
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) {
const collection = await Collection.findByPk(event.collectionId, {