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

Added members.email_open_rate aggregation to email analytics (#12458)

refs https://github.com/TryGhost/Ghost/issues/12421
requires https://github.com/TryGhost/Ghost/pull/12457

- updates stats aggregator to calculate and store an open rate for each member
  - uses two queries because I couldn't find a reasonable approach to perform the update in a single query as per the email aggregation
  - benchmarked locally at <1sec/1000members
  - will not store an open rate unless the number of tracked emails sent to a member is above a certain threshold (defaults to 5) to avoid new members being heavily weighted
- fixes typo in EmailAnalytics that was stopping member stats from being aggregated
This commit is contained in:
Kevin Ansfield 2020-12-08 12:43:10 +00:00 committed by GitHub
parent 9fd6f30fd7
commit 567eb6325f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 22 additions and 6 deletions

View file

@ -81,7 +81,7 @@ class EmailAnalyticsService {
await this.aggregateEmailStats(emailId);
}
for (const memberId of memberIds) {
await this.aggregateEmailStats(memberId);
await this.aggregateMemberStats(memberId);
}
}

View file

@ -50,7 +50,7 @@ if (parentPort) {
const aggregateEndDate = new Date();
debug(`Finished aggregating email analytics in ${aggregateEndDate - aggregateStartDate}ms`);
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails in ${aggregateEndDate - fetchStartDate}ms`);
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails and ${eventStats.memberIds.length} members in ${aggregateEndDate - fetchStartDate}ms`);
if (parentPort) {
parentPort.postMessage('done');

View file

@ -51,7 +51,7 @@ if (parentPort) {
const aggregateEndDate = new Date();
debug(`Finished aggregating email analytics in ${aggregateEndDate - aggregateStartDate}ms`);
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails in ${aggregateEndDate - fetchStartDate}ms`);
logging.info(`Fetched ${eventStats.totalEvents} events and aggregated stats for ${eventStats.emailIds.length} emails and ${eventStats.memberIds.length} members in ${aggregateEndDate - fetchStartDate}ms`);
if (parentPort) {
parentPort.postMessage('done');

View file

@ -1,5 +1,6 @@
class EmailAnalyticsStatsAggregator {
constructor({logging, db}) {
constructor({options, logging, db}) {
this.options = Object.assign({openRateEmailThreshold: 5}, options);
this.logging = logging || console;
this.db = db;
}
@ -12,8 +13,23 @@ class EmailAnalyticsStatsAggregator {
}).where('id', emailId);
}
async aggregateMember(/*memberId*/) {
// TODO: decide on aggregation algorithm when only certain emails have open tracking
async aggregateMember(memberId) {
const {trackedEmailCount} = await this.db.knex('email_recipients')
.select(this.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() || {};
if (trackedEmailCount >= this.options.openRateEmailThreshold) {
await this.db.knex('members')
.update({
email_open_rate: this.db.knex.raw(`(
(SELECT COUNT(id) FROM email_recipients WHERE member_id = ? AND opened_at IS NOT NULL) * 1.0 / ? * 100)
`, [memberId, trackedEmailCount])
})
.where('id', memberId);
}
}
}