Skip to content

Commit 084224c

Browse files
Add link tracking
1 parent 8511232 commit 084224c

File tree

5 files changed

+71
-5
lines changed

5 files changed

+71
-5
lines changed

src/core/utils.js

+11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { loadEntities } from 'core/actions';
55
import { fetchAddon } from 'core/api';
66
import log from 'core/logger';
77
import purify from 'core/purify';
8+
import {
9+
EXTENSION_TYPE,
10+
THEME_TYPE,
11+
} from 'core/constants';
812

913
export function gettext(str) {
1014
return str;
@@ -102,3 +106,10 @@ export function loadAddonIfNeeded(
102106
return fetchAddon({ slug, api: state.api })
103107
.then(({ entities }) => dispatch(loadEntities(entities)));
104108
}
109+
110+
export function getAction(type) {
111+
return {
112+
[EXTENSION_TYPE]: 'addon',
113+
[THEME_TYPE]: 'theme',
114+
}[type] || 'invalid';
115+
}

src/disco/components/Addon.js

+19-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
55
import { connect } from 'react-redux';
66
import config from 'config';
77

8+
import { getAction, sanitizeHTML } from 'core/utils';
89
import translate from 'core/i18n/translate';
9-
import { sanitizeHTML } from 'core/utils';
1010
import themeAction, { getThemeData } from 'disco/themePreview';
1111
import tracking from 'core/tracking';
1212
import * as addonManager from 'disco/addonManager';
@@ -36,6 +36,7 @@ import {
3636
validInstallStates,
3737
} from 'core/constants';
3838
import {
39+
CLICK_CATEGORY,
3940
CLOSE_INFO,
4041
INSTALL_CATEGORY,
4142
SET_ENABLE_NOT_AVAILABLE,
@@ -68,12 +69,14 @@ export class AddonBase extends React.Component {
6869
textcolor: PropTypes.string,
6970
themeAction: PropTypes.func,
7071
type: PropTypes.oneOf(validAddonTypes).isRequired,
72+
_tracking: PropTypes.object,
7173
}
7274

7375
static defaultProps = {
7476
// Defaults themeAction to the imported func.
7577
themeAction,
7678
needsRestart: false,
79+
_tracking: tracking,
7780
}
7881

7982
componentDidMount() {
@@ -89,6 +92,7 @@ export class AddonBase extends React.Component {
8992
return JSON.stringify(getThemeData(this.props));
9093
}
9194

95+
9296
getError() {
9397
const { error, i18n, status } = this.props;
9498
return status === ERROR ? (<div className="notification error" key="error-overlay">
@@ -176,6 +180,18 @@ export class AddonBase extends React.Component {
176180
this.setCurrentStatus();
177181
}
178182

183+
clickHeadingLink = (e) => {
184+
const { type, name, _tracking } = this.props;
185+
186+
if (e.target.nodeName.toLowerCase() === 'a') {
187+
_tracking.sendEvent({
188+
action: getAction(type),
189+
category: CLICK_CATEGORY,
190+
label: name,
191+
});
192+
}
193+
}
194+
179195
clickInstallTheme = (e) => {
180196
const { guid, installTheme, name, status, type } = this.props;
181197
e.preventDefault();
@@ -219,6 +235,7 @@ export class AddonBase extends React.Component {
219235
</ReactCSSTransitionGroup>
220236
<div className="copy">
221237
<h2
238+
onClick={this.clickHeadingLink}
222239
ref="heading"
223240
className="heading"
224241
dangerouslySetInnerHTML={sanitizeHTML(heading, ['a', 'span'])} />
@@ -375,11 +392,8 @@ export function mapDispatchToProps(dispatch, { _tracking = tracking,
375392
},
376393

377394
uninstall({ guid, name, type }) {
378-
const action = {
379-
[EXTENSION_TYPE]: 'addon',
380-
[THEME_TYPE]: 'theme',
381-
}[type] || 'invalid';
382395
dispatch({ type: INSTALL_STATE, payload: { guid, status: UNINSTALLING } });
396+
const action = getAction(type);
383397
return _addonManager.uninstall(guid)
384398
.then(() => {
385399
_tracking.sendEvent({

src/disco/constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111

1212
export const INSTALL_CATEGORY = 'AMO Addon / Theme Installs';
1313
export const UNINSTALL_CATEGORY = 'AMO Addon / Theme Uninstalls';
14+
export const CLICK_CATEGORY = 'AMO Addon / Theme Clicks';
1415
export const VIDEO_CATEGORY = 'Discovery Video';
1516
export const NAVIGATION_CATEGORY = 'Discovery Navigation';
1617

tests/client/core/test_utils.js

+16
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {
44
camelCaseProps,
55
convertBoolean,
66
findAddon,
7+
getAction,
78
getClientApp,
89
getClientConfig,
910
isValidClientApp,
1011
loadAddonIfNeeded,
1112
nl2br,
1213
} from 'core/utils';
14+
import { EXTENSION_TYPE, THEME_TYPE } from 'core/constants';
1315
import { unexpectedSuccess } from 'tests/client/helpers';
1416

1517
describe('camelCaseProps', () => {
@@ -330,3 +332,17 @@ describe('nl2br', () => {
330332
assert.equal(nl2br('\r\n\r\n'), '<br /><br />');
331333
});
332334
});
335+
336+
describe('getAction', () => {
337+
it('returns addon for EXTENSION_TYPE', () => {
338+
assert.equal(getAction(EXTENSION_TYPE), 'addon');
339+
});
340+
341+
it('returns theme for THEME_TYPE', () => {
342+
assert.equal(getAction(THEME_TYPE), 'theme');
343+
});
344+
345+
it('returns invalid for unknown type', () => {
346+
assert.equal(getAction('whatever'), 'invalid');
347+
});
348+
});

tests/client/disco/components/TestAddon.js

+24
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
UNINSTALLING,
3838
} from 'core/constants';
3939
import {
40+
CLICK_CATEGORY,
4041
CLOSE_INFO,
4142
INSTALL_CATEGORY,
4243
SET_ENABLE_NOT_AVAILABLE,
@@ -248,6 +249,29 @@ describe('<Addon />', () => {
248249
renderAddon(data);
249250
}, Error, 'Invalid addon type');
250251
});
252+
253+
it('tracks an add-on link click', () => {
254+
const fakeTracking = {
255+
sendEvent: sinon.stub(),
256+
};
257+
const data = {
258+
...result,
259+
_tracking: fakeTracking,
260+
name: 'foo',
261+
heading: 'This is <span>an <a href="https://addons.mozilla.org">add-on</a>/span>',
262+
type: EXTENSION_TYPE,
263+
};
264+
root = renderAddon(data);
265+
const heading = findDOMNode(root).querySelector('.heading');
266+
// We click the heading providing the link nodeName to emulate
267+
// bubbling.
268+
Simulate.click(heading, { target: { nodeName: 'A' } });
269+
assert.ok(fakeTracking.sendEvent.calledWith({
270+
action: 'addon',
271+
category: CLICK_CATEGORY,
272+
label: 'foo',
273+
}), sinon.format(fakeTracking.sendEvent.firstCall.args));
274+
});
251275
});
252276

253277

0 commit comments

Comments
 (0)