Skip to content

Commit

Permalink
fix: adding referrer metrics to high-organic-low-ctr opportunities
Browse files Browse the repository at this point in the history
  • Loading branch information
rpapani committed Sep 14, 2024
1 parent 682c671 commit 56db7e7
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 141 deletions.
14 changes: 10 additions & 4 deletions packages/spacecat-shared-rum-api-client/src/common/aggregateFns.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import { extractTrafficHints, classifyUTMSource } from './traffic.js';
import { extractTrafficHints, classifyReferrer, getSecondLevelDomain } from './traffic.js';

/**
* Calculates the total page views by URL from an array of bundles.
Expand Down Expand Up @@ -55,7 +55,7 @@ function getCTRByUrl(bundles) {
}

/**
* Calculates the Click-Through Rate (CTR) by URL and Referrer obtained from utm_source.
* Calculates the Click-Through Rate (CTR) by URL and Referrer.
* CTR is defined as the total number of sessions with at least one click event per referrer.
* divided by the total number of pageviews for each URL per referrer.
*
Expand All @@ -67,7 +67,8 @@ function getCTRByUrlAndChannel(bundles) {
const aggregated = bundles.reduce((acc, bundle) => {
const { url } = bundle;
const trafficHints = extractTrafficHints(bundle);
const channel = classifyUTMSource(trafficHints.utmSource);
const referrerDomain = getSecondLevelDomain(trafficHints.referrer);
const channel = classifyReferrer(referrerDomain);
if (!acc[url]) {
acc[url] = { sessionsWithClick: 0, totalPageviews: 0, channels: {} };
}
Expand All @@ -90,9 +91,14 @@ function getCTRByUrlAndChannel(bundles) {
}, {});
return Object.entries(aggregated)
.reduce((acc, [url, { sessionsWithClick, totalPageviews, channels }]) => {
if (!acc[url]) {
acc[url] = { value: 0, channels: {} };
}
acc[url].value = (sessionsWithClick / totalPageviews);
acc[url].channels = Object.entries(channels)
.reduce((_acc, [source, { _sessionsWithClick, _totalPageviews }]) => {
.reduce((_acc, [source, {
sessionsWithClick: _sessionsWithClick, totalPageviews: _totalPageviews,
}]) => {
// eslint-disable-next-line no-param-reassign
_acc[source] = (_sessionsWithClick / _totalPageviews);
return _acc;
Expand Down
43 changes: 19 additions & 24 deletions packages/spacecat-shared-rum-api-client/src/common/traffic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import URI from 'urijs';
* @returns {string} The second-level domain of the given URL, or the original
* URL if it does not contain any text.
*/
function getSecondLevelDomain(url) {
export function getSecondLevelDomain(url) {
if (!hasText(url)) return url;
const uri = new URI(url);
const domain = uri.domain();
Expand All @@ -39,7 +39,7 @@ function getSecondLevelDomain(url) {
// Referrer related
const referrers = {
search: /google|yahoo|bing|yandex|baidu|duckduckgo|brave|ecosia|aol|startpage|ask/,
social: /^\b(x)\b|(.*(facebook|tiktok|snapchat|x|twitter|pinterest|reddit|linkedin|threads|quora|discord|tumblr|mastodon|bluesky|instagram).*)$/,
social: /^\b(x)\b|(.*(facebook|tiktok|snapchat|twitter|pinterest|reddit|linkedin|threads|quora|discord|tumblr|mastodon|bluesky|instagram).*)$/,
ad: /googlesyndication|2mdn|doubleclick|syndicatedsearch/,
video: /youtube|vimeo|twitch|dailymotion|wistia/,
};
Expand Down Expand Up @@ -68,14 +68,12 @@ const sources = {
email: /sfmc|email/,
};

// Indexes of the mathcing groups in the regexes above, to obtain the utm source string
const sourceGroupingIndex = {
// Indexes of the mathcing groups for the regexes in referrer
const referrerGroupingIndex = {
search: [0],
social: [1, 3],
search: [1, 3],
ad: [0],
video: [0],
display: [0],
affiliate: [0],
email: [0],
};

// Tracking params - based on the checkpoints we have in rum-enhancer now
Expand Down Expand Up @@ -189,25 +187,22 @@ export function extractTrafficHints(bundle) {
}

/**
* Returns the name of the utm source as single word, for example: facebook instead of facebook.com
* @param {*} utmSource
* Returns the name of the referrer as single word.
* For example: facebook instead of www.facebook.com
* @param {*} referrerString
*/
export function classifyUTMSource(utmSource) {
if (!utmSource) return '';
let classifiedSource = '';
for (const [source, regex] of Object.entries(sources)) {
const match = utmSource.match(regex);
export function classifyReferrer(referrerString) {
if (!referrerString) return '';
let classifiedReferrer = '';
for (const [referrer, regex] of Object.entries(referrers)) {
const match = referrerString.match(regex);
if (match) {
const indexes = sourceGroupingIndex[source];
const classifiedSourceIndex = indexes.find((index) => match[index]);
if (classifiedSourceIndex === undefined) {
classifiedSource = '';
} else {
classifiedSource = match[classifiedSourceIndex];
}
const indexes = referrerGroupingIndex[referrer];
const classifiedReferrerIndex = indexes.find((index) => match[index]);
classifiedReferrer = match[classifiedReferrerIndex];
}
}
return classifiedSource;
return classifiedReferrer;
}

export function classifyTrafficSource(url, referrer, utmSource, utmMedium, trackingParams) {
Expand All @@ -224,7 +219,7 @@ export function classifyTrafficSource(url, referrer, utmSource, utmMedium, track
&& rule.utmMedium(sanitize(utmMedium))
&& rule.tracking(trackingParams)
));
const channel = classifyUTMSource(utmSource);
const channel = classifyReferrer(referrerDomain);

return {
type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ function convertToOpportunity(traffic) {
url, total, ctr, paid, owned, earned, channels, siteAvgCTR, ctrByUrlAndChannel,
} = traffic;

console.log('channels', channels);

const topChannels = Object.entries(channels)
.sort((a, b) => b[1].total - a[1].total).slice(0, CHANNELS_TO_CONSIDER);

console.log('topChannels', topChannels);
const opportunity = {
type: 'high-organic-low-ctr',
page: url,
Expand Down Expand Up @@ -53,18 +55,19 @@ function convertToOpportunity(traffic) {
}],
};
opportunity.metrics.push(...topChannels.map(([channel, {
_total, _owned, _earned, _paid,
total: _total, owned: _owned, earned: _earned, paid: _paid,
}]) => {
const trafficMetrics = {
type: 'traffic',
referrer: channel,
value: {
_total,
_owned,
_earned,
_paid,
total: _total,
owned: _owned,
earned: _earned,
paid: _paid,
},
};
console.log('trafficMetrics', trafficMetrics);
const ctrMetrics = {
type: 'ctr',
referrer: channel,
Expand All @@ -90,7 +93,9 @@ function handler(bundles, opts = {}) {
const { interval = 7 } = opts;

const trafficByUrl = trafficAcquisition.handler(bundles);
console.log('trafficByUrl', trafficByUrl);
const ctrByUrlAndChannel = getCTRByUrlAndChannel(bundles);
console.log('ctrByUrlAndChannel', ctrByUrlAndChannel);
const siteAvgCTR = getSiteAvgCTR(bundles);

return trafficByUrl.filter((traffic) => traffic.total > interval * DAILY_PAGEVIEW_THRESHOLD)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ function handler(bundles) {
})
.reduce(collectByUrlAndTrafficSource, {});

console.log('trafficSources before transform', trafficSources);

return transformFormat(trafficSources)
.sort((a, b) => b.total - a.total); // sort desc by total views
}
Expand Down
70 changes: 35 additions & 35 deletions packages/spacecat-shared-rum-api-client/test/common/traffic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,54 +27,54 @@ describe('Traffic classification', () => {
it('paid search', () => {
const expected = { type: 'paid', category: 'search', channel: '' };

assert(expected, { referrer: 'https://www.bing.com/', utmSource: 'some-source', utmMedium: 'paidsearch', tracking: null });
assert(expected, { referrer: 'https://www.google.co.uk/', utmSource: 'some-source', utmMedium: 'sea', tracking: null });
assert(expected, { referrer: 'https://yahoo.com/', utmSource: 'some-source', utmMedium: 'paidsearch', tracking: null });
assert(expected, { referrer: 'https://google.com/', utmSource: 'some-source', utmMedium: 'paidsearch', tracking: 'paid' });
assert({ ...expected, channel: 'goo' }, { referrer: 'https://googleads.g.doubleclick.net/', utmSource: 'goo', utmMedium: 'gsea', tracking: null });
assert({ ...expected, channel: 'goo' }, { referrer: '', utmSource: 'goo', utmMedium: 'sem', tracking: null });
assert({ ...expected, channel: 'bing' }, { referrer: 'https://www.bing.com/', utmSource: 'some-source', utmMedium: 'paidsearch', tracking: null });
assert({ ...expected, channel: 'google' }, { referrer: 'https://www.google.co.uk/', utmSource: 'some-source', utmMedium: 'sea', tracking: null });
assert({ ...expected, channel: 'yahoo' }, { referrer: 'https://yahoo.com/', utmSource: 'some-source', utmMedium: 'paidsearch', tracking: null });
assert({ ...expected, channel: 'google' }, { referrer: 'https://google.com/', utmSource: 'some-source', utmMedium: 'paidsearch', tracking: 'paid' });
assert({ ...expected, channel: 'doubleclick' }, { referrer: 'https://googleads.g.doubleclick.net/', utmSource: 'goo', utmMedium: 'gsea', tracking: null });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'goo', utmMedium: 'sem', tracking: null });
assert({ ...expected, channel: 'google' }, { referrer: 'https://www.google.com/', utmSource: 'googlemaps', utmMedium: 'seomaps', tracking: null });
assert({ ...expected, channel: 'sea' }, { referrer: '', utmSource: 'gsea', utmMedium: 'sea', tracking: null });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'gsea', utmMedium: 'sea', tracking: null });
});

it('paid social', () => {
const expected = { type: 'paid', category: 'social', channel: '' };

assert(expected, { referrer: 'https://www.facebook.com/', utmSource: 'some-source', utmMedium: 'facebook', tracking: null });
assert(expected, { referrer: 'https://www.tiktok.com/', utmSource: 'some-source', utmMedium: 'paidsocial', tracking: null });
assert(expected, { referrer: 'https://snapchat.com/', utmSource: 'some-source', utmMedium: 'social', tracking: null });
assert(expected, { referrer: 'https://x.com/', utmSource: 'some-source', utmMedium: '', tracking: 'paid' });
assert({ ...expected, channel: 'meta' }, { referrer: '', utmSource: 'meta', utmMedium: 'paidsocial', tracking: null });
assert(expected, { referrer: 'https://www.tiktok.com/', utmSource: 'tt', utmMedium: 'soci', tracking: null });
assert({ ...expected, channel: 'reddit' }, { referrer: '', utmSource: 'reddit', utmMedium: 'social', tracking: null });
assert({ ...expected, channel: 'soc' }, { referrer: '', utmSource: 'soc', utmMedium: 'fbig', tracking: null });
assert({ ...expected, channel: 'instagram' }, { referrer: '', utmSource: 'instagram', utmMedium: 'social', tracking: null });
assert({ ...expected, channel: 'facebook' }, { referrer: 'https://www.facebook.com/', utmSource: 'some-source', utmMedium: 'facebook', tracking: null });
assert({ ...expected, channel: 'tiktok' }, { referrer: 'https://www.tiktok.com/', utmSource: 'some-source', utmMedium: 'paidsocial', tracking: null });
assert({ ...expected, channel: 'snapchat' }, { referrer: 'https://snapchat.com/', utmSource: 'some-source', utmMedium: 'social', tracking: null });
assert({ ...expected, channel: 'x' }, { referrer: 'https://x.com/', utmSource: 'some-source', utmMedium: '', tracking: 'paid' });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'meta', utmMedium: 'paidsocial', tracking: null });
assert({ ...expected, channel: 'tiktok' }, { referrer: 'https://www.tiktok.com/', utmSource: 'tt', utmMedium: 'soci', tracking: null });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'reddit', utmMedium: 'social', tracking: null });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'soc', utmMedium: 'fbig', tracking: null });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'instagram', utmMedium: 'social', tracking: null });
});

it('paid video', () => {
const expected = { type: 'paid', category: 'video', channel: '' };

assert(expected, { referrer: 'https://www.youtube.com/', utmSource: 'some-source', utmMedium: 'cpc', tracking: null });
assert(expected, { referrer: 'https://www.youtube.com/', utmSource: 'some-source', utmMedium: 'ppc', tracking: null });
assert(expected, { referrer: 'https://www.dailymotion.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'paid' });
assert(expected, { referrer: 'https://www.twitch.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'paid' });
assert({ ...expected, channel: 'youtube' }, { referrer: '', utmSource: 'youtube', utmMedium: 'video', tracking: null });
assert({ ...expected, channel: 'youtube' }, { referrer: 'https://www.youtube.com/', utmSource: 'some-source', utmMedium: 'cpc', tracking: null });
assert({ ...expected, channel: 'youtube' }, { referrer: 'https://www.youtube.com/', utmSource: 'some-source', utmMedium: 'ppc', tracking: null });
assert({ ...expected, channel: 'dailymotion' }, { referrer: 'https://www.dailymotion.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'paid' });
assert({ ...expected, channel: 'twitch' }, { referrer: 'https://www.twitch.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'paid' });
assert({ ...expected, channel: '' }, { referrer: '', utmSource: 'youtube', utmMedium: 'video', tracking: null });
});

it('paid display', () => {
const expected = { type: 'paid', category: 'display', channel: '' };

assert(expected, { referrer: 'not-empty', utmSource: 'some-source', utmMedium: 'cpc', tracking: null });
assert(expected, { referrer: 'https://hebele.hebele.googlesyndication.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: null });
assert({ ...expected, channel: 'gdn' }, { referrer: 'not-empty', utmSource: 'gdn', utmMedium: 'some-medium', tracking: null });
assert({ ...expected, channel: 'googlesyndication' }, { referrer: 'https://hebele.hebele.googlesyndication.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: null });
assert(expected, { referrer: 'not-empty', utmSource: 'gdn', utmMedium: 'some-medium', tracking: null });
assert(expected, { referrer: 'not-empty', utmSource: 'some-source', utmMedium: 'pp', tracking: null });
assert(expected, { referrer: 'not-empty', utmSource: 'some-source', utmMedium: 'display', tracking: null });
assert({ ...expected, channel: 'dv360' }, { referrer: '', utmSource: 'dv360', utmMedium: 'some-medium', tracking: null });
assert(expected, { referrer: '', utmSource: 'dv360', utmMedium: 'some-medium', tracking: null });
assert(expected, { referrer: '', utmSource: 'some-source', utmMedium: 'cpc', tracking: null });
assert(expected, { referrer: 'some-referrer', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'paid' });
assert(expected, { referrer: '', utmSource: '', utmMedium: '', tracking: 'paid' });
assert({ ...expected, channel: 'newsshowcase' }, { referrer: 'https://www.google.com/', utmSource: 'newsshowcase', utmMedium: 'discover', tracking: null });
assert(expected, { referrer: 'https://googleads.g.doubleclick.net/', utmSource: 'some', utmMedium: 'some', tracking: null });
assert({ ...expected, channel: 'google' }, { referrer: 'https://www.google.com/', utmSource: 'newsshowcase', utmMedium: 'discover', tracking: null });
assert({ ...expected, channel: 'doubleclick' }, { referrer: 'https://googleads.g.doubleclick.net/', utmSource: 'some', utmMedium: 'some', tracking: null });
assert({ ...expected, channel: 'google' }, { referrer: 'https://www.google.com/', utmSource: 'google', utmMedium: 'businesslistings', tracking: null });
});

Expand All @@ -87,27 +87,27 @@ describe('Traffic classification', () => {
it('earned search', () => {
const expected = { type: 'earned', category: 'search', channel: '' };

assert(expected, { referrer: 'https://www.bing.com/', utmSource: '', utmMedium: '', tracking: null });
assert(expected, { referrer: 'https://www.google.co.uk/', utmSource: '', utmMedium: '', tracking: null });
assert(expected, { referrer: 'https://yahoo.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'some' });
assert(expected, { referrer: 'https://google.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'some' });
assert({ ...expected, channel: 'bing' }, { referrer: 'https://www.bing.com/', utmSource: '', utmMedium: '', tracking: null });
assert({ ...expected, channel: 'google' }, { referrer: 'https://www.google.co.uk/', utmSource: '', utmMedium: '', tracking: null });
assert({ ...expected, channel: 'yahoo' }, { referrer: 'https://yahoo.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'some' });
assert({ ...expected, channel: 'google' }, { referrer: 'https://google.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: 'some' });
assert({ ...expected, channel: 'google' }, { referrer: 'https://www.google.com/', utmSource: 'google', utmMedium: 'organicgmb', tracking: null });
});

it('earned social', () => {
const expected = { type: 'earned', category: 'social', channel: '' };

assert(expected, { referrer: 'https://www.facebook.com/', utmSource: '', utmMedium: '', tracking: null });
assert(expected, { referrer: 'https://www.tiktok.com/', utmSource: '', utmMedium: '', tracking: null });
assert({ ...expected, channel: 'facebook' }, { referrer: 'https://www.facebook.com/', utmSource: '', utmMedium: '', tracking: null });
assert({ ...expected, channel: 'tiktok' }, { referrer: 'https://www.tiktok.com/', utmSource: '', utmMedium: '', tracking: null });
assert(expected, { referrer: 'https://some-site.com/', utmSource: 'some-source', utmMedium: 'organicsocial', tracking: null });
});

it('earned video', () => {
const expected = { type: 'earned', category: 'video', channel: '' };

assert(expected, { referrer: 'https://www.youtube.com/', utmSource: '', utmMedium: '', tracking: null });
assert(expected, { referrer: 'https://www.youtube.com/', utmSource: '', utmMedium: '', tracking: null });
assert(expected, { referrer: 'https://www.dailymotion.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: null });
assert({ ...expected, channel: 'youtube' }, { referrer: 'https://www.youtube.com/', utmSource: '', utmMedium: '', tracking: null });
assert({ ...expected, channel: 'youtube' }, { referrer: 'https://www.youtube.com/', utmSource: '', utmMedium: '', tracking: null });
assert({ ...expected, channel: 'dailymotion' }, { referrer: 'https://www.dailymotion.com/', utmSource: 'some-source', utmMedium: 'some-medium', tracking: null });
});

it('earned referral', () => {
Expand Down
Loading

0 comments on commit 56db7e7

Please sign in to comment.