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') { if (this.chartType === 'free') {
const sortedByFree = [...this.sources]; const sortedByFree = [...this.sources];
sortedByFree.sort((a, b) => { sortedByFree.sort((a, b) => {
return b.freeSignups - a.freeSignups; return b.signups - a.signups;
}); });
return { return {
labels: sortedByFree.slice(0, 5).map(source => source.source), labels: sortedByFree.slice(0, 5).map(source => source.source),
datasets: [{ datasets: [{
label: 'Free Signups', 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), backgroundColor: CHART_COLORS.slice(0, 5),
borderWidth: 2, borderWidth: 2,
borderColor: '#fff' borderColor: '#fff'

View File

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

View File

@ -1,34 +1,45 @@
import Controller from '@ember/controller'; 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 * @typedef {import('../../services/dashboard-stats').SourceAttributionCount} SourceAttributionCount
*/ */
export default class AnalyticsController extends Controller { export default class AnalyticsController extends Controller {
@service ajax;
@service ghostPaths;
@tracked sources = null;
get post() { get post() {
return this.model; return this.model;
} }
/** @action
* @returns {SourceAttributionCount[]} - array of objects with source and count properties loadData() {
*/ this.fetchReferrersStats();
get sources() { }
return [
{ async fetchReferrersStats() {
source: 'Twitter', if (this._fetchReferrersStats.isRunning) {
freeSignups: 12, return this._fetchReferrersStats.last;
paidConversions: 50 }
}, return this._fetchReferrersStats.perform();
{ }
source: 'Google',
freeSignups: 9, @task
paidConversions: 32 *_fetchReferrersStats() {
}, let statsUrl = this.ghostPaths.url.api(`stats/referrers/posts/${this.post.id}`);
{ let result = yield this.ajax.request(statsUrl);
source: 'Direct', this.sources = result.stats.map((stat) => {
freeSignups: 2, return {
paidConversions: 40 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({ this.memberAttributionStats.push({
date: date.toISOString().split('T')[0], date: date.toISOString().split('T')[0],
source: attributionSources[Math.floor(Math.random() * attributionSources.length)], 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) paidConversions: Math.floor(Math.random() * 30)
}); });
} }

View File

@ -26,7 +26,7 @@ import {tracked} from '@glimmer/tracking';
* @type {Object} * @type {Object}
* @property {string} date The date (YYYY-MM-DD) on which these counts were recorded * @property {string} date The date (YYYY-MM-DD) on which these counts were recorded
* @property {number} source Attribution Source * @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 * @property {number} paidConversions Total paid conversions for this source
*/ */
@ -34,7 +34,7 @@ import {tracked} from '@glimmer/tracking';
* @typedef SourceAttributionCount * @typedef SourceAttributionCount
* @type {Object} * @type {Object}
* @property {string} source Attribution Source * @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 * @property {number} paidConversions Total paid conversions for this source
*/ */
@ -248,18 +248,18 @@ export default class DashboardStatsService extends Service {
}).reduce((acc, stat) => { }).reduce((acc, stat) => {
const existingSource = acc.find(s => s.source === stat.source); const existingSource = acc.find(s => s.source === stat.source);
if (existingSource) { if (existingSource) {
existingSource.freeSignups += stat.freeSignups || 0; existingSource.signups += stat.signups || 0;
existingSource.paidConversions += stat.paidConversions || 0; existingSource.paidConversions += stat.paidConversions || 0;
} else { } else {
acc.push({ acc.push({
source: stat.source, source: stat.source,
freeSignups: stat.freeSignups || 0, signups: stat.signups || 0,
paidConversions: stat.paidConversions || 0 paidConversions: stat.paidConversions || 0
}); });
} }
return acc; return acc;
}, []).sort((a, b) => { }, []).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> </div>
{{#if (feature 'sourceAttribution')}} {{#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 Source attribution
</h4> </h4>
<div class="gh-post-analytics-box resources"> {{#if this.sources}}
<div style="display: grid; <div class="gh-post-analytics-box resources">
grid-template-columns: 2fr 1fr; <div style="display: grid;
grid-gap: 64px; grid-template-columns: 2fr 1fr;
"> grid-gap: 64px;
<MemberAttribution::SourceAttributionTable @sources={{this.sources}} /> ">
<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="border-left: 1px solid #eceef0; padding-left: 48px;display: flex;justify-content: center;align-items: center;">
<div style="max-width: 200px;"> <div style="max-width: 200px;">
<MemberAttribution::SourceAttributionChart @sources={{this.sources}} /> <MemberAttribution::SourceAttributionChart @sources={{this.sources}} />
</div>
</div> </div>
</div> </div>
</div> </div>
</div> {{/if}}
{{/if}} {{/if}}
<h4 class="gh-main-section-header small bn"> <h4 class="gh-main-section-header small bn">