mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 21:03:29 +03:00
208b4948ff
refs https://github.com/TryGhost/Team/issues/807 refs https://github.com/TryGhost/Ghost/pull/13703 The launch wizard completed flag was previously stored at per user level in accessibility column of user table, so an administrator still got the option to complete the launch wizard even if the owner had completed it previously, which is not expected pattern. This change moves the launch complete flag to be stored in new global setting for site.
242 lines
9.3 KiB
JavaScript
242 lines
9.3 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 membersStats;
|
|
@service store;
|
|
@service settings;
|
|
@service whatsNew;
|
|
|
|
@tracked eventsData = null;
|
|
@tracked eventsError = null;
|
|
@tracked eventsLoading = false;
|
|
|
|
@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.loadEvents();
|
|
this.loadTopMembers();
|
|
this.loadCharts();
|
|
this.loadWhatsNew();
|
|
}
|
|
|
|
async loadMRRStats() {
|
|
const products = await this.store.query('product', {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();
|
|
}
|
|
|
|
loadEvents() {
|
|
this.eventsLoading = true;
|
|
this.membersStats.fetchTimeline({limit: 5}).then(({events}) => {
|
|
this.eventsData = events;
|
|
this.eventsLoading = false;
|
|
}, (error) => {
|
|
this.eventsError = error;
|
|
this.eventsLoading = 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;
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|