mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-25 00:54:50 +03:00
Added source attribution table to posts' analytics page
refs https://github.com/TryGhost/Team/issues/1921
This commit is contained in:
parent
cd4e4d7003
commit
ad79d45926
@ -6,52 +6,11 @@
|
||||
grid-template-columns: 2fr 1fr;
|
||||
grid-gap: 64px;
|
||||
">
|
||||
<div class="gh-dashboard-recents-posts gh-dashboard-list">
|
||||
<div class="gh-dashboard-list-header">
|
||||
<div class="gh-dashboard-list-title">Sources</div>
|
||||
<div class="gh-dashboard-list-title">Free Signups</div>
|
||||
<div class="gh-dashboard-list-title">Paid Conversions</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-body">
|
||||
{{#each this.sources as |sourceData|}}
|
||||
<div class="gh-dashboard-list-item">
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-list-text">{{sourceData.source}}</span>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-metric-minivalue">
|
||||
{{#if sourceData.freeSignups}}
|
||||
{{format-number sourceData.freeSignups}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-metric-minivalue">
|
||||
{{#if sourceData.paidConversions}}
|
||||
{{format-number sourceData.paidConversions}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-empty">
|
||||
<p>No sources.</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<MemberAttribution::SourceAttributionTable @sources={{this.sources}} class="gh-dashboard-recents-posts gh-dashboard-list" />
|
||||
|
||||
<div style="border-left: 1px solid #eceef0; padding-left: 48px;display: flex;justify-content: center;align-items: center;">
|
||||
<div style="max-width: 200px;">
|
||||
<EmberChart
|
||||
@type='doughnut'
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@height={{400}}
|
||||
/>
|
||||
<MemberAttribution::SourceAttributionChart @sources={{this.sources}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,76 +1,15 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#853EED',
|
||||
'#CA3FED',
|
||||
'#E993CC',
|
||||
'#EE9696',
|
||||
'#FEC7C0'
|
||||
];
|
||||
export default class Recents extends Component {
|
||||
@service dashboardStats;
|
||||
@tracked chartType = 'free';
|
||||
|
||||
@action
|
||||
loadData() {
|
||||
this.dashboardStats.loadMemberAttributionStats();
|
||||
}
|
||||
|
||||
get chartOptions() {
|
||||
return {
|
||||
cutoutPercentage: 60,
|
||||
borderColor: '#555',
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
align: 'start',
|
||||
labels: {
|
||||
color: 'rgb(255, 99, 132)',
|
||||
fontSize: 12,
|
||||
boxWidth: 10,
|
||||
padding: 3
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
if (this.chartType === 'free') {
|
||||
const sortedByFree = [...this.sources];
|
||||
sortedByFree.sort((a, b) => {
|
||||
return b.freeSignups - a.freeSignups;
|
||||
});
|
||||
return {
|
||||
labels: sortedByFree.slice(0, 5).map(source => source.source),
|
||||
datasets: [{
|
||||
label: 'Free Signups',
|
||||
data: sortedByFree.slice(0, 5).map(source => source.freeSignups),
|
||||
backgroundColor: CHART_COLORS.slice(0, 5),
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
const sortedByPaid = [...this.sources];
|
||||
sortedByPaid.sort((a, b) => {
|
||||
return b.paidPercentage - a.paidPercentage;
|
||||
});
|
||||
return {
|
||||
labels: sortedByPaid.slice(0, 5).map(source => source.source),
|
||||
datasets: [{
|
||||
label: 'Paid Conversions',
|
||||
data: sortedByPaid.slice(0, 5).map(source => source.paidConversions),
|
||||
backgroundColor: CHART_COLORS.slice(0, 5),
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get sources() {
|
||||
return this.dashboardStats?.memberSourceAttributionCounts || [];
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
<EmberChart
|
||||
@type='doughnut'
|
||||
@data={{this.chartData}}
|
||||
@options={{this.chartOptions}}
|
||||
@height={{400}}
|
||||
/>
|
@ -0,0 +1,70 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#853EED',
|
||||
'#CA3FED',
|
||||
'#E993CC',
|
||||
'#EE9696',
|
||||
'#FEC7C0'
|
||||
];
|
||||
|
||||
export default class SourceAttributionChart extends Component {
|
||||
@tracked chartType = 'free';
|
||||
|
||||
get sources() {
|
||||
return this.args.sources;
|
||||
}
|
||||
|
||||
get chartOptions() {
|
||||
return {
|
||||
cutoutPercentage: 60,
|
||||
borderColor: '#555',
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
align: 'start',
|
||||
labels: {
|
||||
color: 'rgb(255, 99, 132)',
|
||||
fontSize: 12,
|
||||
boxWidth: 10,
|
||||
padding: 3
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get chartData() {
|
||||
if (this.chartType === 'free') {
|
||||
const sortedByFree = [...this.sources];
|
||||
sortedByFree.sort((a, b) => {
|
||||
return b.freeSignups - a.freeSignups;
|
||||
});
|
||||
return {
|
||||
labels: sortedByFree.slice(0, 5).map(source => source.source),
|
||||
datasets: [{
|
||||
label: 'Free Signups',
|
||||
data: sortedByFree.slice(0, 5).map(source => source.freeSignups),
|
||||
backgroundColor: CHART_COLORS.slice(0, 5),
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
const sortedByPaid = [...this.sources];
|
||||
sortedByPaid.sort((a, b) => {
|
||||
return b.paidPercentage - a.paidPercentage;
|
||||
});
|
||||
return {
|
||||
labels: sortedByPaid.slice(0, 5).map(source => source.source),
|
||||
datasets: [{
|
||||
label: 'Paid Conversions',
|
||||
data: sortedByPaid.slice(0, 5).map(source => source.paidConversions),
|
||||
backgroundColor: CHART_COLORS.slice(0, 5),
|
||||
borderWidth: 2,
|
||||
borderColor: '#fff'
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
<div ...attributes>
|
||||
<div class="gh-dashboard-list-header">
|
||||
<div class="gh-dashboard-list-title">Sources</div>
|
||||
<div class="gh-dashboard-list-title">Free Signups</div>
|
||||
<div class="gh-dashboard-list-title">Paid Conversions</div>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-body">
|
||||
{{#each @sources as |sourceData|}}
|
||||
<div class="gh-dashboard-list-item">
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-list-text">{{sourceData.source}}</span>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-metric-minivalue">
|
||||
{{#if sourceData.freeSignups}}
|
||||
{{format-number sourceData.freeSignups}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="gh-dashboard-list-item-sub">
|
||||
<span class="gh-dashboard-metric-minivalue">
|
||||
{{#if sourceData.paidConversions}}
|
||||
{{format-number sourceData.paidConversions}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="gh-dashboard-list-empty">
|
||||
<p>No sources.</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
@ -1,7 +1,34 @@
|
||||
import Controller from '@ember/controller';
|
||||
|
||||
/**
|
||||
* @typedef {import('../../services/dashboard-stats').SourceAttributionCount} SourceAttributionCount
|
||||
*/
|
||||
|
||||
export default class AnalyticsController extends Controller {
|
||||
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
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -31,9 +31,9 @@ import {tracked} from '@glimmer/tracking';
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef SourceAttributionCounts
|
||||
* @typedef SourceAttributionCount
|
||||
* @type {Object}
|
||||
* @property {number} source Attribution Source
|
||||
* @property {string} source Attribution Source
|
||||
* @property {number} freeSignups Total free members signed up for this source
|
||||
* @property {number} paidConversions Total paid conversions for this source
|
||||
*/
|
||||
@ -233,7 +233,7 @@ export default class DashboardStatsService extends Service {
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {?SourceAttributionCounts}
|
||||
* @type {SourceAttributionCount[]}
|
||||
*/
|
||||
get memberSourceAttributionCounts() {
|
||||
if (!this.memberAttributionStats) {
|
||||
|
@ -85,6 +85,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if (feature 'sourceAttribution')}}
|
||||
<h4 class="gh-main-section-header small bn">
|
||||
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}} />
|
||||
|
||||
<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>
|
||||
{{/if}}
|
||||
|
||||
<h4 class="gh-main-section-header small bn">
|
||||
Get started with analytics
|
||||
</h4>
|
||||
|
Loading…
Reference in New Issue
Block a user