Fixed email analytics require error

refs https://github.com/TryGhost/Ghost/pull/12541
refs https://github.com/TryGhost/Ghost/pull/12689

- the analytics job had been switched to create it's own instance of EmailAnalyticsService to avoid requiring logging but the analytics extraction branch was created before this change and wasn't picked up when merging
- pulled `queries` option object into a separate file for re-use
- updated `fetchLatest` job to conform to extracted library interface
This commit is contained in:
Kevin Ansfield 2021-03-02 08:22:11 +00:00
parent 11802ebee0
commit a06064b115
3 changed files with 71 additions and 60 deletions

View File

@ -1,4 +1,3 @@
const _ = require('lodash');
const config = require('../../../shared/config'); const config = require('../../../shared/config');
const logging = require('../../../shared/logging'); const logging = require('../../../shared/logging');
const db = require('../../data/db'); const db = require('../../data/db');
@ -6,9 +5,7 @@ const settings = require('../settings/cache');
const {EmailAnalyticsService} = require('@tryghost/email-analytics-service'); const {EmailAnalyticsService} = require('@tryghost/email-analytics-service');
const EventProcessor = require('./lib/event-processor'); const EventProcessor = require('./lib/event-processor');
const MailgunProvider = require('@tryghost/email-analytics-provider-mailgun'); const MailgunProvider = require('@tryghost/email-analytics-provider-mailgun');
const debug = require('ghost-ignition').debug('services:email-analytics'); const queries = require('../lib/queries');
const MIN_EMAIL_COUNT_FOR_OPEN_RATE = 5;
module.exports = new EmailAnalyticsService({ module.exports = new EmailAnalyticsService({
config, config,
@ -18,57 +15,5 @@ module.exports = new EmailAnalyticsService({
providers: [ providers: [
new MailgunProvider({config, settings, logging}) new MailgunProvider({config, settings, logging})
], ],
queries: { queries
async shouldFetchStats() {
// don't fetch stats from Mailgun if we haven't sent any emails
const [emailCount] = await db.knex('emails').count('id as count');
return emailCount && emailCount.count > 0;
},
async getLastSeenEventTimestamp() {
const startDate = new Date();
// three separate queries is much faster than using max/greatest (with coalesce to handle nulls) across columns
const {maxDeliveredAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first() || {};
const {maxOpenedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first() || {};
const {maxFailedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first() || {};
const lastSeenEventTimestamp = _.max([maxDeliveredAt, maxOpenedAt, maxFailedAt]);
debug(`getLastSeenEventTimestamp: finished in ${Date.now() - startDate}ms`);
return lastSeenEventTimestamp;
},
async aggregateEmailStats(emailId) {
await db.knex('emails').update({
delivered_count: db.knex.raw(`(SELECT COUNT(id) FROM email_recipients WHERE email_id = ? AND delivered_at IS NOT NULL)`, [emailId]),
opened_count: db.knex.raw(`(SELECT COUNT(id) FROM email_recipients WHERE email_id = ? AND opened_at IS NOT NULL)`, [emailId]),
failed_count: db.knex.raw(`(SELECT COUNT(id) FROM email_recipients WHERE email_id = ? AND failed_at IS NOT NULL)`, [emailId])
}).where('id', emailId);
},
async aggregateMemberStats(memberId) {
const {trackedEmailCount} = await db.knex('email_recipients')
.select(db.knex.raw('COUNT(email_recipients.id) as trackedEmailCount'))
.leftJoin('emails', 'email_recipients.email_id', 'emails.id')
.where('email_recipients.member_id', memberId)
.where('emails.track_opens', true)
.first() || {};
const updateQuery = {
email_count: db.knex.raw('(SELECT COUNT(id) FROM email_recipients WHERE member_id = ?)', [memberId]),
email_opened_count: db.knex.raw('(SELECT COUNT(id) FROM email_recipients WHERE member_id = ? AND opened_at IS NOT NULL)', [memberId])
};
if (trackedEmailCount >= MIN_EMAIL_COUNT_FOR_OPEN_RATE) {
updateQuery.email_open_rate = db.knex.raw(`
ROUND(((SELECT COUNT(id) FROM email_recipients WHERE member_id = ? AND opened_at IS NOT NULL) * 1.0 / ? * 100), 0)
`, [memberId, trackedEmailCount]);
}
await db.knex('members')
.update(updateQuery)
.where('id', memberId);
}
}
}); });

View File

@ -56,13 +56,20 @@ if (parentPort) {
} }
}; };
const EmailAnalyticsService = require('../email-analytics'); const {EmailAnalyticsService} = require('@tryghost/email-analytics-service');
const EventProcessor = require('../lib/event-processor');
const MailgunProvider = require('@tryghost/email-analytics-provider-mailgun');
const queries = require('../lib/queries');
const emailAnalyticsService = new EmailAnalyticsService({ const emailAnalyticsService = new EmailAnalyticsService({
config, config,
db,
settings, settings,
logging logging,
eventProcessor: new EventProcessor({db, logging}),
providers: [
new MailgunProvider({config, settings, logging})
],
queries
}); });
const fetchStartDate = new Date(); const fetchStartDate = new Date();

View File

@ -0,0 +1,59 @@
const _ = require('lodash');
const debug = require('ghost-ignition').debug('services:email-analytics');
const db = require('../../../data/db');
const MIN_EMAIL_COUNT_FOR_OPEN_RATE = 5;
module.exports = {
async shouldFetchStats() {
// don't fetch stats from Mailgun if we haven't sent any emails
const [emailCount] = await db.knex('emails').count('id as count');
return emailCount && emailCount.count > 0;
},
async getLastSeenEventTimestamp() {
const startDate = new Date();
// three separate queries is much faster than using max/greatest (with coalesce to handle nulls) across columns
const {maxDeliveredAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first() || {};
const {maxOpenedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first() || {};
const {maxFailedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first() || {};
const lastSeenEventTimestamp = _.max([maxDeliveredAt, maxOpenedAt, maxFailedAt]);
debug(`getLastSeenEventTimestamp: finished in ${Date.now() - startDate}ms`);
return lastSeenEventTimestamp;
},
async aggregateEmailStats(emailId) {
await db.knex('emails').update({
delivered_count: db.knex.raw(`(SELECT COUNT(id) FROM email_recipients WHERE email_id = ? AND delivered_at IS NOT NULL)`, [emailId]),
opened_count: db.knex.raw(`(SELECT COUNT(id) FROM email_recipients WHERE email_id = ? AND opened_at IS NOT NULL)`, [emailId]),
failed_count: db.knex.raw(`(SELECT COUNT(id) FROM email_recipients WHERE email_id = ? AND failed_at IS NOT NULL)`, [emailId])
}).where('id', emailId);
},
async aggregateMemberStats(memberId) {
const {trackedEmailCount} = await db.knex('email_recipients')
.select(db.knex.raw('COUNT(email_recipients.id) as trackedEmailCount'))
.leftJoin('emails', 'email_recipients.email_id', 'emails.id')
.where('email_recipients.member_id', memberId)
.where('emails.track_opens', true)
.first() || {};
const updateQuery = {
email_count: db.knex.raw('(SELECT COUNT(id) FROM email_recipients WHERE member_id = ?)', [memberId]),
email_opened_count: db.knex.raw('(SELECT COUNT(id) FROM email_recipients WHERE member_id = ? AND opened_at IS NOT NULL)', [memberId])
};
if (trackedEmailCount >= MIN_EMAIL_COUNT_FOR_OPEN_RATE) {
updateQuery.email_open_rate = db.knex.raw(`
ROUND(((SELECT COUNT(id) FROM email_recipients WHERE member_id = ? AND opened_at IS NOT NULL) * 1.0 / ? * 100), 0)
`, [memberId, trackedEmailCount]);
}
await db.knex('members')
.update(updateQuery)
.where('id', memberId);
}
};