From 5fcf5098a84eea5aed82a412718e6dde8d2b725a Mon Sep 17 00:00:00 2001 From: Fabien 'egg' O'Carroll Date: Thu, 22 Sep 2022 07:39:52 -0400 Subject: [PATCH] Added links API (#15446) closes https://github.com/TryGhost/Team/issues/1927 This expose the /links endpoint on the Admin API, which is filterable by Post ID. Co-authored-by: Simon Backx --- .../admin/app/controllers/posts/analytics.js | 52 +++++++++++++++++++ ghost/admin/app/templates/posts/analytics.hbs | 34 +++++------- ghost/core/core/server/api/endpoints/index.js | 4 ++ ghost/core/core/server/api/endpoints/links.js | 25 +++++++++ .../core/core/server/models/link-redirect.js | 32 ++++++++++++ .../LinkClickRepository.js | 19 ++++++- .../link-click-tracking/PostLinkRepository.js | 36 ++++++++++++- .../services/link-click-tracking/index.js | 11 +++- .../LinkRedirectRepository.js | 34 ++++++++++-- .../server/services/link-redirection/index.js | 7 +-- .../server/web/api/endpoints/admin/routes.js | 3 ++ .../lib/LinkRedirectsService.js | 1 + ghost/link-tracking/lib/FullPostLink.js | 36 +++++++++++++ .../lib/LinkClickTrackingService.js | 18 +++++++ ghost/link-tracking/lib/link-tracking.js | 3 +- 15 files changed, 281 insertions(+), 34 deletions(-) create mode 100644 ghost/core/core/server/api/endpoints/links.js create mode 100644 ghost/link-tracking/lib/FullPostLink.js diff --git a/ghost/admin/app/controllers/posts/analytics.js b/ghost/admin/app/controllers/posts/analytics.js index 90a01eb586..cbcbf14a1d 100644 --- a/ghost/admin/app/controllers/posts/analytics.js +++ b/ghost/admin/app/controllers/posts/analytics.js @@ -13,6 +13,7 @@ export default class AnalyticsController extends Controller { @service ghostPaths; @tracked sources = null; + @tracked links = null; get post() { return this.model; @@ -21,6 +22,7 @@ export default class AnalyticsController extends Controller { @action loadData() { this.fetchReferrersStats(); + this.fetchLinks(); } async fetchReferrersStats() { @@ -30,6 +32,27 @@ export default class AnalyticsController extends Controller { return this._fetchReferrersStats.perform(); } + cleanURL(url, display = false) { + // Remove our own querystring parameters and protocol + const removeParams = ['rel', 'attribution_id', 'attribution_type']; + const urlObj = new URL(url); + for (const param of removeParams) { + urlObj.searchParams.delete(param); + } + if (!display) { + return urlObj.toString(); + } + // Return URL without protocol + return urlObj.host + (urlObj.pathname === '/' ? '' : urlObj.pathname) + (urlObj.search ? '?' + urlObj.search : ''); + } + + 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}`); @@ -42,4 +65,33 @@ export default class AnalyticsController extends Controller { }; }); } + + @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.cleanURL(link.link.to, false), + title: this.cleanURL(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); + } } diff --git a/ghost/admin/app/templates/posts/analytics.hbs b/ghost/admin/app/templates/posts/analytics.hbs index 4c5a06c51e..0187c379b2 100644 --- a/ghost/admin/app/templates/posts/analytics.hbs +++ b/ghost/admin/app/templates/posts/analytics.hbs @@ -1,4 +1,4 @@ -
+
@@ -61,32 +61,24 @@
+ {{#if (and this.links this.links.length) }}

Link clicks

-
-