mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-15 03:01:37 -05:00
Added admin mock stats for source attribution
refs https://github.com/TryGhost/Team/issues/1891 - adds basic mock stats data for member source attribution on dashboard
This commit is contained in:
parent
a3a0a1c46c
commit
e151791fe2
2 changed files with 160 additions and 36 deletions
|
@ -137,11 +137,11 @@ export default class DashboardMocksService extends Service {
|
|||
* This method generates new data and forces a reload for all the charts
|
||||
* Might be better to move this code to a temporary mocking service
|
||||
*/
|
||||
updateMockedData({days}) {
|
||||
updateMockedData({days}) {
|
||||
const generateDays = days;
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - generateDays + 1);
|
||||
|
||||
|
||||
/**
|
||||
* @type {MemberCountStat[]}
|
||||
*/
|
||||
|
@ -236,7 +236,7 @@ export default class DashboardMocksService extends Service {
|
|||
let freeDelta = Math.max(0, this._updateGrow(freeGrowth));
|
||||
|
||||
viralCounter -= 1;
|
||||
|
||||
|
||||
if (viralCounter <= 0) {
|
||||
viralCounter = Math.floor(Math.random() * 900);
|
||||
freeDelta += Math.floor(Math.random() * 20 * index);
|
||||
|
@ -334,10 +334,79 @@ export default class DashboardMocksService extends Service {
|
|||
};
|
||||
|
||||
this.emailsSent30d = Math.floor(days * 123 / 90);
|
||||
|
||||
|
||||
this.membersLastSeen7d = Math.round(Math.random() * currentCounts.free / 2);
|
||||
this.membersLastSeen30d = this.membersLastSeen7d + Math.round(Math.random() * currentCounts.free / 2);
|
||||
|
||||
this.membersAttributionSources7d = [
|
||||
{
|
||||
source: 'Twitter',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 3),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 3)
|
||||
},
|
||||
{
|
||||
source: 'Ghost Newsletter',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 3),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 3)
|
||||
},
|
||||
{
|
||||
source: 'Ghost Network',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 3),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 3)
|
||||
},
|
||||
{
|
||||
source: 'Direct',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 3),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 3)
|
||||
}
|
||||
];
|
||||
|
||||
this.membersAttributionSources30d = [
|
||||
{
|
||||
source: 'Twitter',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 2),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 2)
|
||||
},
|
||||
{
|
||||
source: 'Ghost Newsletter',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 2),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 2)
|
||||
},
|
||||
{
|
||||
source: 'Ghost Network',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 2),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 2)
|
||||
},
|
||||
{
|
||||
source: 'Direct',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 2),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 2)
|
||||
}
|
||||
];
|
||||
|
||||
this.membersAttributionSources90d = [
|
||||
{
|
||||
source: 'Twitter',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 20),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 20)
|
||||
},
|
||||
{
|
||||
source: 'Ghost Newsletter',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 20),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 20)
|
||||
},
|
||||
{
|
||||
source: 'Ghost Network',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 20),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 20)
|
||||
},
|
||||
{
|
||||
source: 'Direct',
|
||||
freeSignups: Math.floor(Math.random() * currentCounts.free / 20),
|
||||
paidConversions: Math.floor(Math.random() * currentCounts.paid / 20)
|
||||
}
|
||||
];
|
||||
|
||||
this.emailOpenRateStats = [];
|
||||
if (days >= 7) {
|
||||
this.emailOpenRateStats.push(
|
||||
|
|
|
@ -21,6 +21,14 @@ import {tracked} from '@glimmer/tracking';
|
|||
* @property {number} paidCanceled Amount of canceled paid members
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef AttributionCountStat
|
||||
* @type {Object}
|
||||
* @property {number} source Attribution Source
|
||||
* @property {number} freeSignups Total free members signed up for this source
|
||||
* @property {number} paidConversions Total paid conversions for this source
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef MemberCounts
|
||||
* @type {Object}
|
||||
|
@ -107,6 +115,24 @@ export default class DashboardStatsService extends Service {
|
|||
@tracked
|
||||
membersLastSeen30d = null;
|
||||
|
||||
/**
|
||||
* @type {AttributionCountStat[]} Count of Attribution sources in last 7 days
|
||||
*/
|
||||
@tracked
|
||||
membersAttributionSources7d = null;
|
||||
|
||||
/**
|
||||
* @type {AttributionCountStat[]} Count of Attribution sources in last 30 days
|
||||
*/
|
||||
@tracked
|
||||
membersAttributionSources30d = null;
|
||||
|
||||
/**
|
||||
* @type {AttributionCountStat[]} Count of Attribution sources in last 90 days
|
||||
*/
|
||||
@tracked
|
||||
membersAttributionSources90d = null;
|
||||
|
||||
/**
|
||||
* @type {?number} Number of members last seen in last 7 days (could differ if filtered by member status)
|
||||
*/
|
||||
|
@ -476,50 +502,77 @@ export default class DashboardStatsService extends Service {
|
|||
});
|
||||
}
|
||||
|
||||
loadMrrStats() {
|
||||
if (this._loadMrrStats.isRunning) {
|
||||
loadMemberAttributionStats() {
|
||||
if (this._loadMemberAttributionStats.isRunning) {
|
||||
// We need to explicitly wait for the already running task instead of dropping it and returning immediately
|
||||
return this._loadMrrStats.last;
|
||||
return this._loadMemberAttributionStats.last;
|
||||
}
|
||||
return this._loadMrrStats.perform();
|
||||
return this._loadMemberAttributionStats.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the members attribution stats
|
||||
*/
|
||||
@task
|
||||
*_loadMemberAttributionStats() {
|
||||
this.membersAttributionSources7d = null;
|
||||
this.membersAttributionSources30d = null;
|
||||
this.membersAttributionSources90d = null;
|
||||
|
||||
if (this.dashboardMocks.enabled) {
|
||||
yield this.dashboardMocks.waitRandom();
|
||||
this.membersAttributionSources7d = this.dashboardMocks.membersAttributionSources7d;
|
||||
this.membersAttributionSources30d = this.dashboardMocks.membersAttributionSources30d;
|
||||
this.membersAttributionSources90d = this.dashboardMocks.membersAttributionSources90d;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
loadMrrStats() {
|
||||
if (this._loadMrrStats.isRunning) {
|
||||
// We need to explicitly wait for the already running task instead of dropping it and returning immediately
|
||||
return this._loadMrrStats.last;
|
||||
}
|
||||
return this._loadMrrStats.perform();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the mrr graphs for the current chartDays days
|
||||
*/
|
||||
@task
|
||||
*_loadMrrStats() {
|
||||
this.mrrStats = null;
|
||||
if (this.dashboardMocks.enabled) {
|
||||
yield this.dashboardMocks.waitRandom();
|
||||
if (this.dashboardMocks.mrrStats === null) {
|
||||
return null;
|
||||
}
|
||||
this.mrrStats = this.dashboardMocks.mrrStats;
|
||||
return;
|
||||
}
|
||||
*_loadMrrStats() {
|
||||
this.mrrStats = null;
|
||||
if (this.dashboardMocks.enabled) {
|
||||
yield this.dashboardMocks.waitRandom();
|
||||
if (this.dashboardMocks.mrrStats === null) {
|
||||
return null;
|
||||
}
|
||||
this.mrrStats = this.dashboardMocks.mrrStats;
|
||||
return;
|
||||
}
|
||||
|
||||
let statsUrl = this.ghostPaths.url.api('stats/mrr');
|
||||
let stats = yield this.ajax.request(statsUrl);
|
||||
let statsUrl = this.ghostPaths.url.api('stats/mrr');
|
||||
let stats = yield this.ajax.request(statsUrl);
|
||||
|
||||
// Only show the highest value currency and filter the other ones out
|
||||
const totals = stats.meta.totals;
|
||||
let currentMax = totals[0];
|
||||
if (!currentMax) {
|
||||
// No valid data
|
||||
this.mrrStats = [];
|
||||
return;
|
||||
}
|
||||
// Only show the highest value currency and filter the other ones out
|
||||
const totals = stats.meta.totals;
|
||||
let currentMax = totals[0];
|
||||
if (!currentMax) {
|
||||
// No valid data
|
||||
this.mrrStats = [];
|
||||
return;
|
||||
}
|
||||
|
||||
for (const total of totals) {
|
||||
if (total.mrr > currentMax.mrr) {
|
||||
currentMax = total;
|
||||
}
|
||||
}
|
||||
for (const total of totals) {
|
||||
if (total.mrr > currentMax.mrr) {
|
||||
currentMax = total;
|
||||
}
|
||||
}
|
||||
|
||||
const useCurrency = currentMax.currency;
|
||||
this.mrrStats = stats.stats.filter(d => d.currency === useCurrency);
|
||||
}
|
||||
const useCurrency = currentMax.currency;
|
||||
this.mrrStats = stats.stats.filter(d => d.currency === useCurrency);
|
||||
}
|
||||
|
||||
loadLastSeen() {
|
||||
// todo: add proper logic to prevent duplicate calls + reuse results if nothing has changed
|
||||
|
@ -703,6 +756,7 @@ export default class DashboardStatsService extends Service {
|
|||
await this._loadNewsletterSubscribers.cancelAll();
|
||||
await this._loadEmailsSent.cancelAll();
|
||||
await this._loadEmailOpenRateStats.cancelAll();
|
||||
await this._loadMemberAttributionStats.cancelAll();
|
||||
|
||||
// Restart tasks
|
||||
this.loadSiteStatus();
|
||||
|
@ -717,6 +771,7 @@ export default class DashboardStatsService extends Service {
|
|||
this.loadNewsletterSubscribers();
|
||||
this.loadEmailsSent();
|
||||
this.loadEmailOpenRateStats();
|
||||
this.loadMemberAttributionStats();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue