EBernhardson has uploaded a new change for review.

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

Change subject: [WIP] do not deploy. Restore broken version of satisfaction 
schema
......................................................................

[WIP] do not deploy. Restore broken version of satisfaction schema

Revert "Refactor search.js to only load deps for users in test"
Revert "Move Schema:Search from CirrusSearch"
Revert "Rename search.js -> searchSatisfaction.js"
Revert "Roll back satisfaction schema changes to pre oct-13 deploy"

This reverts commit f793b13afb204248d8a36a4218dcfcb464024fdc.
This reverts commit c0fd94423182e31edd6f3cd8fd0f65b185ec1ab2.
This reverts commit df13d6d873a1ac4b6e79ee6122afb50ce26e7e63.
This reverts commit dc6e3f134d80a997049f0e48c0f32478c9662419.

Change-Id: Ibeaaf8f43b1d67678e81653c0429875dec4fdb0b
---
M WikimediaEvents.php
M modules/ext.wikimediaEvents.searchSatisfaction.js
2 files changed, 126 insertions(+), 25 deletions(-)


  git pull 
ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/WikimediaEvents 
refs/changes/87/248087/1

diff --git a/WikimediaEvents.php b/WikimediaEvents.php
index 1e13c88..0599de5 100644
--- a/WikimediaEvents.php
+++ b/WikimediaEvents.php
@@ -88,7 +88,7 @@
        'schema.TestSearchSatisfaction2' => array(
                'class'    => 'ResourceLoaderSchemaModule',
                'schema'   => 'TestSearchSatisfaction2',
-               'revision' => 13223897,
+               'revision' => 14098806,
        ),
        'schema.GeoFeatures' => array(
                'class'    => 'ResourceLoaderSchemaModule',
diff --git a/modules/ext.wikimediaEvents.searchSatisfaction.js 
b/modules/ext.wikimediaEvents.searchSatisfaction.js
index ac173f9..2d99d03 100644
--- a/modules/ext.wikimediaEvents.searchSatisfaction.js
+++ b/modules/ext.wikimediaEvents.searchSatisfaction.js
@@ -1,5 +1,19 @@
 /*!
  * Javacsript module for measuring internal search bounce rate and dwell time.
+ * Utilizes two wprov query string formats:
+ * - serp:N - This indicates the link was visited directly from a SERP. N is
+ *   a positive integer indicating the position of this page within the 
results.
+ * - cirrus - This indicates the link was visited as part of a search session
+ *   but not directly from the search page.
+ *
+ * Example:
+ * - User performs search, is shown Special:Search. This has no wprov query 
string parameter
+ * - User clicks the 2nd result in the page which is `Jimmy Wales`, the user 
is sent to
+ *   /wiki/Jimmy_Wales?wprov=serp:2
+ * - User clicks a link in the content area of `Jimmy Wales` to `Wikipedia`, 
the user is sent to
+ *   /wiki/Wikipedia?wprov=cirrus.
+ * - Visiting any page without having a direct click stream through article 
pages back
+ *   to a SERP does not log events.
  *
  * @license GNU GPL v2 or later
  * @author Erik Bernhardson <ebernhard...@wikimedia.org>
@@ -7,20 +21,31 @@
 ( function ( mw, $, undefined ) {
        var isSearchResultPage = mw.config.get( 'wgIsSearchResultPage' ),
                uri = new mw.Uri( location.href ),
-               cameFromSearchResult = uri.query.wprov === 'cirrus';
+               // wprov attached to all search result links. If available
+               // indicates user got here directly from Special:Search
+               wprovPrefix = 'srpw1_',
+               // srpw1 has the position (including offset) of the search
+               // result appended.
+               searchResultPosition = parseInt( uri.query.wprov &&
+                       uri.query.wprov.substr( 0, wprovPrefix.length ) === 
wprovPrefix &&
+                       uri.query.wprov.substr( wprovPrefix.length ), 10 ),
+               cameFromSearchResult = !isNaN( searchResultPosition ),
+               isDeepSearchResult = uri.query.wprov === 'sdlw1',
+               lastScrollTop = $( window ).scrollTop();
 
        function oneIn( populationSize ) {
-               return Math.floor( Math.random() * populationSize ) === 0;
+               var rand = mw.user.generateRandomSessionId(),
+                       // take the first 52 bits of the rand value
+                       parsed = parseInt( rand.slice( 0, 13 ), 16 );
+               return parsed % populationSize === 0;
        }
 
-       if ( cameFromSearchResult ) {
+       if ( cameFromSearchResult || isDeepSearchResult ) {
                // cleanup the location bar in supported browsers
                if ( window.history.replaceState ) {
                        delete uri.query.wprov;
                        window.history.replaceState( {}, '', uri.toString() );
                }
-       } else if ( !isSearchResultPage ) {
-               return;
        }
 
        mw.loader.using( [
@@ -29,56 +54,93 @@
                'ext.eventLogging',
                'schema.TestSearchSatisfaction2'
        ] ).then( function () {
-               var searchSessionId = $.jStorage.get( 'searchSessionId' ),
+               var controlGroup, commonTermsProfile,
+                       searchSessionId = $.jStorage.get( 'searchSessionId' ),
+                       searchToken = $.jStorage.get( 'searchToken' ),
                        sessionLifetimeMs = 10 * 60 * 1000,
+                       tokenLifetimeMs = 24 * 60 * 60 * 1000,
                        checkinTimes = [ 10, 20, 30, 40, 50, 60, 90, 120, 150, 
180, 210, 240, 300, 360, 420 ],
-                       pageId = mw.user.generateRandomSessionId(),
+                       articleId = mw.config.get( 'wgArticleId' ),
+                       pageViewId = mw.user.generateRandomSessionId(),
+                       activeSubTest = $.jStorage.get( 'searchSubTest' ),
+                       subTestGroups = [ 'default', 'default.control', 
'strict', 'strict.control', 'aggressive_recall', 'aggressive_recall.control' ],
                        logEvent = function ( action, checkinTime ) {
-                               var evt = {
+                               var scrollTop = $( window ).scrollTop(),
+                                       evt = {
                                                // searchResultPage, visitPage 
or checkin
                                                action: action,
                                                // identifies a single user 
performing searches within
                                                // a limited time span.
                                                searchSessionId: 
searchSessionId,
+                                               // identifies a single user 
over a 24 hour timespan,
+                                               // allowing to tie together 
multiple search sessions
+                                               searchToken: searchToken,
                                                // used to correlate actions 
that happen on the same
                                                // page. Otherwise a user 
opening multiple search results
                                                // in tabs would make their 
events overlap and the dwell
                                                // time per page uncertain.
-                                               pageId: pageId,
-                                               // we noticed a number of 
events get sent multiple
-                                               // times from javascript, 
especially when using sendBeacon.
-                                               // This logId allows for later 
deduplication
-                                               logId: 
mw.user.generateRandomSessionId()
+                                               pageViewId: pageViewId,
+                                               // identifies if a user has 
scrolled the page since the
+                                               // last event
+                                               scroll: scrollTop !== 
lastScrollTop
                                        };
+                               lastScrollTop = scrollTop;
                                if ( checkinTime !== undefined ) {
+                                       // identifies how long the user has 
been on this page
                                        evt.checkin = checkinTime;
+                               }
+                               if ( isSearchResultPage ) {
+                                       // the users actual search term
+                                       evt.query = mw.config.get( 'searchTerm' 
);
+                                       // the number of results shown on this 
page.
+                                       evt.hitsReturned = $( 
'.mw-search-result-heading' ).length;
+                                       if ( activeSubTest ) {
+                                               evt.subTest = 'common-terms:' + 
activeSubTest + ':' +
+                                                       ( mw.config.get( 
'wgCirrusCommonTermsApplicable' ) ? 'enabled' : 'disabled' );
+                                       }
+                               }
+                               if ( articleId > 0 ) {
+                                       evt.articleId = articleId;
+                               }
+                               if ( cameFromSearchResult ) {
+                                       // this is only available on article 
pages linked
+                                       // directly from a search result.
+                                       evt.position = searchResultPosition;
                                }
                                mw.eventLog.logEvent( 
'TestSearchSatisfaction2', evt );
                        },
-                       updateHref = function () {
+                       // expects to be run with an html anchor as `this`
+                       updateSearchHref = function () {
+                               var uri = new mw.Uri( this.href ),
+                                       offset = $( this ).data( 'serp-pos' );
+                               if ( offset ) {
+                                       uri.query.wprov = 'srpw1_' + offset;
+                                       this.href = uri.toString();
+                               }
+                       },
+                       // expects to be run with an html anchor as `this`
+                       updateDeepHref = function () {
                                var uri = new mw.Uri( this.href );
-                               // try to not add our query param to 
unnecessary places
+                               // try to not add our query param to 
unnecessary places. The
+                               // wikitext parser always outputs /wiki/ for 
[[WikiLinks]].
                                if ( uri.path.substr( 0, 6 ) === '/wiki/' ) {
-                                       uri.query.wprov = 'cirrus';
+                                       uri.query.wprov = 'sdlw1';
                                        this.href = uri.toString();
                                }
                        };
 
                if ( searchSessionId === 'rejected' ) {
-                       // User was previously rejected or timed out
+                       // User was previously rejected
                        return;
                } else if ( searchSessionId ) {
                        // User was previously chosen to participate in the 
test.
                        // When a new search is performed reset the session 
lifetime.
                        if ( isSearchResultPage ) {
                                $.jStorage.setTTL( 'searchSessionId', 
sessionLifetimeMs );
+                               $.jStorage.setTTL( 'searchSubTest', 
sessionLifetimeMs );
                        }
-               } else if (
-                       // Most likely this means the users search session 
timed out.
-                       !isSearchResultPage ||
+               } else if ( !oneIn( 200 ) ) {
                        // user was not chosen in a sampling of search results
-                       !oneIn( 200 )
-               ) {
                        $.jStorage.set( 'searchSessionId', 'rejected', { TTL: 2 
* sessionLifetimeMs } );
                        return;
                } else {
@@ -95,11 +157,50 @@
                        }
                }
 
-               $( '#mw-content-text a:not(.external)' ).each( updateHref );
+               if ( searchToken === null ) {
+                       searchToken = mw.user.generateRandomSessionId();
+                       $.jStorage.set( 'searchToken', searchToken, { TTL: 
tokenLifetimeMs } );
+                       if ( $.jStorage.get( 'searchToken' ) !== searchToken ) {
+                               // likely localstorage is full, we can't 
properly track
+                               // this user
+                               return;
+                       }
+               }
+
+               if ( activeSubTest === null ) {
+                       // include 1 in 10 of the users in the satisfaction 
metric into the common terms sub test.
+                       activeSubTest = subTestGroups[Math.floor( Math.random() 
* subTestGroups.length )];
+                       $.jStorage.set( 'searchSubTest', activeSubTest, { TTL: 
sessionLifetimeMs } );
+                       if ( $.jStorage.get( 'searchSubTest' ) !== 
activeSubTest ) {
+                               // localstorage full, just opt them back out of 
the sub test
+                               activeSubTest = '';
+                       }
+               }
+
+               if ( activeSubTest !== '' ) {
+                       controlGroup = activeSubTest.substring( 
activeSubTest.length - '.control'.length ) === '.control';
+                       commonTermsProfile = controlGroup ? 
activeSubTest.substring( activeSubTest.length - '.control'.length ) : 
activeSubTest;
+
+                       $( 'input[type="search"]' ).closest( 'form' ).append( 
$( '<input>' ).attr( {
+                               type: 'hidden',
+                               name: 'cirrusUseCommonTermsQuery',
+                               value: 'yes'
+                       } ) ).append( $( '<input>' ).attr( {
+                               type: 'hidden',
+                               name: 'cirrusCommonTermsQueryProfile',
+                               value: commonTermsProfile
+                       } ) ).append( $( '<input>' ).attr( {
+                               type: 'hidden',
+                               name: 'cirrusCommonTermsQueryControlGroup',
+                               value: controlGroup ? 'yes' : 'no'
+                       } ) );
+               }
 
                if ( isSearchResultPage ) {
+                       $( '.mw-search-result-heading a' ).each( 
updateSearchHref );
                        logEvent( 'searchResultPage' );
-               } else {
+               } else if ( cameFromSearchResult || isDeepSearchResult ) {
+                       $( '#mw-content-text a:not(.external)' ).each( 
updateDeepHref );
                        logEvent( 'visitPage' );
                        $( checkinTimes ).each( function ( _, checkin ) {
                                setTimeout( function () {

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ibeaaf8f43b1d67678e81653c0429875dec4fdb0b
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/WikimediaEvents
Gerrit-Branch: master
Gerrit-Owner: EBernhardson <ebernhard...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to