0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Added Mentions email notification settings (#16245)

closes https://github.com/TryGhost/Team/issues/2526

- Mention emails can now be toggled inside staff user' profiles, if they
have the webmention flag enabled on their Ghost site.
- Removed the flag dedicated to webmention email notifications and is
now handled by the `webmention` flag.
- Does not send email notification if `webmention` flag is not enabled.
- Updated tests.

---------

Co-authored-by: Fabien "egg" O'Carroll <fabien@allou.is>
This commit is contained in:
Ronald Langeveld 2023-02-09 17:03:03 +08:00 committed by GitHub
parent ea2c69565f
commit ce567b9816
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 60 additions and 27 deletions

View file

@ -211,6 +211,11 @@ export default class UserController extends Controller {
this.user.commentNotifications = event.target.checked; this.user.commentNotifications = event.target.checked;
} }
@action
toggleMentionNotifications(event) {
this.user.mentionNotifications = event.target.checked;
}
@action @action
toggleMemberEmailAlerts(type, event) { toggleMemberEmailAlerts(type, event) {
if (type === 'free-signup') { if (type === 'free-signup') {

View file

@ -40,7 +40,7 @@ export default BaseModel.extend(ValidationEngine, {
freeMemberSignupNotification: attr(), freeMemberSignupNotification: attr(),
paidSubscriptionStartedNotification: attr(), paidSubscriptionStartedNotification: attr(),
paidSubscriptionCanceledNotification: attr(), paidSubscriptionCanceledNotification: attr(),
mentionNotifications: attr(),
ghostPaths: service(), ghostPaths: service(),
ajax: service(), ajax: service(),
session: service(), session: service(),

View file

@ -69,7 +69,6 @@ export default class FeatureService extends Service {
@feature('emailStability') emailStability; @feature('emailStability') emailStability;
@feature('webmentions') webmentions; @feature('webmentions') webmentions;
@feature('outboundLinkTagging') outboundLinkTagging; @feature('outboundLinkTagging') outboundLinkTagging;
@feature('webmentionEmail') webmentionEmail;
@feature('emailErrors') emailErrors; @feature('emailErrors') emailErrors;
@feature('milestoneEmails') milestoneEmails; @feature('milestoneEmails') milestoneEmails;

View file

@ -213,19 +213,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="gh-expandable-block">
<div class="gh-expandable-header">
<div>
<h4 class="gh-expandable-title">Webmention Email Notifications</h4>
<p class="gh-expandable-description">
Receive email notifications when a webmention is received.
</p>
</div>
<div class="for-switch">
<GhFeatureFlag @flag="webmentionEmail" />
</div>
</div>
</div>
<div class="gh-expandable-block"> <div class="gh-expandable-block">
<div class="gh-expandable-header"> <div class="gh-expandable-header">
<div> <div>

View file

@ -327,6 +327,27 @@
{{/if}} {{/if}}
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#if (feature 'webmentions')}}
<div class="user-setting-toggle">
<div>
<label for="user-email">New Mentions</label>
<p>When you receive a new webmention</p>
</div>
<div class="for-switch small">
<label class="switch" for="mention-notifications" data-test-label="mention-notifications">
<input
id="mention-notifications"
type="checkbox"
checked={{this.user.mentionNotifications}}
class="gh-input"
{{on "change" this.toggleMentionNotifications}}
data-test-checkbox="mention-notifications"
>
<span class="input-toggle-component"></span>
</label>
</div>
</div>
{{/if}}
</GhFormGroup> </GhFormGroup>
</div> </div>
{{/if}} {{/if}}

View file

@ -19,7 +19,6 @@ export default Factory.extend({
updatedAt: '2015-11-02T16:12:05.000Z', updatedAt: '2015-11-02T16:12:05.000Z',
updatedBy: '1', updatedBy: '1',
website: 'http://example.com', website: 'http://example.com',
posts() { return []; }, posts() { return []; },
roles() { return []; } roles() { return []; }
}); });

View file

@ -14,6 +14,7 @@ import {
focus, focus,
triggerEvent triggerEvent
} from '@ember/test-helpers'; } from '@ember/test-helpers';
import {enableLabsFlag} from '../helpers/labs-flag';
import {enableMembers} from '../helpers/members'; import {enableMembers} from '../helpers/members';
import {enableStripe} from '../helpers/stripe'; import {enableStripe} from '../helpers/stripe';
import {expect} from 'chai'; import {expect} from 'chai';
@ -79,6 +80,7 @@ describe('Acceptance: Staff', function () {
adminRole = this.server.schema.roles.find(1); adminRole = this.server.schema.roles.find(1);
enableMembers(this.server); enableMembers(this.server);
enableStripe(this.server); enableStripe(this.server);
enableLabsFlag(this.server, 'webmentions');
admin = this.server.create('user', {email: 'admin@example.com', roles: [adminRole]}); admin = this.server.create('user', {email: 'admin@example.com', roles: [adminRole]});
@ -869,17 +871,20 @@ describe('Acceptance: Staff', function () {
expect(find('[data-test-checkbox="free-signup-notifications"]')).to.not.be.checked; expect(find('[data-test-checkbox="free-signup-notifications"]')).to.not.be.checked;
expect(find('[data-test-checkbox="paid-started-notifications"]')).to.not.be.checked; expect(find('[data-test-checkbox="paid-started-notifications"]')).to.not.be.checked;
expect(find('[data-test-checkbox="paid-canceled-notifications"]')).to.not.be.checked; expect(find('[data-test-checkbox="paid-canceled-notifications"]')).to.not.be.checked;
expect(find('[data-test-checkbox="mention-notifications"]')).to.not.be.checked;
await click('[data-test-label="free-signup-notifications"]'); await click('[data-test-label="free-signup-notifications"]');
await click('[data-test-label="paid-started-notifications"]'); await click('[data-test-label="paid-started-notifications"]');
await click('[data-test-label="paid-canceled-notifications"]'); await click('[data-test-label="paid-canceled-notifications"]');
await click('[data-test-label="mention-notifications"]');
await click('[data-test-save-button]'); await click('[data-test-save-button]');
await visit(`/settings/staff/${admin.slug}`); await visit(`/settings/staff/${admin.slug}`);
expect(find('[data-test-checkbox="free-signup-notifications"]')).to.be.checked; expect(find('[data-test-checkbox="free-signup-notifications"]')).to.be.checked;
expect(find('[data-test-checkbox="paid-started-notifications"]')).to.be.checked; expect(find('[data-test-checkbox="paid-started-notifications"]')).to.be.checked;
expect(find('[data-test-checkbox="paid-canceled-notifications"]')).to.be.checked; expect(find('[data-test-checkbox="paid-canceled-notifications"]')).to.be.checked;
expect(find('[data-test-checkbox="mention-notifications"]')).to.be.checked;
}); });
}); });

View file

@ -505,6 +505,8 @@ User = ghostBookshelf.Model.extend({
filter += '+paid_subscription_started_notification:true'; filter += '+paid_subscription_started_notification:true';
} else if (type === 'paid-canceled') { } else if (type === 'paid-canceled') {
filter += '+paid_subscription_canceled_notification:true'; filter += '+paid_subscription_canceled_notification:true';
} else if (type === 'mention-received') {
filter += '+mention_notifications:true';
} }
const updatedOptions = _.merge({}, options, {filter, withRelated: ['roles']}); const updatedOptions = _.merge({}, options, {filter, withRelated: ['roles']});
return this.findAll(updatedOptions).then((users) => { return this.findAll(updatedOptions).then((users) => {

View file

@ -35,7 +35,6 @@ const ALPHA_FEATURES = [
'urlCache', 'urlCache',
'beforeAfterCard', 'beforeAfterCard',
'lexicalEditor', 'lexicalEditor',
'webmentionEmail',
'outboundLinkTagging', 'outboundLinkTagging',
'milestoneEmails' 'milestoneEmails'
]; ];

View file

@ -19,7 +19,7 @@ describe('Webmentions (receiving)', function () {
agent = await agentProvider.getWebmentionsAPIAgent(); agent = await agentProvider.getWebmentionsAPIAgent();
await fixtureManager.init('posts'); await fixtureManager.init('posts');
nock.disableNetConnect(); nock.disableNetConnect();
mockManager.mockLabsEnabled('webmentionEmail'); mockManager.mockLabsEnabled('webmentions');
}); });
after(function () { after(function () {
@ -136,7 +136,7 @@ describe('Webmentions (receiving)', function () {
await processWebmentionJob; await processWebmentionJob;
await DomainEvents.allSettled(); await DomainEvents.allSettled();
const users = await models.User.findAll(); const users = await models.User.getEmailAlertUsers('mention-received');
users.forEach(async (user) => { users.forEach(async (user) => {
await mockManager.assert.sentEmail({ await mockManager.assert.sentEmail({
subject: 'You\'ve been mentioned!', subject: 'You\'ve been mentioned!',
@ -147,7 +147,7 @@ describe('Webmentions (receiving)', function () {
}); });
it('does not send notification with flag disabled', async function () { it('does not send notification with flag disabled', async function () {
mockManager.mockLabsDisabled('webmentionEmail'); mockManager.mockLabsDisabled('webmentions');
const processWebmentionJob = jobsService.awaitCompletion('processWebmention'); const processWebmentionJob = jobsService.awaitCompletion('processWebmention');
const targetUrl = new URL('integrations/', urlUtils.getSiteUrl()); const targetUrl = new URL('integrations/', urlUtils.getSiteUrl());
const sourceUrl = new URL('http://testpage.com/external-article-123-email-test/'); const sourceUrl = new URL('http://testpage.com/external-article-123-email-test/');

View file

@ -143,9 +143,24 @@ class StaffServiceEmails {
} }
async notifyMentionReceived({mention}) { async notifyMentionReceived({mention}) {
const users = await this.models.User.findAll(); // sending to all staff users for now const users = await this.models.User.getEmailAlertUsers('mention-received');
let resource = null;
if (mention.resourceId) {
try {
const postModel = await this.models.Post.findOne({id: mention.resourceId.toString()});
if (postModel) {
resource = {
id: postModel.id,
name: postModel.get('title'),
type: 'post'
};
}
} catch (err) {
this.logging.error(err);
}
}
for (const user of users) { for (const user of users) {
const to = user.toJSON().email; const to = user.email;
const subject = `💌 New mention from: ${mention.sourceSiteTitle}`; const subject = `💌 New mention from: ${mention.sourceSiteTitle}`;
const templateData = { const templateData = {
@ -155,13 +170,14 @@ class StaffServiceEmails {
sourceSiteTitle: mention.sourceSiteTitle, sourceSiteTitle: mention.sourceSiteTitle,
sourceFavicon: mention.sourceFavicon, sourceFavicon: mention.sourceFavicon,
sourceAuthor: mention.sourceAuthor, sourceAuthor: mention.sourceAuthor,
resource,
siteTitle: this.settingsCache.get('title'), siteTitle: this.settingsCache.get('title'),
siteUrl: this.urlUtils.getSiteUrl(), siteUrl: this.urlUtils.getSiteUrl(),
siteDomain: this.siteDomain, siteDomain: this.siteDomain,
accentColor: this.settingsCache.get('accent_color'), accentColor: this.settingsCache.get('accent_color'),
fromEmail: this.fromEmailAddress, fromEmail: this.fromEmailAddress,
toEmail: to, toEmail: to,
staffUrl: this.urlUtils.urlJoin(this.urlUtils.urlFor('admin', true), '#', `/settings/staff/${user.toJSON().slug}`) staffUrl: this.urlUtils.urlJoin(this.urlUtils.urlFor('admin', true), '#', `/settings/staff/${user.slug}`)
}; };
const {html, text} = await this.renderEmailTemplate('new-mention-received', templateData); const {html, text} = await this.renderEmailTemplate('new-mention-received', templateData);

View file

@ -77,7 +77,7 @@ class StaffService {
/** @private */ /** @private */
async handleEvent(type, event) { async handleEvent(type, event) {
if (type === MentionCreatedEvent && event.data.mention && this.labs.isSet('webmentionEmail')) { if (type === MentionCreatedEvent && event.data.mention && this.labs.isSet('webmentions')) {
await this.emails.notifyMentionReceived(event.data); await this.emails.notifyMentionReceived(event.data);
} }
if (!['api', 'member'].includes(event.data.source)) { if (!['api', 'member'].includes(event.data.source)) {

View file

@ -269,7 +269,7 @@ describe('StaffService', function () {
urlUtils, urlUtils,
settingsHelpers, settingsHelpers,
labs: { labs: {
isSet: () => 'webmentionEmail' isSet: () => 'webmentions'
} }
}); });
}); });