mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-02 16:41:24 +03:00
dcd607ad94
refs https://github.com/TryGhost/Team/issues/2116 - editing a link caused it to jump in the list as its count is reset to 0 - this forces the order of links same after edit, the new order is only visible on refresh or navigating away and coming back
255 lines
7.2 KiB
JavaScript
255 lines
7.2 KiB
JavaScript
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';
|
|
@tracked showSuccess;
|
|
@tracked updateLinkId;
|
|
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) {
|
|
if (this._updateLinks.isRunning) {
|
|
return this._updateLinks.last;
|
|
}
|
|
return this._updateLinks.perform(linkId, linkTo);
|
|
}
|
|
|
|
@action
|
|
loadData() {
|
|
if (this.showSources) {
|
|
this.fetchReferrersStats();
|
|
} else {
|
|
this.sources = [];
|
|
}
|
|
|
|
if (this.showLinks) {
|
|
this.fetchLinks();
|
|
} else {
|
|
this.links = [];
|
|
}
|
|
}
|
|
|
|
updateLinkData(linksData) {
|
|
let updatedLinks;
|
|
if (this.links?.length) {
|
|
updatedLinks = this.links.map((link) => {
|
|
let linkData = linksData.find(l => l.link.link_id === link.link.link_id);
|
|
if (linkData) {
|
|
return {
|
|
...linkData,
|
|
link: {
|
|
...linkData.link,
|
|
originalTo: linkData.link.to,
|
|
to: this.utils.cleanTrackedUrl(linkData.link.to, false),
|
|
title: this.utils.cleanTrackedUrl(linkData.link.to, true)
|
|
}
|
|
};
|
|
}
|
|
return link;
|
|
});
|
|
} else {
|
|
updatedLinks = linksData.map((link) => {
|
|
return {
|
|
...link,
|
|
link: {
|
|
...link.link,
|
|
originalTo: link.link.to,
|
|
to: this.utils.cleanTrackedUrl(link.link.to, false),
|
|
title: this.utils.cleanTrackedUrl(link.link.to, true)
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
// Remove duplicates by title ad merge
|
|
const linksByTitle = updatedLinks.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);
|
|
}
|
|
|
|
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
|
|
*_updateLinks(linkId, newLink) {
|
|
this.updateLinkId = linkId;
|
|
let currentLink;
|
|
this.links = this.links?.map((link) => {
|
|
if (link.link.link_id === linkId) {
|
|
currentLink = new URL(link.link.originalTo);
|
|
return {
|
|
...link,
|
|
link: {
|
|
...link.link,
|
|
to: this.utils.cleanTrackedUrl(newLink, false),
|
|
title: this.utils.cleanTrackedUrl(newLink, true)
|
|
}
|
|
};
|
|
}
|
|
return link;
|
|
});
|
|
|
|
const filter = `post_id:${this.post.id}+to:'${currentLink}'`;
|
|
let bulkUpdateUrl = this.ghostPaths.url.api(`links/bulk`) + `?filter=${encodeURIComponent(filter)}`;
|
|
yield this.ajax.put(bulkUpdateUrl, {
|
|
data: {
|
|
bulk: {
|
|
action: 'updateLink',
|
|
meta: {link: {to: newLink}}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Refresh links data
|
|
const linksFilter = `post_id:${this.post.id}`;
|
|
let statsUrl = this.ghostPaths.url.api(`links/`) + `?filter=${encodeURIComponent(linksFilter)}`;
|
|
let result = yield this.ajax.request(statsUrl);
|
|
this.updateLinkData(result.links);
|
|
this.showSuccess = this.updateLinkId;
|
|
setTimeout(() => {
|
|
this.showSuccess = null;
|
|
}, 2000);
|
|
}
|
|
|
|
@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);
|
|
this.updateLinkData(result.links);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|