Added click tracking for recommendations helper (#18496)
fixes https://github.com/TryGhost/Product/issues/4001
This commit is contained in:
parent
d454f18ad2
commit
1882b7048d
@ -195,6 +195,7 @@ export default class App extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.setupRecommendationButtons();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.error(`[Portal] Failed to initialize:`, e);
|
console.error(`[Portal] Failed to initialize:`, e);
|
||||||
@ -888,6 +889,35 @@ export default class App extends React.Component {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRecommendationButtons() {
|
||||||
|
const customTriggerSelector = '[data-recommendation]';
|
||||||
|
return document.querySelectorAll(customTriggerSelector) || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Setup click tracking for recommendation buttons */
|
||||||
|
setupRecommendationButtons() {
|
||||||
|
// Handler for custom buttons
|
||||||
|
const clickHandler = (event) => {
|
||||||
|
// Send beacons for recommendation clicks
|
||||||
|
const recommendationId = event.currentTarget.dataset.recommendation;
|
||||||
|
|
||||||
|
if (recommendationId) {
|
||||||
|
this.dispatchAction('trackRecommendationClicked', {
|
||||||
|
recommendationId
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
}).catch(console.error);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.warn('[Portal] Invalid usage of data-recommendation attribute');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const elements = this.getRecommendationButtons();
|
||||||
|
for (const element of elements) {
|
||||||
|
element.addEventListener('click', clickHandler, {passive: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.initStatus === 'success') {
|
if (this.state.initStatus === 'success') {
|
||||||
return (
|
return (
|
||||||
|
@ -502,6 +502,18 @@ async function oneClickSubscribe({data: {siteUrl}, state}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function trackRecommendationClicked({data: {recommendationId}, api}) {
|
function trackRecommendationClicked({data: {recommendationId}, api}) {
|
||||||
|
try {
|
||||||
|
const existing = localStorage.getItem('ghost-recommendations-clicked');
|
||||||
|
const clicked = existing ? JSON.parse(existing) : [];
|
||||||
|
if (clicked.includes(recommendationId)) {
|
||||||
|
// Already tracked
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clicked.push(recommendationId);
|
||||||
|
localStorage.setItem('ghost-recommendations-clicked', JSON.stringify(clicked));
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore localstorage errors (browser not supported or in private mode)
|
||||||
|
}
|
||||||
api.recommendations.trackClicked({
|
api.recommendations.trackClicked({
|
||||||
recommendationId
|
recommendationId
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<ul class="recommendations">
|
<ul class="recommendations">
|
||||||
{{#each recommendations as |rec|}}
|
{{#each recommendations as |rec|}}
|
||||||
<li class="recommendation">
|
<li class="recommendation">
|
||||||
<a href="{{rec.url}}">
|
<a href="{{rec.url}}" data-recommendation="{{rec.id}}" target="_blank" rel="noopener">
|
||||||
<img class="recommendation-favicon" src="{{rec.favicon}}" alt="{{rec.title}}">
|
<img class="recommendation-favicon" src="{{rec.favicon}}" alt="{{rec.title}}">
|
||||||
<h5 class="recommendation-title">{{rec.title}}</h5>
|
<h5 class="recommendation-title">{{rec.title}}</h5>
|
||||||
<p class="recommendation-reason">{{rec.reason}}</p>
|
<p class="recommendation-reason">{{rec.reason}}</p>
|
||||||
|
@ -41,11 +41,11 @@ describe('{{#recommendations}} helper', function () {
|
|||||||
sinon.stub(api, 'recommendationsPublic').get(() => {
|
sinon.stub(api, 'recommendationsPublic').get(() => {
|
||||||
return {
|
return {
|
||||||
browse: sinon.stub().resolves({recommendations: [
|
browse: sinon.stub().resolves({recommendations: [
|
||||||
{title: 'Recommendation 1', url: 'https://recommendations1.com', favicon: 'https://recommendations1.com/favicon.ico', reason: 'Reason 1'},
|
{id: '1', title: 'Recommendation 1', url: 'https://recommendations1.com', favicon: 'https://recommendations1.com/favicon.ico', reason: 'Reason 1'},
|
||||||
{title: 'Recommendation 2', url: 'https://recommendations2.com', favicon: 'https://recommendations2.com/favicon.ico', reason: 'Reason 2'},
|
{id: '2', title: 'Recommendation 2', url: 'https://recommendations2.com', favicon: 'https://recommendations2.com/favicon.ico', reason: 'Reason 2'},
|
||||||
{title: 'Recommendation 3', url: 'https://recommendations3.com', favicon: 'https://recommendations3.com/favicon.ico', reason: 'Reason 3'},
|
{id: '3', title: 'Recommendation 3', url: 'https://recommendations3.com', favicon: 'https://recommendations3.com/favicon.ico', reason: 'Reason 3'},
|
||||||
{title: 'Recommendation 4', url: 'https://recommendations4.com', favicon: 'https://recommendations4.com/favicon.ico', reason: 'Reason 4'},
|
{id: '4', title: 'Recommendation 4', url: 'https://recommendations4.com', favicon: 'https://recommendations4.com/favicon.ico', reason: 'Reason 4'},
|
||||||
{title: 'Recommendation 5', url: 'https://recommendations5.com', favicon: 'https://recommendations5.com/favicon.ico', reason: 'Reason 5'}
|
{id: '5', title: 'Recommendation 5', url: 'https://recommendations5.com', favicon: 'https://recommendations5.com/favicon.ico', reason: 'Reason 5'}
|
||||||
], meta: meta})
|
], meta: meta})
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -71,35 +71,35 @@ describe('{{#recommendations}} helper', function () {
|
|||||||
const expected = trimSpaces(html`
|
const expected = trimSpaces(html`
|
||||||
<ul class="recommendations">
|
<ul class="recommendations">
|
||||||
<li class="recommendation">
|
<li class="recommendation">
|
||||||
<a href="https://recommendations1.com">
|
<a href="https://recommendations1.com" data-recommendation="1" target="_blank" rel="noopener">
|
||||||
<img class="recommendation-favicon" src="https://recommendations1.com/favicon.ico" alt="Recommendation 1">
|
<img class="recommendation-favicon" src="https://recommendations1.com/favicon.ico" alt="Recommendation 1">
|
||||||
<h5 class="recommendation-title">Recommendation 1</h5>
|
<h5 class="recommendation-title">Recommendation 1</h5>
|
||||||
<p class="recommendation-reason">Reason 1</p>
|
<p class="recommendation-reason">Reason 1</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="recommendation">
|
<li class="recommendation">
|
||||||
<a href="https://recommendations2.com">
|
<a href="https://recommendations2.com" data-recommendation="2" target="_blank" rel="noopener">
|
||||||
<img class="recommendation-favicon" src="https://recommendations2.com/favicon.ico" alt="Recommendation 2">
|
<img class="recommendation-favicon" src="https://recommendations2.com/favicon.ico" alt="Recommendation 2">
|
||||||
<h5 class="recommendation-title">Recommendation 2</h5>
|
<h5 class="recommendation-title">Recommendation 2</h5>
|
||||||
<p class="recommendation-reason">Reason 2</p>
|
<p class="recommendation-reason">Reason 2</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="recommendation">
|
<li class="recommendation">
|
||||||
<a href="https://recommendations3.com">
|
<a href="https://recommendations3.com" data-recommendation="3" target="_blank" rel="noopener">
|
||||||
<img class="recommendation-favicon" src="https://recommendations3.com/favicon.ico" alt="Recommendation 3">
|
<img class="recommendation-favicon" src="https://recommendations3.com/favicon.ico" alt="Recommendation 3">
|
||||||
<h5 class="recommendation-title">Recommendation 3</h5>
|
<h5 class="recommendation-title">Recommendation 3</h5>
|
||||||
<p class="recommendation-reason">Reason 3</p>
|
<p class="recommendation-reason">Reason 3</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="recommendation">
|
<li class="recommendation">
|
||||||
<a href="https://recommendations4.com">
|
<a href="https://recommendations4.com" data-recommendation="4" target="_blank" rel="noopener">
|
||||||
<img class="recommendation-favicon" src="https://recommendations4.com/favicon.ico" alt="Recommendation 4">
|
<img class="recommendation-favicon" src="https://recommendations4.com/favicon.ico" alt="Recommendation 4">
|
||||||
<h5 class="recommendation-title">Recommendation 4</h5>
|
<h5 class="recommendation-title">Recommendation 4</h5>
|
||||||
<p class="recommendation-reason">Reason 4</p>
|
<p class="recommendation-reason">Reason 4</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="recommendation">
|
<li class="recommendation">
|
||||||
<a href="https://recommendations5.com">
|
<a href="https://recommendations5.com" data-recommendation="5" target="_blank" rel="noopener">
|
||||||
<img class="recommendation-favicon" src="https://recommendations5.com/favicon.ico" alt="Recommendation 5">
|
<img class="recommendation-favicon" src="https://recommendations5.com/favicon.ico" alt="Recommendation 5">
|
||||||
<h5 class="recommendation-title">Recommendation 5</h5>
|
<h5 class="recommendation-title">Recommendation 5</h5>
|
||||||
<p class="recommendation-reason">Reason 5</p>
|
<p class="recommendation-reason">Reason 5</p>
|
||||||
|
Loading…
Reference in New Issue
Block a user