diff --git a/ghost/admin/app/components/dashboard/charts/attribution.hbs b/ghost/admin/app/components/dashboard/charts/attribution.hbs index 513d91d8b3..e089c2f572 100644 --- a/ghost/admin/app/components/dashboard/charts/attribution.hbs +++ b/ghost/admin/app/components/dashboard/charts/attribution.hbs @@ -6,52 +6,11 @@ grid-template-columns: 2fr 1fr; grid-gap: 64px; "> -
-
-
Sources
-
Free Signups
-
Paid Conversions
-
-
- {{#each this.sources as |sourceData|}} -
-
- {{sourceData.source}} -
-
- - {{#if sourceData.freeSignups}} - {{format-number sourceData.freeSignups}} - {{else}} - — - {{/if}} - -
-
- - {{#if sourceData.paidConversions}} - {{format-number sourceData.paidConversions}} - {{else}} - — - {{/if}} - -
-
- {{else}} -
-

No sources.

-
- {{/each}} -
-
+ +
- +
diff --git a/ghost/admin/app/components/dashboard/charts/attribution.js b/ghost/admin/app/components/dashboard/charts/attribution.js index 3b0a0754ab..c0a4c7966d 100644 --- a/ghost/admin/app/components/dashboard/charts/attribution.js +++ b/ghost/admin/app/components/dashboard/charts/attribution.js @@ -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 || []; } diff --git a/ghost/admin/app/components/member-attribution/source-attribution-chart.hbs b/ghost/admin/app/components/member-attribution/source-attribution-chart.hbs new file mode 100644 index 0000000000..60152a3487 --- /dev/null +++ b/ghost/admin/app/components/member-attribution/source-attribution-chart.hbs @@ -0,0 +1,6 @@ + diff --git a/ghost/admin/app/components/member-attribution/source-attribution-chart.js b/ghost/admin/app/components/member-attribution/source-attribution-chart.js new file mode 100644 index 0000000000..580c5314ec --- /dev/null +++ b/ghost/admin/app/components/member-attribution/source-attribution-chart.js @@ -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' + }] + }; + } + } +} diff --git a/ghost/admin/app/components/member-attribution/source-attribution-table.hbs b/ghost/admin/app/components/member-attribution/source-attribution-table.hbs new file mode 100644 index 0000000000..6c204119b8 --- /dev/null +++ b/ghost/admin/app/components/member-attribution/source-attribution-table.hbs @@ -0,0 +1,38 @@ +
+
+
Sources
+
Free Signups
+
Paid Conversions
+
+
+ {{#each @sources as |sourceData|}} +
+
+ {{sourceData.source}} +
+
+ + {{#if sourceData.freeSignups}} + {{format-number sourceData.freeSignups}} + {{else}} + — + {{/if}} + +
+
+ + {{#if sourceData.paidConversions}} + {{format-number sourceData.paidConversions}} + {{else}} + — + {{/if}} + +
+
+ {{else}} +
+

No sources.

+
+ {{/each}} +
+
diff --git a/ghost/admin/app/controllers/posts/analytics.js b/ghost/admin/app/controllers/posts/analytics.js index d6f5e93de1..fe25751473 100644 --- a/ghost/admin/app/controllers/posts/analytics.js +++ b/ghost/admin/app/controllers/posts/analytics.js @@ -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 + } + ]; + } } diff --git a/ghost/admin/app/services/dashboard-stats.js b/ghost/admin/app/services/dashboard-stats.js index 9aec129bcc..82414cf267 100644 --- a/ghost/admin/app/services/dashboard-stats.js +++ b/ghost/admin/app/services/dashboard-stats.js @@ -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) { diff --git a/ghost/admin/app/templates/posts/analytics.hbs b/ghost/admin/app/templates/posts/analytics.hbs index eac774993a..f31f76c889 100644 --- a/ghost/admin/app/templates/posts/analytics.hbs +++ b/ghost/admin/app/templates/posts/analytics.hbs @@ -85,6 +85,26 @@ + {{#if (feature 'sourceAttribution')}} +

+ Source attribution +

+
+
+ + +
+
+ +
+
+
+
+ {{/if}} +

Get started with analytics