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) {
|
||||
/* eslint-disable no-console */
|
||||
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() {
|
||||
if (this.state.initStatus === 'success') {
|
||||
return (
|
||||
|
@ -502,6 +502,18 @@ async function oneClickSubscribe({data: {siteUrl}, state}) {
|
||||
}
|
||||
|
||||
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({
|
||||
recommendationId
|
||||
});
|
||||
|
@ -2,7 +2,7 @@
|
||||
<ul class="recommendations">
|
||||
{{#each recommendations as |rec|}}
|
||||
<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}}">
|
||||
<h5 class="recommendation-title">{{rec.title}}</h5>
|
||||
<p class="recommendation-reason">{{rec.reason}}</p>
|
||||
|
@ -41,11 +41,11 @@ describe('{{#recommendations}} helper', function () {
|
||||
sinon.stub(api, 'recommendationsPublic').get(() => {
|
||||
return {
|
||||
browse: sinon.stub().resolves({recommendations: [
|
||||
{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'},
|
||||
{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'},
|
||||
{title: 'Recommendation 5', url: 'https://recommendations5.com', favicon: 'https://recommendations5.com/favicon.ico', reason: 'Reason 5'}
|
||||
{id: '1', title: 'Recommendation 1', url: 'https://recommendations1.com', favicon: 'https://recommendations1.com/favicon.ico', reason: 'Reason 1'},
|
||||
{id: '2', title: 'Recommendation 2', url: 'https://recommendations2.com', favicon: 'https://recommendations2.com/favicon.ico', reason: 'Reason 2'},
|
||||
{id: '3', title: 'Recommendation 3', url: 'https://recommendations3.com', favicon: 'https://recommendations3.com/favicon.ico', reason: 'Reason 3'},
|
||||
{id: '4', title: 'Recommendation 4', url: 'https://recommendations4.com', favicon: 'https://recommendations4.com/favicon.ico', reason: 'Reason 4'},
|
||||
{id: '5', title: 'Recommendation 5', url: 'https://recommendations5.com', favicon: 'https://recommendations5.com/favicon.ico', reason: 'Reason 5'}
|
||||
], meta: meta})
|
||||
};
|
||||
});
|
||||
@ -71,35 +71,35 @@ describe('{{#recommendations}} helper', function () {
|
||||
const expected = trimSpaces(html`
|
||||
<ul class="recommendations">
|
||||
<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">
|
||||
<h5 class="recommendation-title">Recommendation 1</h5>
|
||||
<p class="recommendation-reason">Reason 1</p>
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<h5 class="recommendation-title">Recommendation 2</h5>
|
||||
<p class="recommendation-reason">Reason 2</p>
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<h5 class="recommendation-title">Recommendation 3</h5>
|
||||
<p class="recommendation-reason">Reason 3</p>
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<h5 class="recommendation-title">Recommendation 4</h5>
|
||||
<p class="recommendation-reason">Reason 4</p>
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<h5 class="recommendation-title">Recommendation 5</h5>
|
||||
<p class="recommendation-reason">Reason 5</p>
|
||||
|
Loading…
Reference in New Issue
Block a user