From d4540012dc999a20dc3522ccc7b4bec16ad8a362 Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Thu, 6 Oct 2022 11:43:39 +0200 Subject: [PATCH] Added tests for click events in the activity feed fixes https://github.com/TryGhost/Team/issues/2018 - Includes new test fixtures for redirects and click events - Tests if post, and links are returned in the click events --- .../services/mega/post-email-serializer.js | 8 +- .../admin/__snapshots__/members.test.js.snap | 203 ++++++++++++++++++ ghost/core/test/e2e-api/admin/members.test.js | 32 ++- ghost/core/test/utils/fixture-utils.js | 18 ++ .../test/utils/fixtures/data-generator.js | 24 ++- 5 files changed, 281 insertions(+), 4 deletions(-) diff --git a/ghost/core/core/server/services/mega/post-email-serializer.js b/ghost/core/core/server/services/mega/post-email-serializer.js index 87d1d3ad52..dbe031d636 100644 --- a/ghost/core/core/server/services/mega/post-email-serializer.js +++ b/ghost/core/core/server/services/mega/post-email-serializer.js @@ -358,13 +358,17 @@ const PostEmailSerializer = { // Now replace the links in the HTML version if (!options.isBrowserPreview && !options.isTestEmail && settingsCache.get('email_track_clicks')) { result.html = await linkReplacer.replace(result.html, async (url) => { - // Add newsletter source attribution - url = memberAttribution.service.addEmailSourceAttributionTracking(url, newsletter); const isSite = urlUtils.isSiteUrl(url); if (isSite) { + url = memberAttribution.service.addEmailSourceAttributionTracking(url, newsletter); + // Only add post attribution to our own site (because external sites could/should not process this information) url = memberAttribution.service.addPostAttributionTracking(url, post); + } else { + // For external sites, add ref to the site URL instead of newsletter + const siteUrl = new URL(urlUtils.urlFor('home', true)); + url.searchParams.append('ref', siteUrl.hostname); } // Add link click tracking diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap index 04e54e6660..ae0d5c161b 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/members.test.js.snap @@ -4042,6 +4042,209 @@ Object { } `; +exports[`Members API Returns click events in activity feed 1: [body] 1`] = ` +Object { + "events": Array [ + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/0", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "member1@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Mr Egg", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "HTML Ipsum", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/1", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "member2@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": null, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "Ghostly Kitchen Sink", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/2", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "paid@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Egon Spengler", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "Short and Sweet", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/3", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "trialing@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Ray Stantz", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "Not finished yet", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/4", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "comped@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Vinz Clortho", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "Not so short, bit complex", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/5", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "vip@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Winston Zeddemore", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "This is a static page", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/6", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "vip-paid@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Peter Venkman", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "This is a draft static page", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + Object { + "data": Object { + "created_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}/, + "link": Object { + "from": "/r/7", + "to": "https:://ghost.org", + }, + "member": Object { + "avatar_image": null, + "email": "with-product@test.com", + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "name": "Dana Barrett", + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + "post": Object { + "id": StringMatching /\\[a-f0-9\\]\\{24\\}/, + "title": "This is a scheduled post!!", + "url": Any, + "uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/, + }, + }, + "type": Any, + }, + ], +} +`; + +exports[`Members API Returns click events in activity feed 2: [headers] 1`] = ` +Object { + "access-control-allow-origin": "http://127.0.0.1:2369", + "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", + "content-length": "3631", + "content-type": "application/json; charset=utf-8", + "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, + "vary": "Accept-Version, Origin, Accept-Encoding", + "x-powered-by": "Express", +} +`; + exports[`Members API Returns comments in activity feed 1: [body] 1`] = ` Object { "events": Array [ diff --git a/ghost/core/test/e2e-api/admin/members.test.js b/ghost/core/test/e2e-api/admin/members.test.js index 2fe545337d..c295838765 100644 --- a/ghost/core/test/e2e-api/admin/members.test.js +++ b/ghost/core/test/e2e-api/admin/members.test.js @@ -416,7 +416,7 @@ describe('Members API', function () { before(async function () { agent = await agentProvider.getAdminAPIAgent(); - await fixtureManager.init('posts', 'newsletters', 'members:newsletters', 'comments'); + await fixtureManager.init('posts', 'newsletters', 'members:newsletters', 'comments', 'redirects', 'clicks'); await agent.loginAsOwner(); newsletters = await getNewsletters(); @@ -451,6 +451,36 @@ describe('Members API', function () { }); }); + it('Returns click events in activity feed', async function () { + // Check activity feed + await agent + .get(`/members/events?filter=type:click_event`) + .expectStatus(200) + .matchHeaderSnapshot({ + etag: anyEtag + }) + .matchBodySnapshot({ + events: new Array(8).fill({ + type: anyString, + data: { + created_at: anyISODate, + member: { + id: anyObjectId, + uuid: anyUuid + }, + post: { + id: anyObjectId, + uuid: anyUuid, + url: anyString + } + } + }) + }) + .expect(({body}) => { + should(body.events.find(e => e.type === 'click_event')).not.be.undefined(); + }); + }); + // List Members it('Can browse', async function () { diff --git a/ghost/core/test/utils/fixture-utils.js b/ghost/core/test/utils/fixture-utils.js index 331776270e..4ad56d72c3 100644 --- a/ghost/core/test/utils/fixture-utils.js +++ b/ghost/core/test/utils/fixture-utils.js @@ -643,6 +643,18 @@ const fixtures = { })); }, + insertRedirects: async function insertClicks() { + await Promise.all(DataGenerator.forKnex.redirects.map((click) => { + return models.Redirect.add(click, context.internal); + })); + }, + + insertClicks: async function insertClicks() { + await Promise.all(DataGenerator.forKnex.members_click_events.map((click) => { + return models.MemberClickEvent.add(click, context.internal); + })); + }, + insertSnippets: function insertSnippets() { return Promise.map(DataGenerator.forKnex.snippets, function (snippet) { return models.Snippet.add(snippet, context.internal); @@ -776,6 +788,12 @@ const toDoList = { }, comments: function insertComments() { return fixtures.insertComments(); + }, + redirects: function insertRedirects() { + return fixtures.insertRedirects(); + }, + clicks: function insertClicks() { + return fixtures.insertClicks(); } }; diff --git a/ghost/core/test/utils/fixtures/data-generator.js b/ghost/core/test/utils/fixtures/data-generator.js index 7e2aaaff77..72ccb7977e 100644 --- a/ghost/core/test/utils/fixtures/data-generator.js +++ b/ghost/core/test/utils/fixtures/data-generator.js @@ -1656,6 +1656,26 @@ DataGenerator.forKnex = (function () { createBasic(DataGenerator.Content.members_paid_subscription_events[2]) ]; + const redirects = posts.map((post, index) => { + return { + id: ObjectId().toHexString(), + from: '/r/' + index, + to: 'https:://ghost.org', + post_id: post.id, + created_at: new Date(), + updated_at: new Date() + }; + }); + + const members_click_events = redirects.map((redirect, index) => { + return { + id: ObjectId().toHexString(), + member_id: members[index].id, + redirect_id: redirect.id, + created_at: new Date() + }; + }); + const snippets = [ createBasic(DataGenerator.Content.snippets[0]) ]; @@ -1730,10 +1750,12 @@ DataGenerator.forKnex = (function () { snippets, custom_theme_settings, comments, + redirects, members_paid_subscription_events, members_created_events, - members_subscription_created_events + members_subscription_created_events, + members_click_events }; }());