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