import Component from '@glimmer/component'; 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 */ const DISPLAY_OPTIONS = [{ name: 'Free signups', value: 'signups' }, { name: 'Paid conversions', value: 'paid' }]; export default class Analytics extends Component { @service ajax; @service ghostPaths; @service settings; @service membersUtils; @service utils; @service feature; @tracked sources = null; @tracked links = null; @tracked sortColumn = 'signups'; displayOptions = DISPLAY_OPTIONS; get post() { return this.args.post; } get allowedDisplayOptions() { if (!this.hasPaidConversionData) { return this.displayOptions.filter(d => d.value === 'signups'); } if (!this.hasFreeSignups) { return this.displayOptions.filter(d => d.value === 'paid'); } return this.displayOptions; } get isDropdownDisabled() { if (!this.hasPaidConversionData || !this.hasFreeSignups) { return true; } return false; } get selectedDisplayOption() { if (!this.hasPaidConversionData) { return this.displayOptions.find(d => d.value === 'signups'); } if (!this.hasFreeSignups) { return this.displayOptions.find(d => d.value === 'paid'); } return this.displayOptions.find(d => d.value === this.sortColumn) ?? this.displayOptions[0]; } get selectedSortColumn() { if (!this.hasPaidConversionData) { return 'signups'; } if (!this.hasFreeSignups) { return 'paid'; } return this.sortColumn; } get hasPaidConversionData() { return this.sources.some(sourceData => sourceData.paidConversions > 0); } get hasFreeSignups() { return this.sources.some(sourceData => sourceData.signups > 0); } @action onDisplayChange(selected) { this.sortColumn = selected.value; } @action setSortColumn(column) { this.sortColumn = column; } @action updateLink(linkId, linkTo) { this.links = this.links?.map((link) => { if (link.link.link_id === linkId) { return { ...link, link: { ...link.link, to: this.utils.cleanTrackedUrl(linkTo, false), title: this.utils.cleanTrackedUrl(linkTo, true) } }; } return link; }); } @action loadData() { if (this.showSources) { this.fetchReferrersStats(); } else { this.sources = []; } if (this.showLinks) { this.fetchLinks(); } else { this.links = []; } } async fetchReferrersStats() { if (this._fetchReferrersStats.isRunning) { return this._fetchReferrersStats.last; } return this._fetchReferrersStats.perform(); } async fetchLinks() { if (this._fetchLinks.isRunning) { return this._fetchLinks.last; } return this._fetchLinks.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 }; }); } @task *_fetchLinks() { const filter = `post_id:${this.post.id}`; let statsUrl = this.ghostPaths.url.api(`links/`) + `?filter=${encodeURIComponent(filter)}`; let result = yield this.ajax.request(statsUrl); const links = result.links.map((link) => { return { ...link, link: { ...link.link, to: this.utils.cleanTrackedUrl(link.link.to, false), title: this.utils.cleanTrackedUrl(link.link.to, true) } }; }); // Remove duplicates by title ad merge const linksByTitle = links.reduce((acc, link) => { if (!acc[link.link.title]) { acc[link.link.title] = link; } else { acc[link.link.title].clicks += link.clicks; } return acc; }, {}); this.links = Object.values(linksByTitle); } get showLinks() { return this.post.showEmailClickAnalytics; } get showSources() { return this.feature.get('sourceAttribution') && this.post.showAttributionAnalytics; } get isLoaded() { return this.links !== null && this.souces !== null; } }