diff --git a/ghost/admin/app/components/gh-members-chart.js b/ghost/admin/app/components/gh-members-chart.js index be662c082e..7206f8d47e 100644 --- a/ghost/admin/app/components/gh-members-chart.js +++ b/ghost/admin/app/components/gh-members-chart.js @@ -7,7 +7,7 @@ import {task} from 'ember-concurrency'; export default Component.extend({ ajax: service(), - ghostPaths: service(), + membersStats: service(), // public attrs nightShift: false, @@ -63,8 +63,7 @@ export default Component.extend({ // Tasks ------------------------------------------------------------------- fetchStatsTask: task(function* () { - let statsUrl = this.ghostPaths.url.api('members/stats'); - let stats = yield this.ajax.request(statsUrl, {data: {days: this.selectedRange.days}}); + let stats = yield this.membersStats.fetch({days: this.selectedRange.days}); this.set('stats', stats); diff --git a/ghost/admin/app/components/modal-delete-member.js b/ghost/admin/app/components/modal-delete-member.js index f188863e3a..6b0172a797 100644 --- a/ghost/admin/app/components/modal-delete-member.js +++ b/ghost/admin/app/components/modal-delete-member.js @@ -1,8 +1,11 @@ import ModalComponent from 'ghost-admin/components/modal-base'; import {alias} from '@ember/object/computed'; +import {inject as service} from '@ember/service'; import {task} from 'ember-concurrency'; export default ModalComponent.extend({ + membersStats: service(), + // Allowed actions confirm: () => {}, @@ -17,6 +20,7 @@ export default ModalComponent.extend({ deleteMember: task(function* () { try { yield this.confirm(); + this.membersStats.invalidate(); } finally { this.send('closeModal'); } diff --git a/ghost/admin/app/components/modal-import-members.js b/ghost/admin/app/components/modal-import-members.js index 8da3a14b27..b4687a8a61 100644 --- a/ghost/admin/app/components/modal-import-members.js +++ b/ghost/admin/app/components/modal-import-members.js @@ -1,8 +1,11 @@ import ModalComponent from 'ghost-admin/components/modal-base'; import ghostPaths from 'ghost-admin/utils/ghost-paths'; import {computed} from '@ember/object'; +import {inject as service} from '@ember/service'; export default ModalComponent.extend({ + membersStats: service(), + labelText: 'Select or drag-and-drop a CSV File', response: null, @@ -26,6 +29,7 @@ export default ModalComponent.extend({ uploadSuccess(response) { this.set('response', response.meta.stats); + this.membersStats.invalidate(); // invoke the passed in confirm action this.confirm(); }, diff --git a/ghost/admin/app/controllers/member.js b/ghost/admin/app/controllers/member.js index fc4914ea71..6e921e7c68 100644 --- a/ghost/admin/app/controllers/member.js +++ b/ghost/admin/app/controllers/member.js @@ -13,6 +13,7 @@ export default class MemberController extends Controller { @controller members; @service session; @service dropdown; + @service membersStats; @service notifications; @service router; @service store; @@ -66,6 +67,7 @@ export default class MemberController extends Controller { @action deleteMember() { return this.member.destroyRecord().then(() => { + this.membersStats.invalidate(); return this.transitionToRoute('members'); }, (error) => { return this.notifications.showAPIError(error, {key: 'member.delete'}); @@ -114,6 +116,10 @@ export default class MemberController extends Controller { let scratchProps = scratchMember.getProperties(SCRATCH_PROPS); member.setProperties(scratchProps); + if (!member.isNew) { + this.membersStats.invalidate(); + } + try { yield member.save(); member.updateLabels(); diff --git a/ghost/admin/app/services/members-stats.js b/ghost/admin/app/services/members-stats.js new file mode 100644 index 0000000000..f925298b50 --- /dev/null +++ b/ghost/admin/app/services/members-stats.js @@ -0,0 +1,33 @@ +import Service from '@ember/service'; +import {inject as service} from '@ember/service'; +import {tracked} from '@glimmer/tracking'; + +export default class MembersStatsService extends Service { + @service ajax; + @service ghostPaths; + + @tracked stats = null; + + fetch({days}) { + // return existing stats unless data is > 1 min old + let daysChanged = days === this._days; + let staleData = this._lastFetched - new Date() > 1 * 60 * 1000; + if (!this._forceRefresh && !daysChanged && !staleData) { + return Promise.resolve(this.stats); + } + + this._days = days; + this._lastFetched = new Date(); + + let statsUrl = this.ghostPaths.url.api('members/stats'); + + return this.ajax.request(statsUrl, {data: {days}}).then((stats) => { + this.stats = stats; + return stats; + }); + } + + invalidate() { + this._forceRefresh = true; + } +}