Jdrewniak has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/251091

Change subject: wikipedia.org event logging
......................................................................

wikipedia.org event logging

Change-Id: Ia52a523723dfbdd3db79a52e72e606c58351bd27
---
A dev/wikipedia.org/assets/js/event-logging-lite.js
A dev/wikipedia.org/assets/js/wikimedia-org-event-logging.js
M dev/wikipedia.org/index.html
3 files changed, 383 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/wikimedia/portals 
refs/changes/91/251091/1

diff --git a/dev/wikipedia.org/assets/js/event-logging-lite.js 
b/dev/wikipedia.org/assets/js/event-logging-lite.js
new file mode 100644
index 0000000..e34c5f2
--- /dev/null
+++ b/dev/wikipedia.org/assets/js/event-logging-lite.js
@@ -0,0 +1,209 @@
+/*
+
+A slimmed down version of the event logging API.
+Mostly copy & pasted from:
+https://github.com/wikimedia/mediawiki-extensions-EventLogging/blob/master/modules/ext.eventLogging.core.js
+without dependancies on jQuery or mediawiki.js. For use on wikipedia.org 
portal page.
+
+*/
+
+( function () {
+
+'use strict';
+
+// baseUrl = mw.config.get( 'wgEventLoggingBaseUri' );
+var baseUrl = '//www.wikipedia.org/beacon/event',
+byteToHex = [],
+self, helpers;
+
+helpers = {
+       // replaces $.extend
+       extend: function ( defaults, options ) {
+               var extended = {},
+                               prop;
+
+               for ( prop in defaults ) {
+                       if ( Object.prototype.hasOwnProperty.call( defaults, 
prop ) ) {
+                               extended[ prop ] = defaults[ prop ];
+                       }
+               }
+               for ( prop in options ) {
+                       if ( Object.prototype.hasOwnProperty.call( options, 
prop ) ) {
+                               extended[ prop ] = options[ prop ];
+                       }
+               }
+               return extended;
+       },
+       // replaces $.noop
+       noop: function () {},
+
+       // navigator.sendBeacon polyfill
+       sendBeacon: function ( url ) {
+               var xhr = ( 'XDomainRequest' in window ) ? new XDomainRequest() 
:  new XMLHttpRequest();
+               xhr.open( 'GET', url, false ); // synchronous request
+               xhr.responseType = 'text/plain';
+               xhr.send();
+               return true;
+       }
+};
+//indexOf polyfill 
http://stackoverflow.com/questions/3629183/why-doesnt-indexof-work-on-an-array-ie8
+if ( !Array.prototype.indexOf ) {
+    Array.prototype.indexOf = function( elt /*, from*/)
+    {
+        var len = this.length >>> 0;
+
+        var from = Number(arguments[1]) || 0;
+        from = (from < 0)
+            ? Math.ceil(from)
+            : Math.floor(from);
+        if (from < 0)
+            from += len;
+
+        for (; from < len; from++)
+        {
+            if (from in this &&
+                this[from] === elt)
+                return from;
+        }
+        return -1;
+    };
+}
+
+
+// byte to hex from
+// 
https://github.com/wikimedia/mediawiki/blob/e87668e86ce9ad20df05c1baa8e7cf3f58900524/resources/src/mediawiki/mediawiki.user.js
+for ( var i = 0; i < 256; i++ ) {
+       // Padding: Add a full byte (0x100, 256) and strip the extra character
+       byteToHex[ i ] = ( i + 256 ).toString( 16 ).slice( 1 );
+}
+
+self = window.eventLoggingLite = {
+
+       schema: {},
+
+       maxUrlSize: 2000,
+
+       byteToHex: byteToHex,
+
+       checkUrlSize: function ( schemaName, url ) {
+               var message;
+               if ( url.length > self.maxUrlSize ) {
+                       message = 'Url exceeds maximum length';
+                       return message;
+               }
+       },
+
+       generateRandomSessionId: function () {
+               // copied from:
+               // 
https://github.com/wikimedia/mediawiki/blob/e87668e86ce9ad20df05c1baa8e7cf3f58900524/resources/src/mediawiki/mediawiki.user.js
+
+               /*jshint bitwise:false */
+               var rnds, i, r,
+                               hexRnds = new Array( 8 ),
+               // Support: IE 11
+                               crypto = window.crypto || window.msCrypto;
+
+               // Based on 
https://github.com/broofa/node-uuid/blob/bfd9f96127/uuid.js
+               if ( crypto && crypto.getRandomValues ) {
+                       // Fill an array with 8 random values, each of which is 
8 bits.
+                       // Note that Uint8Array is array-like but does not 
implement Array.
+                       rnds = new Uint8Array( 8 );
+                       crypto.getRandomValues( rnds );
+               } else {
+                       rnds = new Array( 8 );
+                       for ( i = 0; i < 8; i++ ) {
+                               if ( ( i & 3 ) === 0 ) {
+                                       r = Math.random() * 0x100000000;
+                               }
+                               rnds[ i ] = r >>> ( ( i & 3 ) << 3 ) & 255;
+                       }
+               }
+               // Convert from number to hex
+               for ( i = 0; i < 8; i++ ) {
+                       hexRnds[ i ] = self.byteToHex[ rnds[ i ] ];
+               }
+
+               // Concatenation of two random integers with entrophy n and m
+               // returns a string with entrophy n+m if those strings are 
independent
+               return hexRnds.join( '' );
+
+       },
+
+       validate: function ( obj, schema ) {
+               var key, val, prop,
+                               errors = [];
+
+               if ( !schema || !schema.properties ) {
+                       errors.push( 'Missing or empty schema' );
+                       return errors;
+               }
+
+               for ( key in obj ) {
+                       if ( !schema.properties.hasOwnProperty( key ) ) {
+                               errors.push( 'Undeclared property: ' + key );
+                       }
+               }
+
+               for ( key in schema.properties ) {
+                       prop = schema.properties[ key ];
+
+                       if ( !obj.hasOwnProperty( key ) ) {
+                               if ( prop.required ) {
+                                       errors.push( 'Missing property:', key );
+                               }
+                               continue;
+                       }
+                       val = obj[ key ];
+
+                       if ( prop[ 'enum' ] && prop.required && prop[ 'enum' 
].indexOf( val ) === -1 ) {
+                               errors.push( 'Value "' + JSON.stringify( val ) 
+ '" for property "' + key + '" is not one of ' + JSON.stringify( prop[ 'enum' 
] ) );
+                       }
+               }
+
+               return errors;
+
+       },
+
+       prepare: function ( schema, eventData ) {
+
+               var event = helpers.extend( schema.defaults, eventData ),
+               errors = self.validate( event, schema );
+
+               while ( errors.length ) {
+                       console.log( errors[ errors.length - 1 ] );
+                       errors.pop();
+               }
+
+               return {
+                       event: event,
+                       revision: schema.revision || -1,
+                       schema: schema.name,
+                       webHost: location.hostname
+                       /* wiki: mw.config.get( 'wgDBname' ) */
+               };
+       },
+
+       makeBeaconUrl: function ( data ) {
+               var queryString = encodeURIComponent( JSON.stringify( data ) );
+               return baseUrl + '?' + queryString + ';';
+       },
+
+       sendBeacon: ( /1|yes/.test( navigator.doNotTrack ) || !baseUrl )
+                       ? helpers.noop
+                       : navigator.sendBeacon
+                       ? function ( url ) { try { navigator.sendBeacon( url ); 
} catch ( e ) {} }
+                       : function ( url ) { helpers.sendBeacon( url ); },
+
+       logEvent: function ( schemaName, eventData ) {
+               var event = self.prepare( schemaName, eventData ),
+                               url = self.makeBeaconUrl( event ),
+                               sizeError = self.checkUrlSize( schemaName, url 
);
+
+               if ( !sizeError ) {
+                       self.sendBeacon( url );
+               }
+       }
+
+}; // eventLoggingLite
+
+}() );
diff --git a/dev/wikipedia.org/assets/js/wikimedia-org-event-logging.js 
b/dev/wikipedia.org/assets/js/wikimedia-org-event-logging.js
new file mode 100644
index 0000000..f189e4e
--- /dev/null
+++ b/dev/wikipedia.org/assets/js/wikimedia-org-event-logging.js
@@ -0,0 +1,172 @@
+// jscs:disable requireCamelCaseOrUpperCaseIdentifiers
+
+( function ( eventLoggingLite ) {
+
+'use strict';
+
+var portalSchema, linkSections, docLinks, docForms, i;
+
+portalSchema = {
+       name: 'WikipediaPortal',
+       // revision # from 
https://meta.wikimedia.org/wiki/Schema:WikipediaPortal
+       revision: 14377354,
+       defaults: {
+               session_id: eventLoggingLite.generateRandomSessionId(),
+               event_type: 'landing',
+               section_used: null,
+               destination: null,
+               referer: document.referrer || null,
+               country: null,
+               accept_language: ( navigator && navigator.language ) ? 
navigator.language : navigator.browserLanguage,
+               cohort: null
+       },
+       description: 'Logs basic information about the use of the 
Wikipedia\nportal (www.wikipedia.org). Most of the things we want (user agent, 
say) are already tracked',
+       properties: {
+               session_id: {
+                       type: 'string',
+                       required: true,
+                       description: 'A unique session ID that identifies 
events from a single user, resetting on every portal view. In other words, both 
"landing" and "clickthrough" events from a single user in a single session can 
be clumped together.'
+               },
+               event_type: {
+                       type: 'string',
+                       required: true,
+                       'enum': [
+                               'landing',
+                               'clickthrough'
+                       ],
+                       description: 'The type of event were logging. Options 
are "landing" - the start event, when someone shows up on a portal - and 
"clickthrough", indicating that they chose a link or the search bar or whatever 
and went somewhere else.'
+               },
+               section_used: {
+                       type: 'string',
+                       required: false,
+                       'enum': [
+                               'primary links',
+                               'search',
+                               'language search',
+                               'secondary links',
+                               'other languages',
+                               'other projects'
+                       ],
+                       description: 'The overall section interacted with. This 
could be\n \'primary links\' (the called-out project links around the globe\', 
\n \'search\' (the main search box), \'language search\' (\' Find Wikipedia in 
a language\'),\n \'secondary links\' (the less-prominent plaintext links)\n 
\'other languages\' (this page in other langauges)\n or \'other projects\' 
(links to wikisource, wiktionary, et al, portals). \n NULL in the case that 
\'event_type\' is landing'
+               },
+               destination: {
+                       type: 'string',
+                       required: false,
+                       description: 'Where the user clicked through to.\n This 
may be NULL if the event type is \'landing\''
+               },
+               referer: {
+                       type: 'string',
+                       required: false,
+                       description: 'The referer the user came from, if known. 
May be NULL.'
+               },
+               country: {
+                       type: 'string',
+                       required: false,
+                       description: 'The ISO code for the country the user 
geolocates to. May be NULL.'
+               },
+               accept_language: {
+                       type: 'string',
+                       required: true,
+                       description: 'The accept_language header from the 
user\'s request'
+               },
+               cohort: {
+                       type: 'string',
+                       required: false,
+                       description: 'A cohort identifier. We can use this for 
running A/B tests or unrelated experiments, with the population who are *not* 
in any tests getting a NULL cohort field, meaning we can base our dashboards 
off those users and A/B test from the same schema in peace.'
+               }
+       }
+};
+
+linkSections = [
+       { name: 'primary links',   nodes: document.querySelectorAll( 
'.central-featured' ) },
+       { name: 'search',          nodes: document.querySelectorAll( 
'.search-form' ) },
+       { name: 'language search', nodes: document.querySelectorAll( 
'.language-search' ) },
+       { name: 'secondary links', nodes: document.querySelectorAll( 
'.langlist' ) },
+       { name: 'other languages', nodes: document.querySelectorAll( 
'#langlist-other' ) },
+       { name: 'other projects',  nodes: document.querySelectorAll( 
'.otherprojects' ) }
+];
+
+function findLinkSection( clickNode ) {
+
+       var linkSection = {},
+        i, j;
+
+       for ( i = 0; i < linkSections.length; i++ ) {
+
+               //var nodes = Array.prototype.slice.call( linkSections[ i 
].nodes, 0 );
+        var nodes = linkSections[ i ].nodes;
+
+               for ( j = 0; j < nodes.length; j++ ) {
+
+                       if (  nodes[ j ].contains( clickNode ) ) {
+
+                               linkSection = linkSections[ i ];
+                       }
+               }
+       }
+       return linkSection.name;
+}
+
+function interceptLandingEvent() {
+       eventData = {
+               event_type: 'landing'
+       };
+       eventLoggingLite.logEvent( portalSchema, eventData );
+}
+
+// polyfill element.matches 
https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
+// for eventual event delegation.
+/*
+var matches = function ( elm, selector ) {
+    var matches = ( elm.document || elm.ownerDocument ).querySelectorAll( 
selector ),
+        i = matches.length;
+    while ( --i >= 0 && matches.item( i ) !== elm );
+    return i > -1;
+}
+*/
+
+function interceptLinkClick( event ) {
+
+    var event = window.event;
+
+    // for IE6-8
+    var target = event.target || event.srcElement;
+
+    var eventData = {
+           event_type: 'clickthrough',
+           destination: target.href,
+               section_used: findLinkSection( target )
+       };
+
+
+       if ( eventData.section_used ) {
+               eventLoggingLite.logEvent( portalSchema, eventData );
+       }
+
+}
+
+function interceptForms ( event ) {
+       var eventData = {
+               event_type: 'clickthrough',
+               section_used: findLinkSection( event.target )
+       };
+       eventLoggingLite.logEvent( portalSchema, eventData );
+}
+
+docLinks = document.links;
+
+docForms =  document.getElementsByTagName('form');
+
+for ( i = 0; i < docForms.length; i++ ) {
+       docForms[ i ].onsubmit = interceptForms;
+}
+
+for ( i = 0; i < docLinks.length; i++ ) {
+       docLinks[ i ].onclick = interceptLinkClick;
+}
+
+document.cookie = 'portal_session_id=' + portalSchema.defaults.session_id;
+
+window.onload = interceptLandingEvent;
+
+}( eventLoggingLite ) );
diff --git a/dev/wikipedia.org/index.html b/dev/wikipedia.org/index.html
index 3688c88..c71f174 100644
--- a/dev/wikipedia.org/index.html
+++ b/dev/wikipedia.org/index.html
@@ -597,5 +597,7 @@
 <div style="text-align:center"><span id="discovery-feedback-survey" 
style="display:none"><a 
href="//wikimedia.qualtrics.com/jfe/form/SV_3rZFiKhpb2JzWbb">Give Us 
Feedback</a> | </span><a 
href="//wikimediafoundation.org/wiki/Terms_of_Use">Terms of Use</a> | <a 
href="//wikimediafoundation.org/wiki/Privacy_policy">Privacy Policy</a></div>
 <script>if (Math.floor( Math.random() * 5 ) === 0) { document.getElementById( 
"discovery-feedback-survey" ).style.display="inline"; }</script>
 <script src="assets/js/wm-portal.js"></script>
+<script src="assets/js/event-logging-lite.js"></script>
+<script src="assets/js/wikimedia-org-event-logging.js"></script>
 </body>
 </html>

-- 
To view, visit https://gerrit.wikimedia.org/r/251091
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia52a523723dfbdd3db79a52e72e606c58351bd27
Gerrit-PatchSet: 1
Gerrit-Project: wikimedia/portals
Gerrit-Branch: master
Gerrit-Owner: Jdrewniak <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to