mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-23 19:02:29 +03:00
Added MilestoneCreatedEvent
to staff service notifications (#16307)
refs https://www.notion.so/ghost/Marketing-Milestone-email-campaigns-1d2c9dee3cfa4029863edb16092ad5c4?pvs=4 - Added MilestoneCreatedEvent subscription to staff-service incl. first handling - Logs information for now instead of actually sending an email
This commit is contained in:
parent
fed2cb2675
commit
e0331bbfcf
@ -508,6 +508,8 @@ User = ghostBookshelf.Model.extend({
|
||||
filter += '+paid_subscription_canceled_notification:true';
|
||||
} else if (type === 'mention-received') {
|
||||
filter += '+mention_notifications:true';
|
||||
} else if (type === 'milestone-received') {
|
||||
filter += '+milestone_notifications:true';
|
||||
}
|
||||
const updatedOptions = _.merge({}, options, {filter, withRelated: ['roles']});
|
||||
return this.findAll(updatedOptions).then((users) => {
|
||||
|
@ -1,5 +1,5 @@
|
||||
const sinon = require('sinon');
|
||||
|
||||
const assert = require('assert');
|
||||
const staffService = require('../../../../../core/server/services/staff');
|
||||
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
@ -7,15 +7,18 @@ const {mockManager} = require('../../../../utils/e2e-framework');
|
||||
const models = require('../../../../../core/server/models');
|
||||
|
||||
const {SubscriptionCancelledEvent, MemberCreatedEvent, SubscriptionActivatedEvent} = require('@tryghost/member-events');
|
||||
const {MilestoneCreatedEvent} = require('@tryghost/milestones');
|
||||
|
||||
describe('Staff Service:', function () {
|
||||
let userModelStub;
|
||||
|
||||
before(function () {
|
||||
models.init();
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
mockManager.mockMail();
|
||||
sinon.stub(models.User, 'getEmailAlertUsers').resolves([{
|
||||
userModelStub = sinon.stub(models.User, 'getEmailAlertUsers').resolves([{
|
||||
email: 'owner@ghost.org',
|
||||
slug: 'ghost'
|
||||
}]);
|
||||
@ -226,4 +229,37 @@ describe('Staff Service:', function () {
|
||||
mockManager.assert.sentEmailCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('milestone created event:', function () {
|
||||
beforeEach(function () {
|
||||
mockManager.mockLabsEnabled('milestoneEmails');
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
sinon.restore();
|
||||
mockManager.restore();
|
||||
});
|
||||
|
||||
it('logs when milestone event is handled', async function () {
|
||||
await staffService.init();
|
||||
DomainEvents.dispatch(MilestoneCreatedEvent.create({
|
||||
milestone: {
|
||||
type: 'arr',
|
||||
currency: 'usd',
|
||||
name: 'arr-100-usd',
|
||||
value: 100,
|
||||
createdAt: new Date(),
|
||||
emailSentAt: new Date()
|
||||
},
|
||||
meta: {
|
||||
currentARR: 105
|
||||
}
|
||||
}));
|
||||
|
||||
// Wait for the dispatched events (because this happens async)
|
||||
await DomainEvents.allSettled();
|
||||
const [userCalls] = userModelStub.args[0];
|
||||
assert.equal(userCalls, ['milestone-received']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -194,6 +194,24 @@ class StaffServiceEmails {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} eventData
|
||||
* @param {object} eventData.milestone
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async notifyMilestoneReceived({milestone}) {
|
||||
const users = await this.models.User.getEmailAlertUsers('milestone-received');
|
||||
|
||||
// TODO: send email with correct templates
|
||||
for (const user of users) {
|
||||
const to = user.email;
|
||||
|
||||
this.logging.info(`Will send email to ${to} for ${milestone.type} / ${milestone.value} milestone.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
/** @private */
|
||||
@ -227,7 +245,7 @@ class StaffServiceEmails {
|
||||
/** @private */
|
||||
getFormattedAmount({amount = 0, currency}) {
|
||||
if (!currency) {
|
||||
return '';
|
||||
return amount > 0 ? Intl.NumberFormat().format(amount) : '';
|
||||
}
|
||||
|
||||
return Intl.NumberFormat('en', {
|
||||
|
@ -1,5 +1,6 @@
|
||||
const {MemberCreatedEvent, SubscriptionCancelledEvent, SubscriptionActivatedEvent} = require('@tryghost/member-events');
|
||||
const {MentionCreatedEvent} = require('@tryghost/webmentions');
|
||||
const {MilestoneCreatedEvent} = require('@tryghost/milestones');
|
||||
|
||||
// @NOTE: 'StaffService' is a vague name that does not describe what it's actually doing.
|
||||
// Possibly, "StaffNotificationService" or "StaffEventNotificationService" would be a more accurate name
|
||||
@ -80,6 +81,11 @@ class StaffService {
|
||||
if (type === MentionCreatedEvent && event.data.mention && this.labs.isSet('webmentions')) {
|
||||
await this.emails.notifyMentionReceived(event.data);
|
||||
}
|
||||
|
||||
if (type === MilestoneCreatedEvent && event.data.milestone && this.labs.isSet('milestoneEmails')) {
|
||||
await this.emails.notifyMilestoneReceived(event.data);
|
||||
}
|
||||
|
||||
if (!['api', 'member'].includes(event.data.source)) {
|
||||
return;
|
||||
}
|
||||
@ -146,6 +152,15 @@ class StaffService {
|
||||
this.logging.error(e, `Failed to notify webmention`);
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger email when a new milestone is reached
|
||||
this.DomainEvents.subscribe(MilestoneCreatedEvent, async (event) => {
|
||||
try {
|
||||
await this.handleEvent(MilestoneCreatedEvent, event);
|
||||
} catch (e) {
|
||||
this.logging.error(e, `Failed to notify milestone`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,10 @@
|
||||
const sinon = require('sinon');
|
||||
const {MemberCreatedEvent, SubscriptionCancelledEvent, SubscriptionActivatedEvent} = require('@tryghost/member-events');
|
||||
const {MentionCreatedEvent} = require('@tryghost/webmentions');
|
||||
const {MilestoneCreatedEvent} = require('@tryghost/milestones');
|
||||
|
||||
require('./utils');
|
||||
const StaffService = require('../lib/staff-service');
|
||||
const StaffService = require('../index');
|
||||
|
||||
function testCommonMailData({mailStub, getEmailAlertUsersStub}) {
|
||||
getEmailAlertUsersStub.calledWith(
|
||||
@ -108,6 +109,7 @@ describe('StaffService', function () {
|
||||
|
||||
describe('email notifications:', function () {
|
||||
let mailStub;
|
||||
let loggingInfoStub;
|
||||
let subscribeStub;
|
||||
let getEmailAlertUsersStub;
|
||||
let service;
|
||||
@ -147,6 +149,7 @@ describe('StaffService', function () {
|
||||
};
|
||||
|
||||
beforeEach(function () {
|
||||
loggingInfoStub = sinon.stub().resolves();
|
||||
mailStub = sinon.stub().resolves();
|
||||
subscribeStub = sinon.stub().resolves();
|
||||
getEmailAlertUsersStub = sinon.stub().resolves([{
|
||||
@ -155,6 +158,7 @@ describe('StaffService', function () {
|
||||
}]);
|
||||
service = new StaffService({
|
||||
logging: {
|
||||
info: loggingInfoStub,
|
||||
warn: () => {},
|
||||
error: () => {}
|
||||
},
|
||||
@ -182,16 +186,19 @@ describe('StaffService', function () {
|
||||
describe('subscribeEvents', function () {
|
||||
it('subscribes to events', async function () {
|
||||
service.subscribeEvents();
|
||||
subscribeStub.callCount.should.eql(4);
|
||||
subscribeStub.callCount.should.eql(5);
|
||||
subscribeStub.calledWith(SubscriptionActivatedEvent).should.be.true();
|
||||
subscribeStub.calledWith(SubscriptionCancelledEvent).should.be.true();
|
||||
subscribeStub.calledWith(MemberCreatedEvent).should.be.true();
|
||||
subscribeStub.calledWith(MentionCreatedEvent).should.be.true();
|
||||
subscribeStub.calledWith(MilestoneCreatedEvent).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleEvent', function () {
|
||||
beforeEach(function () {
|
||||
loggingInfoStub = sinon.stub().resolves();
|
||||
|
||||
const models = {
|
||||
User: {
|
||||
getEmailAlertUsers: sinon.stub().resolves([{
|
||||
@ -255,6 +262,7 @@ describe('StaffService', function () {
|
||||
|
||||
service = new StaffService({
|
||||
logging: {
|
||||
info: loggingInfoStub,
|
||||
warn: () => {},
|
||||
error: () => {}
|
||||
},
|
||||
@ -269,7 +277,15 @@ describe('StaffService', function () {
|
||||
urlUtils,
|
||||
settingsHelpers,
|
||||
labs: {
|
||||
isSet: () => 'webmentions'
|
||||
isSet: (flag) => {
|
||||
if (flag === 'webmentions') {
|
||||
return true;
|
||||
}
|
||||
if (flag === 'milestoneEmails') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -331,6 +347,21 @@ describe('StaffService', function () {
|
||||
sinon.match({subject: `💌 New mention from: Exmaple`})
|
||||
).should.be.true();
|
||||
});
|
||||
|
||||
it('handles milestone created event', async function () {
|
||||
await service.handleEvent(MilestoneCreatedEvent, {
|
||||
data: {
|
||||
milestone: {
|
||||
type: 'arr',
|
||||
value: '100',
|
||||
currency: 'usd'
|
||||
}
|
||||
}
|
||||
});
|
||||
mailStub.called.should.be.false();
|
||||
loggingInfoStub.calledOnce.should.be.true();
|
||||
loggingInfoStub.calledWith('Will send email to owner@ghost.org for arr / 100 milestone.').should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('notifyFreeMemberSignup', function () {
|
||||
@ -635,5 +666,19 @@ describe('StaffService', function () {
|
||||
).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
describe('notifyMilestoneReceived', function () {
|
||||
it('prepares to send email when user setting available', async function () {
|
||||
const milestone = {
|
||||
type: 'members',
|
||||
value: 25000
|
||||
};
|
||||
|
||||
await service.emails.notifyMilestoneReceived({milestone});
|
||||
|
||||
mailStub.called.should.be.false();
|
||||
getEmailAlertUsersStub.calledWith('milestone-received').should.be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user