mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-04 17:04:59 +03:00
4b3fc52cf0
refs https://github.com/TryGhost/Team/issues/1277 - first step of further refactoring to make member activity display more generic - separates component data loading from overall controller logic and allows it to be tested in integration tests as well as acceptance tests
229 lines
9.0 KiB
JavaScript
229 lines
9.0 KiB
JavaScript
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';
|
|
|
|
export default class DashboardController extends Controller {
|
|
@service feature;
|
|
@service session;
|
|
@service membersActivity;
|
|
@service membersStats;
|
|
@service store;
|
|
@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;
|
|
|
|
get topMembersDataHasOpenRates() {
|
|
return this.topMembersData && this.topMembersData.find((member) => {
|
|
return member.emailOpenRate !== null;
|
|
});
|
|
}
|
|
|
|
get showMembersData() {
|
|
return this.settings.get('membersSignupAccess') !== 'none';
|
|
}
|
|
|
|
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() {
|
|
this.topMembersLoading = true;
|
|
let query = {
|
|
filter: 'email_open_rate:-null',
|
|
order: 'email_open_rate desc',
|
|
limit: 5
|
|
};
|
|
this.store.query('member', query).then((result) => {
|
|
if (!result.length) {
|
|
return this.store.query('member', {
|
|
filter: 'status:paid',
|
|
order: 'created_at asc',
|
|
limit: 5
|
|
});
|
|
}
|
|
return result;
|
|
}).then((result) => {
|
|
this.topMembersData = result;
|
|
this.topMembersLoading = false;
|
|
}).catch((error) => {
|
|
this.topMembersError = error;
|
|
this.topMembersLoading = false;
|
|
});
|
|
}
|
|
|
|
loadWhatsNew() {
|
|
this.whatsNewEntriesLoading = true;
|
|
this.whatsNew.fetchLatest.perform().then(() => {
|
|
this.whatsNewEntriesLoading = false;
|
|
this.whatsNewEntries = this.whatsNew.entries.slice(0, 3);
|
|
}, (error) => {
|
|
this.whatsNewEntriesError = error;
|
|
this.whatsNewEntriesLoading = false;
|
|
});
|
|
}
|
|
|
|
@action
|
|
dismissLaunchBanner() {
|
|
this.settings.set('editorIsLaunchComplete', true);
|
|
this.settings.save();
|
|
}
|
|
}
|