From c1159337a96dca6251d9d2095cc3b53925a5d4e5 Mon Sep 17 00:00:00 2001 From: Erik Bernhardson Date: Mon, 15 Feb 2016 12:49:37 -0800 Subject: [PATCH] Add additional tracking information to mediawiki.searchSuggest Adds a few pieces of information to improve tracking of autocomplete usage. * When using Special:Search 'go' feature forward wprov parameter to redirect * Include a data attribute indicating autocomplete location to differentiate usage of the header and Special:Search content autocompletes * Report exact query string that was used for impression-results * Add handling to allow searchSuggest subscribers to append tracking information to generated article links * Add a new hook, SpecialSearchGoResult, that can either change the url redirected to in the 'go' feature or cancel it entirely. Bug: T125915 Change-Id: Iec7171fcf301f1659d852afa87ce271f468177c1 --- docs/hooks.txt | 8 +++ includes/specials/SpecialSearch.php | 11 ++++- resources/src/jquery/jquery.suggestions.js | 7 +-- .../src/mediawiki/mediawiki.searchSuggest.js | 49 ++++++++++++++++--- 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/docs/hooks.txt b/docs/hooks.txt index 930aa0a84c..dc86ef195b 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -2914,6 +2914,14 @@ go to the existing page. $t: title object searched for &$params: an array of the default message name and page title (as parameter) +'SpecialSearchGoResult': If a hook returns false the 'go' feature will be +canceled and a normal search will be performed. Returning true without setting +$url does a standard redirect to $title. Setting $url redirects to the +specified URL. +$term - The string the user searched for +$title - The title the 'go' feature has decided to forward the user to +&$url - Initially null, hook subscribers can set this to specify the final url to redirect to + 'SpecialSearchNogomatch': Called when user clicked the "Go" button but the target doesn't exist. &$title: title object generated from the text entered by the user diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index 5b9778c270..9bb5d9592e 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -204,8 +204,13 @@ class SpecialSearch extends SpecialPage { # If there's an exact or very near match, jump right there. $title = SearchEngine::getNearMatch( $term ); - if ( !is_null( $title ) ) { - $this->getOutput()->redirect( $title->getFullURL() ); + if ( !is_null( $title ) && + Hooks::run( 'SpecialSearchGoResult', [ $term, $title, &$url ] ) + ) { + if ( $url === null ) { + $url = $title->getFullURL(); + } + $this->getOutput()->redirect( $url ); return; } @@ -1221,6 +1226,8 @@ class SpecialSearch extends SpecialPage { 'size' => '50', 'autofocus' => trim( $term ) === '', 'class' => 'mw-ui-input mw-ui-input-inline', + // identifies the location of the search bar for tracking purposes + 'data-search-loc' => 'content', ] ) . "\n"; $out .= Html::hidden( 'fulltext', 'Search' ) . "\n"; $out .= Html::submitButton( diff --git a/resources/src/jquery/jquery.suggestions.js b/resources/src/jquery/jquery.suggestions.js index 01d9a43d61..1f977bf910 100644 --- a/resources/src/jquery/jquery.suggestions.js +++ b/resources/src/jquery/jquery.suggestions.js @@ -181,7 +181,7 @@ if ( +new Date() - cache[ val ].timestamp < context.config.cacheMaxAge ) { context.data.$textbox.suggestions( 'suggestions', cache[ val ].suggestions ); if ( typeof context.config.update.after === 'function' ) { - context.config.update.after.call( context.data.$textbox ); + context.config.update.after.call( context.data.$textbox, cache[ val ].metadata ); } cacheHit = true; } else { @@ -193,15 +193,16 @@ context.config.fetch.call( context.data.$textbox, val, - function ( suggestions ) { + function ( suggestions, metadata ) { suggestions = suggestions.slice( 0, context.config.maxRows ); context.data.$textbox.suggestions( 'suggestions', suggestions ); if ( typeof context.config.update.after === 'function' ) { - context.config.update.after.call( context.data.$textbox ); + context.config.update.after.call( context.data.$textbox, metadata ); } if ( context.config.cache ) { cache[ val ] = { suggestions: suggestions, + metadata: metadata, timestamp: +new Date() }; } diff --git a/resources/src/mediawiki/mediawiki.searchSuggest.js b/resources/src/mediawiki/mediawiki.searchSuggest.js index 0fcd22c46f..739064524f 100644 --- a/resources/src/mediawiki/mediawiki.searchSuggest.js +++ b/resources/src/mediawiki/mediawiki.searchSuggest.js @@ -12,12 +12,13 @@ namespace: 0, limit: maxRows, suggest: true - } ).done( function ( data ) { - response( data[ 1 ] ); + } ).done( function ( data, jqXHR ) { + response( data[ 1 ], { + type: jqXHR.getResponseHeader( 'X-OpenSearch-Type' ), + query: query + } ); } ); - }, - // The name of the request api for event logging purposes - type: 'prefix' + } }; $( function () { @@ -90,19 +91,37 @@ previousSearchText = searchText; } + /** + * defines the location of autocomplete. Typically either + * header, which is in the top right of vector (for example) + * and content which identifies the main search bar on + * Special:Search. Defaults to header for skins that don't set + * explicitly. + * + * @ignore + */ + function getInputLocation( context ) { + return context.config.$region + .closest( 'form' ) + .find( '[data-search-loc]' ) + .data( 'search-loc' ) || 'header'; + } + /** * Callback that's run when suggestions have been updated either from the cache or the API * 'this' is the search input box (jQuery object) * * @ignore */ - function onAfterUpdate() { + function onAfterUpdate( metadata ) { var context = this.data( 'suggestionsContext' ); mw.track( 'mediawiki.searchSuggest', { action: 'impression-results', numberOfResults: context.config.suggestions.length, - resultSetType: mw.searchSuggest.type + resultSetType: metadata.type || 'unknown', + query: metadata.query, + inputLocation: getInputLocation( context ) } ); } @@ -113,6 +132,14 @@ // linkParams object is modified and reused formData.linkParams[ formData.textParam ] = text; + // Allow trackers to attach tracking information, such + // as wprov, to clicked links. + mw.track( 'mediawiki.searchSuggest', { + action: 'render-one', + formData: formData, + index: context.config.suggestions.indexOf( text ) + 1 + } ); + // this is the container
, jQueryfied this.text( text ) .wrap( @@ -208,6 +235,10 @@ return true; } }, + update: { + before: onBeforeUpdate, + after: onAfterUpdate + }, cache: true, highlightInput: true } ) @@ -262,7 +293,9 @@ var context = $searchInput.data( 'suggestionsContext' ); mw.track( 'mediawiki.searchSuggest', { action: 'submit-form', - numberOfResults: context.config.suggestions.length + numberOfResults: context.config.suggestions.length, + $form: context.config.$region.closest( 'form' ), + inputLocation: getInputLocation( context ) } ); } ) // If the form includes any fallback fulltext search buttons, remove them -- 2.20.1