Implemented referrers stats API on posts' analytics page

refs https://github.com/TryGhost/Team/issues/1921
This commit is contained in:
Simon Backx 2022-09-21 18:18:01 +02:00
parent 7437d92d50
commit 7cadaa6378
6 changed files with 55 additions and 42 deletions

View File

@ -38,13 +38,13 @@ export default class SourceAttributionChart extends Component {
if (this.chartType === 'free') {
const sortedByFree = [...this.sources];
sortedByFree.sort((a, b) => {
return b.freeSignups - a.freeSignups;
return b.signups - a.signups;
});
return {
labels: sortedByFree.slice(0, 5).map(source => source.source),
datasets: [{
label: 'Free Signups',
data: sortedByFree.slice(0, 5).map(source => source.freeSignups),
data: sortedByFree.slice(0, 5).map(source => source.signups),
backgroundColor: CHART_COLORS.slice(0, 5),
borderWidth: 2,
borderColor: '#fff'

View File

@ -12,8 +12,8 @@
</div>
<div class="gh-dashboard-list-item-sub">
<span class="gh-dashboard-metric-minivalue">
{{#if sourceData.freeSignups}}
{{format-number sourceData.freeSignups}}
{{#if sourceData.signups}}
{{format-number sourceData.signups}}
{{else}}
&mdash;
{{/if}}

View File

@ -1,34 +1,45 @@
import Controller from '@ember/controller';
import {action} from '@ember/object';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
/**
* @typedef {import('../../services/dashboard-stats').SourceAttributionCount} SourceAttributionCount
*/
export default class AnalyticsController extends Controller {
@service ajax;
@service ghostPaths;
@tracked sources = null;
get post() {
return this.model;
}
/**
* @returns {SourceAttributionCount[]} - array of objects with source and count properties
*/
get sources() {
return [
{
source: 'Twitter',
freeSignups: 12,
paidConversions: 50
},
{
source: 'Google',
freeSignups: 9,
paidConversions: 32
},
{
source: 'Direct',
freeSignups: 2,
paidConversions: 40
}
];
@action
loadData() {
this.fetchReferrersStats();
}
async fetchReferrersStats() {
if (this._fetchReferrersStats.isRunning) {
return this._fetchReferrersStats.last;
}
return this._fetchReferrersStats.perform();
}
@task
*_fetchReferrersStats() {
let statsUrl = this.ghostPaths.url.api(`stats/referrers/posts/${this.post.id}`);
let result = yield this.ajax.request(statsUrl);
this.sources = result.stats.map((stat) => {
return {
source: stat.source ?? 'Direct',
signups: stat.signups,
paidConversions: stat.paid_conversions
};
});
}
}

View File

@ -274,7 +274,7 @@ export default class DashboardMocksService extends Service {
this.memberAttributionStats.push({
date: date.toISOString().split('T')[0],
source: attributionSources[Math.floor(Math.random() * attributionSources.length)],
freeSignups: Math.floor(Math.random() * 50),
signups: Math.floor(Math.random() * 50),
paidConversions: Math.floor(Math.random() * 30)
});
}

View File

@ -26,7 +26,7 @@ import {tracked} from '@glimmer/tracking';
* @type {Object}
* @property {string} date The date (YYYY-MM-DD) on which these counts were recorded
* @property {number} source Attribution Source
* @property {number} freeSignups Total free members signed up for this source
* @property {number} signups Total free members signed up for this source
* @property {number} paidConversions Total paid conversions for this source
*/
@ -34,7 +34,7 @@ import {tracked} from '@glimmer/tracking';
* @typedef SourceAttributionCount
* @type {Object}
* @property {string} source Attribution Source
* @property {number} freeSignups Total free members signed up for this source
* @property {number} signups Total free members signed up for this source
* @property {number} paidConversions Total paid conversions for this source
*/
@ -248,18 +248,18 @@ export default class DashboardStatsService extends Service {
}).reduce((acc, stat) => {
const existingSource = acc.find(s => s.source === stat.source);
if (existingSource) {
existingSource.freeSignups += stat.freeSignups || 0;
existingSource.signups += stat.signups || 0;
existingSource.paidConversions += stat.paidConversions || 0;
} else {
acc.push({
source: stat.source,
freeSignups: stat.freeSignups || 0,
signups: stat.signups || 0,
paidConversions: stat.paidConversions || 0
});
}
return acc;
}, []).sort((a, b) => {
return (b.freeSignups + b.paidConversions) - (a.freeSignups - a.paidConversions);
return (b.signups + b.paidConversions) - (a.signups - a.paidConversions);
});
}

View File

@ -86,23 +86,25 @@
</div>
{{#if (feature 'sourceAttribution')}}
<h4 class="gh-main-section-header small bn">
<h4 class="gh-main-section-header small bn" {{did-insert this.loadData}}>
Source attribution
</h4>
<div class="gh-post-analytics-box resources">
<div style="display: grid;
grid-template-columns: 2fr 1fr;
grid-gap: 64px;
">
<MemberAttribution::SourceAttributionTable @sources={{this.sources}} />
{{#if this.sources}}
<div class="gh-post-analytics-box resources">
<div style="display: grid;
grid-template-columns: 2fr 1fr;
grid-gap: 64px;
">
<MemberAttribution::SourceAttributionTable @sources={{this.sources}} />
<div style="border-left: 1px solid #eceef0; padding-left: 48px;display: flex;justify-content: center;align-items: center;">
<div style="max-width: 200px;">
<MemberAttribution::SourceAttributionChart @sources={{this.sources}} />
<div style="border-left: 1px solid #eceef0; padding-left: 48px;display: flex;justify-content: center;align-items: center;">
<div style="max-width: 200px;">
<MemberAttribution::SourceAttributionChart @sources={{this.sources}} />
</div>
</div>
</div>
</div>
</div>
{{/if}}
{{/if}}
<h4 class="gh-main-section-header small bn">