From b5992de5eaad7815e242460af9d63c4fb7e45bb3 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 7 Mar 2022 12:33:51 +0000 Subject: [PATCH] Extracted dashboard members graphs block to a component refs https://github.com/TryGhost/Team/issues/1406 - separates concerns from the Dashboard controller so it's easier to refactor and change behaviour - pre-req for introducing optional display of the graphs based on wider system status in a clean way --- .../components/dashboard/members-graphs.hbs | 106 ++++++++++++ .../components/dashboard/members-graphs.js | 161 ++++++++++++++++++ ghost/admin/app/controllers/dashboard.js | 149 ---------------- ghost/admin/app/templates/dashboard.hbs | 107 +----------- 4 files changed, 268 insertions(+), 255 deletions(-) create mode 100644 ghost/admin/app/components/dashboard/members-graphs.hbs create mode 100644 ghost/admin/app/components/dashboard/members-graphs.js diff --git a/ghost/admin/app/components/dashboard/members-graphs.hbs b/ghost/admin/app/components/dashboard/members-graphs.hbs new file mode 100644 index 0000000000..67c3b5ec35 --- /dev/null +++ b/ghost/admin/app/components/dashboard/members-graphs.hbs @@ -0,0 +1,106 @@ +
+
+
+

MRR

+

30 days

+
+
+ {{#if this.mrrStatsLoading}} + Loading... + {{else}} + {{#if this.mrrStatsError}} +

+ There was an error loading MRR + {{this.mrrStatsError.message}} +

+ {{else}} +
+
{{this.mrrStatsData.currency}}{{format-number this.mrrStatsData.currentAmount}}
+
{{this.mrrStatsData.percentGrowth}}%
+
+ {{#if this.mrrStatsData}} +
+ +
+ {{/if}} + {{/if}} + {{/if}} +
+
+
+
+ {{#if this.memberCountStatsLoading}} + Loading... + {{else}} + {{#if this.memberCountStatsError}} +

+ There was an error loading total members + {{this.memberCountStatsData.message}} +

+ {{else}} +
+

Total members

+
+
{{format-number this.memberCountStatsData.all.total}}
+
{{this.memberCountStatsData.all.percentGrowth}}%
+
+
+
+ +
+ {{/if}} + {{/if}} +
+
+ + +
\ No newline at end of file diff --git a/ghost/admin/app/components/dashboard/members-graphs.js b/ghost/admin/app/components/dashboard/members-graphs.js new file mode 100644 index 0000000000..ef4a360f52 --- /dev/null +++ b/ghost/admin/app/components/dashboard/members-graphs.js @@ -0,0 +1,161 @@ +import Component from '@glimmer/component'; +import {getSymbol} from 'ghost-admin/utils/currency'; +import {inject as service} from '@ember/service'; +import {tracked} from '@glimmer/tracking'; + +export default class DashboardMembersGraphs extends Component { + @service membersStats; + @service store; + + @tracked mrrStatsData = null; + @tracked mrrStatsError = null; + @tracked mrrStatsLoading = false; + + @tracked memberCountStatsData = null; + @tracked memberCountStatsError = null; + @tracked memberCountStatsLoading = false; + + @tracked newsletterOpenRatesData = null; + @tracked newsletterOpenRatesError = null; + @tracked newsletterOpenRatesLoading = false; + + constructor() { + super(...arguments); + this.loadCharts(); + } + + loadCharts() { + this.loadMRRStats(); + this.loadMemberCountStats(); + this.loadNewsletterOpenRates(); + } + + async loadMRRStats() { + const products = await this.store.query('product', { + filter: 'type:paid', include: 'monthly_price,yearly_price', limit: 'all' + }); + const defaultProduct = products?.firstObject; + + this.mrrStatsLoading = true; + this.membersStats.fetchMRR().then((stats) => { + this.mrrStatsLoading = false; + const statsData = stats.data || []; + const defaultCurrency = defaultProduct?.monthlyPrice?.currency || 'usd'; + let currencyStats = statsData.find((stat) => { + return stat.currency === defaultCurrency; + }); + currencyStats = currencyStats || { + data: [], + currency: defaultCurrency + }; + if (currencyStats) { + const currencyStatsData = this.membersStats.fillDates(currencyStats.data) || {}; + const dateValues = Object.values(currencyStatsData).map(val => Math.round((val / 100))); + const currentMRR = dateValues.length ? dateValues[dateValues.length - 1] : 0; + const rangeStartMRR = dateValues.length ? dateValues[0] : 0; + const percentGrowth = rangeStartMRR !== 0 ? ((currentMRR - rangeStartMRR) / rangeStartMRR) * 100 : 0; + this.mrrStatsData = { + currentAmount: currentMRR, + currency: getSymbol(currencyStats.currency), + percentGrowth: percentGrowth.toFixed(1), + percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')), + options: { + rangeInDays: 30 + }, + data: { + label: 'MRR', + dateLabels: Object.keys(currencyStatsData), + dateValues + }, + title: 'MRR', + stats: currencyStats + }; + } + }, (error) => { + this.mrrStatsError = error; + this.mrrStatsLoading = false; + }); + } + + loadMemberCountStats() { + this.memberCountStatsLoading = true; + this.membersStats.fetchCounts().then((stats) => { + this.memberCountStatsLoading = false; + + if (stats) { + const statsDateObj = this.membersStats.fillCountDates(stats.data) || {}; + const dateValues = Object.values(statsDateObj); + const currentAllCount = dateValues.length ? dateValues[dateValues.length - 1].total : 0; + const currentPaidCount = dateValues.length ? dateValues[dateValues.length - 1].paid : 0; + const rangeStartAllCount = dateValues.length ? dateValues[0].total : 0; + const rangeStartPaidCount = dateValues.length ? dateValues[0].paid : 0; + const allCountPercentGrowth = rangeStartAllCount !== 0 ? ((currentAllCount - rangeStartAllCount) / rangeStartAllCount) * 100 : 0; + const paidCountPercentGrowth = rangeStartPaidCount !== 0 ? ((currentPaidCount - rangeStartPaidCount) / rangeStartPaidCount) * 100 : 0; + + this.memberCountStatsData = { + all: { + percentGrowth: allCountPercentGrowth.toFixed(1), + percentClass: (allCountPercentGrowth > 0 ? 'positive' : (allCountPercentGrowth < 0 ? 'negative' : '')), + total: dateValues.length ? dateValues[dateValues.length - 1].total : 0, + options: { + rangeInDays: 30 + }, + data: { + label: 'Members', + dateLabels: Object.keys(statsDateObj), + dateValues: dateValues.map(d => d.total) + }, + title: 'Total Members', + stats: stats + }, + paid: { + percentGrowth: paidCountPercentGrowth.toFixed(1), + percentClass: (paidCountPercentGrowth > 0 ? 'positive' : (paidCountPercentGrowth < 0 ? 'negative' : '')), + total: dateValues.length ? dateValues[dateValues.length - 1].paid : 0, + options: { + rangeInDays: 30 + }, + data: { + label: 'Members', + dateLabels: Object.keys(statsDateObj), + dateValues: dateValues.map(d => d.paid) + }, + title: 'Paid Members', + stats: stats + } + }; + } + }, (error) => { + this.memberCountStatsError = error; + this.memberCountStatsLoading = false; + }); + } + + loadNewsletterOpenRates() { + this.newsletterOpenRatesLoading = true; + this.membersStats.fetchNewsletterStats().then((results) => { + const rangeStartOpenRate = results.length > 1 ? results[results.length - 2].openRate : 0; + const rangeEndOpenRate = results.length > 0 ? results[results.length - 1].openRate : 0; + const percentGrowth = rangeStartOpenRate !== 0 ? ((rangeEndOpenRate - rangeStartOpenRate) / rangeStartOpenRate) * 100 : 0; + this.newsletterOpenRatesData = { + percentGrowth: percentGrowth.toFixed(1), + percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')), + current: rangeEndOpenRate, + options: { + rangeInDays: 30 + }, + data: { + label: 'Open rate', + dateLabels: results.map(d => d.subject), + dateValues: results.map(d => d.openRate) + }, + title: 'Open rate', + stats: results + }; + this.newsletterOpenRatesLoading = false; + }, (error) => { + this.newsletterOpenRatesError = error; + this.newsletterOpenRatesLoading = false; + }); + } +} diff --git a/ghost/admin/app/controllers/dashboard.js b/ghost/admin/app/controllers/dashboard.js index 535d161d66..367bd53517 100644 --- a/ghost/admin/app/controllers/dashboard.js +++ b/ghost/admin/app/controllers/dashboard.js @@ -1,6 +1,5 @@ import Controller from '@ember/controller'; import {action} from '@ember/object'; -import {getSymbol} from 'ghost-admin/utils/currency'; import {inject as service} from '@ember/service'; import {tracked} from '@glimmer/tracking'; @@ -12,22 +11,10 @@ export default class DashboardController extends Controller { @service settings; @service whatsNew; - @tracked mrrStatsData = null; - @tracked mrrStatsError = null; - @tracked mrrStatsLoading = false; - - @tracked memberCountStatsData = null; - @tracked memberCountStatsError = null; - @tracked memberCountStatsLoading = false; - @tracked topMembersData = null; @tracked topMembersError = null; @tracked topMembersLoading = false; - @tracked newsletterOpenRatesData = null; - @tracked newsletterOpenRatesError = null; - @tracked newsletterOpenRatesLoading = false; - @tracked whatsNewEntries = null; @tracked whatsNewEntriesLoading = null; @tracked whatsNewEntriesError = null; @@ -44,145 +31,9 @@ export default class DashboardController extends Controller { initialise() { this.loadTopMembers(); - this.loadCharts(); this.loadWhatsNew(); } - async loadMRRStats() { - const products = await this.store.query('product', { - filter: 'type:paid', include: 'monthly_price,yearly_price', limit: 'all' - }); - const defaultProduct = products?.firstObject; - - this.mrrStatsLoading = true; - this.membersStats.fetchMRR().then((stats) => { - this.mrrStatsLoading = false; - const statsData = stats.data || []; - const defaultCurrency = defaultProduct?.monthlyPrice?.currency || 'usd'; - let currencyStats = statsData.find((stat) => { - return stat.currency === defaultCurrency; - }); - currencyStats = currencyStats || { - data: [], - currency: defaultCurrency - }; - if (currencyStats) { - const currencyStatsData = this.membersStats.fillDates(currencyStats.data) || {}; - const dateValues = Object.values(currencyStatsData).map(val => Math.round((val / 100))); - const currentMRR = dateValues.length ? dateValues[dateValues.length - 1] : 0; - const rangeStartMRR = dateValues.length ? dateValues[0] : 0; - const percentGrowth = rangeStartMRR !== 0 ? ((currentMRR - rangeStartMRR) / rangeStartMRR) * 100 : 0; - this.mrrStatsData = { - currentAmount: currentMRR, - currency: getSymbol(currencyStats.currency), - percentGrowth: percentGrowth.toFixed(1), - percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')), - options: { - rangeInDays: 30 - }, - data: { - label: 'MRR', - dateLabels: Object.keys(currencyStatsData), - dateValues - }, - title: 'MRR', - stats: currencyStats - }; - } - }, (error) => { - this.mrrStatsError = error; - this.mrrStatsLoading = false; - }); - } - - loadMemberCountStats() { - this.memberCountStatsLoading = true; - this.membersStats.fetchCounts().then((stats) => { - this.memberCountStatsLoading = false; - - if (stats) { - const statsDateObj = this.membersStats.fillCountDates(stats.data) || {}; - const dateValues = Object.values(statsDateObj); - const currentAllCount = dateValues.length ? dateValues[dateValues.length - 1].total : 0; - const currentPaidCount = dateValues.length ? dateValues[dateValues.length - 1].paid : 0; - const rangeStartAllCount = dateValues.length ? dateValues[0].total : 0; - const rangeStartPaidCount = dateValues.length ? dateValues[0].paid : 0; - const allCountPercentGrowth = rangeStartAllCount !== 0 ? ((currentAllCount - rangeStartAllCount) / rangeStartAllCount) * 100 : 0; - const paidCountPercentGrowth = rangeStartPaidCount !== 0 ? ((currentPaidCount - rangeStartPaidCount) / rangeStartPaidCount) * 100 : 0; - - this.memberCountStatsData = { - all: { - percentGrowth: allCountPercentGrowth.toFixed(1), - percentClass: (allCountPercentGrowth > 0 ? 'positive' : (allCountPercentGrowth < 0 ? 'negative' : '')), - total: dateValues.length ? dateValues[dateValues.length - 1].total : 0, - options: { - rangeInDays: 30 - }, - data: { - label: 'Members', - dateLabels: Object.keys(statsDateObj), - dateValues: dateValues.map(d => d.total) - }, - title: 'Total Members', - stats: stats - }, - paid: { - percentGrowth: paidCountPercentGrowth.toFixed(1), - percentClass: (paidCountPercentGrowth > 0 ? 'positive' : (paidCountPercentGrowth < 0 ? 'negative' : '')), - total: dateValues.length ? dateValues[dateValues.length - 1].paid : 0, - options: { - rangeInDays: 30 - }, - data: { - label: 'Members', - dateLabels: Object.keys(statsDateObj), - dateValues: dateValues.map(d => d.paid) - }, - title: 'Paid Members', - stats: stats - } - }; - } - }, (error) => { - this.memberCountStatsError = error; - this.memberCountStatsLoading = false; - }); - } - - loadCharts() { - this.loadMRRStats(); - this.loadMemberCountStats(); - this.loadNewsletterOpenRates(); - } - - loadNewsletterOpenRates() { - this.newsletterOpenRatesLoading = true; - this.membersStats.fetchNewsletterStats().then((results) => { - const rangeStartOpenRate = results.length > 1 ? results[results.length - 2].openRate : 0; - const rangeEndOpenRate = results.length > 0 ? results[results.length - 1].openRate : 0; - const percentGrowth = rangeStartOpenRate !== 0 ? ((rangeEndOpenRate - rangeStartOpenRate) / rangeStartOpenRate) * 100 : 0; - this.newsletterOpenRatesData = { - percentGrowth: percentGrowth.toFixed(1), - percentClass: (percentGrowth > 0 ? 'positive' : (percentGrowth < 0 ? 'negative' : '')), - current: rangeEndOpenRate, - options: { - rangeInDays: 30 - }, - data: { - label: 'Open rate', - dateLabels: results.map(d => d.subject), - dateValues: results.map(d => d.openRate) - }, - title: 'Open rate', - stats: results - }; - this.newsletterOpenRatesLoading = false; - }, (error) => { - this.newsletterOpenRatesError = error; - this.newsletterOpenRatesLoading = false; - }); - } - loadTopMembers() { if (this.feature.membersActivityFeed) { return; diff --git a/ghost/admin/app/templates/dashboard.hbs b/ghost/admin/app/templates/dashboard.hbs index 871f80cab9..6933b50984 100644 --- a/ghost/admin/app/templates/dashboard.hbs +++ b/ghost/admin/app/templates/dashboard.hbs @@ -58,112 +58,7 @@ {{else if this.showMembersData}} -
-
-
-

MRR

-

30 days

-
-
- {{#if this.mrrStatsLoading}} - Loading... - {{else}} - {{#if this.mrrStatsError}} -

- There was an error loading MRR - {{this.mrrStatsError.message}} -

- {{else}} -
-
{{this.mrrStatsData.currency}}{{format-number this.mrrStatsData.currentAmount}}
-
{{this.mrrStatsData.percentGrowth}}%
-
- {{#if this.mrrStatsData}} -
- -
- {{/if}} - {{/if}} - {{/if}} -
-
-
-
- {{#if this.memberCountStatsLoading}} - Loading... - {{else}} - {{#if this.memberCountStatsError}} -

- There was an error loading total members - {{this.memberCountStatsData.message}} -

- {{else}} -
-

Total members

-
-
{{format-number this.memberCountStatsData.all.total}}
-
{{this.memberCountStatsData.all.percentGrowth}}%
-
-
-
- -
- {{/if}} - {{/if}} -
-
- - -
+ {{/if}}