Add optional export notifications (#3935)
* Add `emails.export_completed` notification to settings menu Signed-off-by: AKP <tom@tdpain.net> * Don't send email when export_completed notifications are disabled Signed-off-by: AKP <tom@tdpain.net> * Automatically subscribe new users to `export_completed` notifications Signed-off-by: AKP <tom@tdpain.net> * Alter secondary text on export page to mention optional notifications Signed-off-by: AKP <tom@tdpain.net> * Alter toast text on collection export for optional notifications Signed-off-by: AKP <tom@tdpain.net> * Only subscribe new admins to export notifs Signed-off-by: AKP <tom@tdpain.net> * Move `export_completed` notification decision into `beforeSend` Signed-off-by: AKP <tom@tdpain.net> * Update server/emails/templates/ExportFailureEmail.tsx Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update server/emails/templates/ExportSuccessEmail.tsx Co-authored-by: Tom Moor <tom.moor@gmail.com> Signed-off-by: AKP <tom@tdpain.net> Co-authored-by: Tom Moor <tom.moor@gmail.com>
This commit is contained in:
@@ -25,7 +25,9 @@ function CollectionExport({ collection, onSubmit }: Props) {
|
|||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
showToast(
|
showToast(
|
||||||
t("Export started, you will receive an email when it’s complete.")
|
t(
|
||||||
|
"Export started. If you have notifications enabled, you will receive an email when it's complete."
|
||||||
|
)
|
||||||
);
|
);
|
||||||
onSubmit();
|
onSubmit();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ function Export() {
|
|||||||
<Heading>{t("Export")}</Heading>
|
<Heading>{t("Export")}</Heading>
|
||||||
<Text type="secondary">
|
<Text type="secondary">
|
||||||
<Trans
|
<Trans
|
||||||
defaults="A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – we will email a link to <em>{{ userEmail }}</em> when it’s complete."
|
defaults="A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – if you have notifications enabled, we will email a link to <em>{{ userEmail }}</em> when it’s complete."
|
||||||
values={{
|
values={{
|
||||||
userEmail: user.email,
|
userEmail: user.email,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -51,6 +51,13 @@ function Notifications() {
|
|||||||
"Receive a notification when someone you invited creates an account"
|
"Receive a notification when someone you invited creates an account"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
event: "emails.export_completed",
|
||||||
|
title: t("Export completed"),
|
||||||
|
description: t(
|
||||||
|
"Receive a notification when an export you requested has been completed"
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
visible: isCloudHosted,
|
visible: isCloudHosted,
|
||||||
event: "emails.onboarding",
|
event: "emails.onboarding",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { NotificationSetting } from "@server/models";
|
||||||
import BaseEmail from "./BaseEmail";
|
import BaseEmail from "./BaseEmail";
|
||||||
import Body from "./components/Body";
|
import Body from "./components/Body";
|
||||||
import Button from "./components/Button";
|
import Button from "./components/Button";
|
||||||
@@ -10,13 +11,27 @@ import Heading from "./components/Heading";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
to: string;
|
to: string;
|
||||||
|
userId: string;
|
||||||
teamUrl: string;
|
teamUrl: string;
|
||||||
|
teamId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email sent to a user when their data export has failed for some reason.
|
* Email sent to a user when their data export has failed for some reason.
|
||||||
*/
|
*/
|
||||||
export default class ExportFailureEmail extends BaseEmail<Props> {
|
export default class ExportFailureEmail extends BaseEmail<Props> {
|
||||||
|
protected async beforeSend({ userId, teamId }: Props) {
|
||||||
|
const notificationSetting = await NotificationSetting.findOne({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
event: "emails.export_completed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return notificationSetting !== null;
|
||||||
|
}
|
||||||
|
|
||||||
protected subject() {
|
protected subject() {
|
||||||
return "Your requested export";
|
return "Your requested export";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { NotificationSetting } from "@server/models";
|
||||||
import BaseEmail from "./BaseEmail";
|
import BaseEmail from "./BaseEmail";
|
||||||
import Body from "./components/Body";
|
import Body from "./components/Body";
|
||||||
import Button from "./components/Button";
|
import Button from "./components/Button";
|
||||||
@@ -10,8 +11,10 @@ import Heading from "./components/Heading";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
to: string;
|
to: string;
|
||||||
|
userId: string;
|
||||||
id: string;
|
id: string;
|
||||||
teamUrl: string;
|
teamUrl: string;
|
||||||
|
teamId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,6 +22,18 @@ type Props = {
|
|||||||
* for download in the settings section.
|
* for download in the settings section.
|
||||||
*/
|
*/
|
||||||
export default class ExportSuccessEmail extends BaseEmail<Props> {
|
export default class ExportSuccessEmail extends BaseEmail<Props> {
|
||||||
|
protected async beforeSend({ userId, teamId }: Props) {
|
||||||
|
const notificationSetting = await NotificationSetting.findOne({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
teamId,
|
||||||
|
event: "emails.export_completed",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return notificationSetting !== null;
|
||||||
|
}
|
||||||
|
|
||||||
protected subject() {
|
protected subject() {
|
||||||
return "Your requested export";
|
return "Your requested export";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class NotificationSetting extends Model {
|
|||||||
"emails.invite_accepted",
|
"emails.invite_accepted",
|
||||||
"emails.onboarding",
|
"emails.onboarding",
|
||||||
"emails.features",
|
"emails.features",
|
||||||
|
"emails.export_completed",
|
||||||
],
|
],
|
||||||
])
|
])
|
||||||
@Column(DataType.STRING)
|
@Column(DataType.STRING)
|
||||||
|
|||||||
@@ -497,7 +497,9 @@ class User extends ParanoidModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// By default when a user signs up we subscribe them to email notifications
|
// By default when a user signs up we subscribe them to email notifications
|
||||||
// when documents they created are edited by other team members and onboarding
|
// when documents they created are edited by other team members and onboarding.
|
||||||
|
// If the user is an admin, they will also be subscribed to export_completed
|
||||||
|
// notifications.
|
||||||
@AfterCreate
|
@AfterCreate
|
||||||
static subscribeToNotifications = async (
|
static subscribeToNotifications = async (
|
||||||
model: User,
|
model: User,
|
||||||
@@ -537,6 +539,17 @@ class User extends ParanoidModel {
|
|||||||
transaction: options.transaction,
|
transaction: options.transaction,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (model.isAdmin) {
|
||||||
|
await NotificationSetting.findOrCreate({
|
||||||
|
where: {
|
||||||
|
userId: model.id,
|
||||||
|
teamId: model.teamId,
|
||||||
|
event: "emails.export_completed",
|
||||||
|
},
|
||||||
|
transaction: options.transaction,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static getCounts = async function (teamId: string) {
|
static getCounts = async function (teamId: string) {
|
||||||
|
|||||||
@@ -72,8 +72,10 @@ export default class ExportMarkdownZipTask extends BaseTask<Props> {
|
|||||||
|
|
||||||
await ExportSuccessEmail.schedule({
|
await ExportSuccessEmail.schedule({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
|
userId: user.id,
|
||||||
id: fileOperation.id,
|
id: fileOperation.id,
|
||||||
teamUrl: team.url,
|
teamUrl: team.url,
|
||||||
|
teamId: team.id,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.updateFileOperation(fileOperation, {
|
await this.updateFileOperation(fileOperation, {
|
||||||
@@ -82,7 +84,9 @@ export default class ExportMarkdownZipTask extends BaseTask<Props> {
|
|||||||
});
|
});
|
||||||
await ExportFailureEmail.schedule({
|
await ExportFailureEmail.schedule({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
|
userId: user.id,
|
||||||
teamUrl: team.url,
|
teamUrl: team.url,
|
||||||
|
teamId: team.id,
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,7 +343,7 @@
|
|||||||
"Sort": "Sort",
|
"Sort": "Sort",
|
||||||
"Saving": "Saving",
|
"Saving": "Saving",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"Export started, you will receive an email when it’s complete.": "Export started, you will receive an email when it’s complete.",
|
"Export started. If you have notifications enabled, you will receive an email when it's complete.": "Export started. If you have notifications enabled, you will receive an email when it's complete.",
|
||||||
"Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be a zip of folders with files in Markdown format. Please visit the Export section on settings to get the zip.": "Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be a zip of folders with files in Markdown format. Please visit the Export section on settings to get the zip.",
|
"Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be a zip of folders with files in Markdown format. Please visit the Export section on settings to get the zip.": "Exporting the collection <em>{{collectionName}}</em> may take a few seconds. Your documents will be a zip of folders with files in Markdown format. Please visit the Export section on settings to get the zip.",
|
||||||
"Exporting": "Exporting",
|
"Exporting": "Exporting",
|
||||||
"Export Collection": "Export Collection",
|
"Export Collection": "Export Collection",
|
||||||
@@ -620,7 +620,7 @@
|
|||||||
"This is the screen that team members will first see when they sign in.": "This is the screen that team members will first see when they sign in.",
|
"This is the screen that team members will first see when they sign in.": "This is the screen that team members will first see when they sign in.",
|
||||||
"Export in progress…": "Export in progress…",
|
"Export in progress…": "Export in progress…",
|
||||||
"Export deleted": "Export deleted",
|
"Export deleted": "Export deleted",
|
||||||
"A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – we will email a link to <em>{{ userEmail }}</em> when it’s complete.": "A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – we will email a link to <em>{{ userEmail }}</em> when it’s complete.",
|
"A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – if you have notifications enabled, we will email a link to <em>{{ userEmail }}</em> when it’s complete.": "A full export might take some time, consider exporting a single document or collection. The exported data is a zip of your documents in Markdown format. You may leave this page once the export has started – if you have notifications enabled, we will email a link to <em>{{ userEmail }}</em> when it’s complete.",
|
||||||
"Export Requested": "Export Requested",
|
"Export Requested": "Export Requested",
|
||||||
"Requesting Export": "Requesting Export",
|
"Requesting Export": "Requesting Export",
|
||||||
"Export Data": "Export Data",
|
"Export Data": "Export Data",
|
||||||
@@ -650,6 +650,8 @@
|
|||||||
"Receive a notification whenever a new collection is created": "Receive a notification whenever a new collection is created",
|
"Receive a notification whenever a new collection is created": "Receive a notification whenever a new collection is created",
|
||||||
"Invite accepted": "Invite accepted",
|
"Invite accepted": "Invite accepted",
|
||||||
"Receive a notification when someone you invited creates an account": "Receive a notification when someone you invited creates an account",
|
"Receive a notification when someone you invited creates an account": "Receive a notification when someone you invited creates an account",
|
||||||
|
"Export completed": "Export completed",
|
||||||
|
"Receive a notification when an export you requested has been completed": "Receive a notification when an export you requested has been completed",
|
||||||
"Getting started": "Getting started",
|
"Getting started": "Getting started",
|
||||||
"Tips on getting started with Outline’s features and functionality": "Tips on getting started with Outline’s features and functionality",
|
"Tips on getting started with Outline’s features and functionality": "Tips on getting started with Outline’s features and functionality",
|
||||||
"New features": "New features",
|
"New features": "New features",
|
||||||
|
|||||||
Reference in New Issue
Block a user