diff --git a/ghost/admin/app/controllers/dashboard.js b/ghost/admin/app/controllers/dashboard.js index 3246fb2e74..dca7821f2e 100644 --- a/ghost/admin/app/controllers/dashboard.js +++ b/ghost/admin/app/controllers/dashboard.js @@ -38,6 +38,13 @@ export default class DashboardController extends Controller { @tracked topMembersLoading = false; + @tracked + newsletterOpenRatesData = null; + @tracked + newsletterOpenRatesError = null; + @tracked + newsletterOpenRatesLoading = false; + get showTopMembers() { return this.feature.get('emailAnalytics') && this.settings.get('emailTrackOpens'); } @@ -130,6 +137,7 @@ export default class DashboardController extends Controller { loadCharts() { this.loadMRRStats(); this.loadMemberCountStats(); + this.loadNewsletterOpenRates(); } loadEvents() { @@ -143,6 +151,28 @@ export default class DashboardController extends Controller { }); } + loadNewsletterOpenRates() { + this.newsletterOpenRatesLoading = true; + this.membersStats.fetchNewsletterStats().then((results) => { + this.newsletterOpenRatesData = { + options: { + rangeInDays: 30 + }, + data: { + label: 'Open Rate', + dateLabels: results.map(d => d.submittedAt), + dateValues: results.map(d => d.openRate) + }, + title: 'Open Rate', + stats: results + }; + this.newsletterOpenRatesLoading = false; + }, (error) => { + this.newsletterOpenRatesError = error; + this.newsletterOpenRatesLoading = false; + }); + } + loadTopMembers() { this.topMembersLoading = true; let query = { diff --git a/ghost/admin/app/services/members-stats.js b/ghost/admin/app/services/members-stats.js index de8e2095f2..a254f983bc 100644 --- a/ghost/admin/app/services/members-stats.js +++ b/ghost/admin/app/services/members-stats.js @@ -7,12 +7,14 @@ import {tracked} from '@glimmer/tracking'; export default class MembersStatsService extends Service { @service ajax; @service ghostPaths; + @service store; @tracked days = '30'; @tracked stats = null; @tracked events = null; @tracked countStats = null; @tracked mrrStats = null; + @tracked newsletterStats = null; fetch() { let daysChanged = this._lastFetchedDays !== this.days; @@ -61,6 +63,22 @@ export default class MembersStatsService extends Service { return this._fetchCountsTask.perform(); } + fetchNewsletterStats() { + let staleData = this._lastFetchedNewsletterStats && this._lastFetchedNewsletterStats - new Date() > 1 * 60 * 1000; + + // return an already in-progress promise unless params have changed + if (this._fetchNewsletterStatsTask.isRunning) { + return this._fetchNewsletterStatsTask.last; + } + + // return existing stats unless data is > 1 min old + if (this.newsletterStats && !this._forceRefresh && !staleData) { + return Promise.resolve(this.countStats); + } + + return this._fetchNewsletterStatsTask.perform(); + } + fillDates(data) { let currentRangeDate = moment().subtract(30, 'days'); @@ -123,6 +141,27 @@ export default class MembersStatsService extends Service { this._forceRefresh = true; } + @task + *_fetchNewsletterStatsTask() { + let query = { + filter: 'email_count:-0', + order: 'submitted_at desc', + limit: 10 + }; + const results = yield this.store.query('email', query); + const stats = results.map((d) => { + const {emailCount, openedCount, subject, submittedAt} = d; + const openRate = (emailCount && emailCount !== 0) ? (openedCount / emailCount).toFixed(1) : 0; + return { + subject, + submittedAt: moment(submittedAt).format('YYYY-MM-DD'), + openRate + }; + }); + this.newsletterStats = stats; + return stats; + } + @task *_fetchCountsTask() { this._lastFetchedCounts = new Date(); diff --git a/ghost/admin/app/templates/dashboard.hbs b/ghost/admin/app/templates/dashboard.hbs index bc9162742d..57c96b893f 100644 --- a/ghost/admin/app/templates/dashboard.hbs +++ b/ghost/admin/app/templates/dashboard.hbs @@ -70,7 +70,9 @@