mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Popups
synced 2024-11-15 03:34:03 +00:00
VirtualPageViews bypass EventLogging for logging virtual events
We are using EventLogging to track page views not user behaviour. This is an exception to the rule and requires special handling. Bug: T190188 Change-Id: If096ccaf0ac884d57744ed57e2f26b51446de2d7
This commit is contained in:
parent
9eccf1c103
commit
e6202b6ce4
BIN
resources/dist/index.js
vendored
BIN
resources/dist/index.js
vendored
Binary file not shown.
BIN
resources/dist/index.js.json
vendored
BIN
resources/dist/index.js.json
vendored
Binary file not shown.
72
src/getPageviewTracker.js
Normal file
72
src/getPageviewTracker.js
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @module getPageviewTracker
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MwCodeLoader
|
||||
*
|
||||
* Loads code from the server to the client on demand.
|
||||
*
|
||||
* @param {array} dependencies to load
|
||||
* @return {jQuery.Deferred} resolving when the code is loaded and
|
||||
* can be used by the client.
|
||||
*
|
||||
* @global
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convert the first letter of a string to uppercase.
|
||||
*
|
||||
* @param {string} word
|
||||
* @return {string}
|
||||
*/
|
||||
function titleCase( word ) {
|
||||
return word[ 0 ].toUpperCase() + word.slice( 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate analytics event tracker for logging virtual pageviews.
|
||||
* Note this bypasses EventLogging in order to track virtual pageviews
|
||||
* for pages where the DNT header (do not track) has been added.
|
||||
* This is explained in https://phabricator.wikimedia.org/T187277.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {MwCodeLoader} loader that can source code that obeys the
|
||||
* EventLogging api specification.
|
||||
* @param {Function} trackerGetter when called returns an instance
|
||||
* of MediaWiki's EventLogging client
|
||||
* @param {Function} sendBeacon see
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon
|
||||
* @return {EventTracker}
|
||||
*/
|
||||
function getPageviewTracker( config, loader, trackerGetter, sendBeacon ) {
|
||||
const pageviewTracker = function ( topic, eventData ) {
|
||||
const schema = titleCase( topic.slice( topic.indexOf( '.' ) + 1 ) );
|
||||
const dependencies = [ 'ext.eventLogging', `schema.${schema}` ];
|
||||
return loader( dependencies ).then( function () {
|
||||
const evLog = trackerGetter();
|
||||
const payload = evLog.prepare( schema, eventData );
|
||||
const url = evLog.makeBeaconUrl( payload );
|
||||
sendBeacon( url );
|
||||
} );
|
||||
};
|
||||
return config.get( 'wgPopupsVirtualPageViews' ) ? pageviewTracker : $.noop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that can asynchronously transfer a small amount of data
|
||||
* over HTTP to a web server.
|
||||
*
|
||||
* @param {Window.Navigator} navigatorObj
|
||||
* @return {Function}
|
||||
*/
|
||||
function getSendBeacon( navigatorObj ) {
|
||||
return navigatorObj.sendBeacon ?
|
||||
navigatorObj.sendBeacon.bind( navigatorObj ) :
|
||||
( url ) => {
|
||||
document.createElement( 'img' ).src = url;
|
||||
};
|
||||
}
|
||||
|
||||
export { getSendBeacon };
|
||||
export default getPageviewTracker;
|
17
src/index.js
17
src/index.js
|
@ -22,6 +22,7 @@ import * as actions from './actions';
|
|||
import reducers from './reducers';
|
||||
import createMediaWikiPopupsObject from './integrations/mwpopups';
|
||||
import getUserBucket from './getUserBucket';
|
||||
import getPageviewTracker, { getSendBeacon } from './getPageviewTracker';
|
||||
|
||||
const mw = mediaWiki,
|
||||
$ = jQuery,
|
||||
|
@ -66,16 +67,6 @@ function getStatsvTracker( user, config, experiments ) {
|
|||
return isStatsvEnabled( user, config, experiments ) ? mw.track : $.noop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate analytics event tracker for logging virtual pageviews.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @return {EventTracker}
|
||||
*/
|
||||
function getPageViewTracker( config ) {
|
||||
return config.get( 'wgPopupsVirtualPageViews' ) ? mw.track : $.noop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate analytics event tracker for logging EventLogging events
|
||||
* via [the "EventLogging subscriber" analytics event protocol][0].
|
||||
|
@ -185,7 +176,11 @@ mw.requestIdleCallback( function () {
|
|||
experiments = createExperiments( mw.experiments ),
|
||||
statsvTracker = getStatsvTracker( mw.user, mw.config, experiments ),
|
||||
// Virtual pageviews are always tracked.
|
||||
pageviewTracker = getPageViewTracker( mw.config ),
|
||||
pageviewTracker = getPageviewTracker( mw.config,
|
||||
mw.loader.using,
|
||||
() => mw.eventLog,
|
||||
getSendBeacon( window.navigator )
|
||||
),
|
||||
eventLoggingTracker = getEventLoggingTracker(
|
||||
mw.user,
|
||||
mw.config,
|
||||
|
|
59
tests/node-qunit/getPageviewTracker.test.js
Normal file
59
tests/node-qunit/getPageviewTracker.test.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* global Promise */
|
||||
import getPageviewTracker, { getSendBeacon } from '../../src/getPageviewTracker';
|
||||
|
||||
QUnit.module( 'ext.popups#getPageviewTracker', {
|
||||
beforeEach: function () {
|
||||
this.makeBeaconUrl = this.sandbox.stub();
|
||||
this.prepare = this.sandbox.stub();
|
||||
this.trackerGetter = () => ( { makeBeaconUrl: this.makeBeaconUrl,
|
||||
prepare: this.prepare } );
|
||||
this.loader = () => Promise.resolve();
|
||||
}
|
||||
} );
|
||||
|
||||
const enabledConfig = {
|
||||
get: () => true
|
||||
};
|
||||
|
||||
QUnit.test( 'getPageviewTracker', function ( assert ) {
|
||||
const loader = this.sandbox.stub();
|
||||
const sendBeacon = this.sandbox.stub();
|
||||
const data = { foo: 1 };
|
||||
const tracker = getPageviewTracker( enabledConfig,
|
||||
loader, this.trackerGetter, sendBeacon );
|
||||
|
||||
loader.resolves();
|
||||
|
||||
return tracker( 'event.VirtualPageView', data ).then( () => {
|
||||
assert.ok( loader.calledOnce, 'loader called once' );
|
||||
assert.ok( loader.calledWith( [ 'ext.eventLogging', 'schema.VirtualPageView' ] ),
|
||||
'appropriate code is loaded' );
|
||||
assert.ok( this.prepare.calledWith( 'VirtualPageView', data ),
|
||||
'mw.eventLog.prepare called appropriately' );
|
||||
assert.ok( this.makeBeaconUrl.calledOnce,
|
||||
'makeBeacon called with result of prepare' );
|
||||
assert.ok( sendBeacon.calledOnce,
|
||||
'sendBeacon called with url from makeBeaconUrl' );
|
||||
} );
|
||||
} );
|
||||
|
||||
QUnit.test( 'getSendBeacon', function ( assert ) {
|
||||
let success = false;
|
||||
const navtr = {
|
||||
successful: true,
|
||||
sendBeacon: function () {
|
||||
// This local variable helps test context. Don't refactor.
|
||||
success = this.successful;
|
||||
}
|
||||
};
|
||||
const sendBeacon = getSendBeacon( navtr );
|
||||
sendBeacon();
|
||||
assert.ok( success, 'native sendBeacon is used when available and run in appropriate context' );
|
||||
} );
|
||||
|
||||
QUnit.test( 'getSendBeacon (fallback)', function ( assert ) {
|
||||
const spy = this.sandbox.spy( document, 'createElement' );
|
||||
const sendBeacon = getSendBeacon( {} );
|
||||
sendBeacon();
|
||||
assert.ok( spy.calledOnce, 'an img element is used as fallback' );
|
||||
} );
|
Loading…
Reference in a new issue