From: Erik Bernhardson Date: Thu, 16 Jun 2016 15:56:43 +0000 (-0700) Subject: autocomplete: fix duplicate/missing tracking events X-Git-Tag: 1.31.0-rc.0~6504^2 X-Git-Url: http://git.cyclocoop.org/%22%20.%20generer_url_ecrire%28%22brouteur%22%2C%28%24id_rubrique%20?a=commitdiff_plain;h=3d43eaa821a5a98440c3834ddff5cccb3c9449db;p=lhc%2Fweb%2Fwiklou.git autocomplete: fix duplicate/missing tracking events The submit-form and click-result handlers are in some cases being fired for the same user interaction. The causes duplicate events to be collected from WikimediaEvents. See also Ic1e0d08d for tests of this functionality along with updates necessary to ensure all the right events are showing up on the backend. Additionally adjusts to ensure all reports of position selected/clicked are 0 indexed. Change-Id: Ie7348e470754746c85f53dbe447b88176c2a0bcc --- diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index a779b66e5d..c1b67bdd96 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -720,7 +720,7 @@ class SpecialSearch extends SpecialPage { $out .= "\n"; diff --git a/resources/src/jquery/jquery.suggestions.js b/resources/src/jquery/jquery.suggestions.js index 212c8f4a72..884ecb6e96 100644 --- a/resources/src/jquery/jquery.suggestions.js +++ b/resources/src/jquery/jquery.suggestions.js @@ -555,14 +555,14 @@ } else if ( selected.is( '.suggestions-special' ) ) { if ( typeof context.config.special.select === 'function' ) { // Allow the callback to decide whether to prevent default or not - if ( context.config.special.select.call( selected, context.data.$textbox ) === true ) { + if ( context.config.special.select.call( selected, context.data.$textbox, 'keyboard' ) === true ) { preventDefault = false; } } } else { if ( typeof context.config.result.select === 'function' ) { // Allow the callback to decide whether to prevent default or not - if ( context.config.result.select.call( selected, context.data.$textbox ) === true ) { + if ( context.config.result.select.call( selected, context.data.$textbox, 'keyboard' ) === true ) { preventDefault = false; } } @@ -675,12 +675,12 @@ if ( $result.get( 0 ) !== $other.get( 0 ) ) { return; } + $.suggestions.highlight( context, $result, true ); + if ( typeof context.config.result.select === 'function' ) { + context.config.result.select.call( $result, context.data.$textbox, 'mouse' ); + } // Don't interfere with special clicks (e.g. to open in new tab) if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) { - $.suggestions.highlight( context, $result, true ); - if ( typeof context.config.result.select === 'function' ) { - context.config.result.select.call( $result, context.data.$textbox ); - } // This will hide the link we're just clicking on, which causes problems // when done synchronously in at least Firefox 3.6 (bug 62858). setTimeout( function () { @@ -708,11 +708,11 @@ if ( $special.get( 0 ) !== $other.get( 0 ) ) { return; } + if ( typeof context.config.special.select === 'function' ) { + context.config.special.select.call( $special, context.data.$textbox, 'mouse' ); + } // Don't interfere with special clicks (e.g. to open in new tab) if ( !( e.which !== 1 || e.altKey || e.ctrlKey || e.shiftKey || e.metaKey ) ) { - if ( typeof context.config.special.select === 'function' ) { - context.config.special.select.call( $special, context.data.$textbox ); - } // This will hide the link we're just clicking on, which causes problems // when done synchronously in at least Firefox 3.6 (bug 62858). setTimeout( function () { diff --git a/resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js b/resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js index df03679980..d816335e72 100755 --- a/resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js +++ b/resources/src/mediawiki.widgets/mw.widgets.SearchInputWidget.js @@ -22,6 +22,12 @@ * used (header or content). */ mw.widgets.SearchInputWidget = function MwWidgetsSearchInputWidget( config ) { + // The parent constructors will detach this from the DOM, and won't + // be reattached until after this function is completed. As such + // grab a handle here. If no config.$input is passed tracking of + // form submissions won't work. + var $form = config.$input ? config.$input.closest( 'form' ) : $(); + config = $.extend( { type: 'search', icon: 'search', @@ -36,6 +42,7 @@ // Initialization this.$element.addClass( 'mw-widget-searchInputWidget' ); this.lookupMenu.$element.addClass( 'mw-widget-searchWidget-menu' ); + this.lastLookupItems = []; if ( !config.pushPending ) { this.pushPending = false; } @@ -46,6 +53,18 @@ this.performSearchOnClick = config.performSearchOnClick; } this.setLookupsDisabled( !this.suggestions ); + + $form.on( 'submit', function () { + mw.track( 'mw.widgets.SearchInputWidget', { + action: 'submit-form', + numberOfResults: this.lastLookupItems.length, + $form: $form, + inputLocation: this.dataLocation || 'header', + index: this.lastLookupItems.indexOf( + this.$input.val() + ) + } ); + }.bind( this ) ); }; /* Setup */ @@ -146,23 +165,27 @@ /** * @inheritdoc */ - mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function ( item ) { - var items; - - // get items which was suggested before the input changes - items = this.lookupMenu.items; - + mw.widgets.SearchInputWidget.prototype.onLookupMenuItemChoose = function () { mw.widgets.SearchInputWidget.parent.prototype.onLookupMenuItemChoose.apply( this, arguments ); - mw.track( 'mw.widgets.SearchInputWidget', { - action: 'click-result', - numberOfResults: items.length, - clickIndex: items.indexOf( item ) + 1 - } ); - if ( this.performSearchOnClick ) { this.$element.closest( 'form' ).submit(); } }; + /** + * @inheritdoc + */ + mw.widgets.SearchInputWidget.prototype.getLookupMenuOptionsFromData = function () { + var items = mw.widgets.SearchInputWidget.parent.prototype.getLookupMenuOptionsFromData.apply( + this, arguments + ); + + this.lastLookupItems = items.map( function ( item ) { + return item.data; + } ); + + return items; + }; + }( jQuery, mediaWiki ) ); diff --git a/resources/src/mediawiki/mediawiki.searchSuggest.js b/resources/src/mediawiki/mediawiki.searchSuggest.js index 2d603bf8d4..7c7aca39fa 100644 --- a/resources/src/mediawiki/mediawiki.searchSuggest.js +++ b/resources/src/mediawiki/mediawiki.searchSuggest.js @@ -138,7 +138,7 @@ mw.track( 'mediawiki.searchSuggest', { action: 'render-one', formData: formData, - index: context.config.suggestions.indexOf( text ) + 1 + index: context.config.suggestions.indexOf( text ) } ); // this is the container
, jQueryfied @@ -156,15 +156,19 @@ } // The function used when the user makes a selection - function selectFunction( $input ) { + function selectFunction( $input, source ) { var context = $input.data( 'suggestionsContext' ), text = $input.val(); - mw.track( 'mediawiki.searchSuggest', { - action: 'click-result', - numberOfResults: context.config.suggestions.length, - clickIndex: context.config.suggestions.indexOf( text ) + 1 - } ); + // Selecting via keyboard triggers a form submission. That will fire + // the submit-form event in addition to this click-result event. + if ( source !== 'keyboard' ) { + mw.track( 'mediawiki.searchSuggest', { + action: 'click-result', + numberOfResults: context.config.suggestions.length, + index: context.config.suggestions.indexOf( text ) + } ); + } // allow the form to be submitted return true; @@ -177,6 +181,12 @@ // linkParams object is modified and reused formData.linkParams[ formData.textParam ] = query; + mw.track( 'mediawiki.searchSuggest', { + action: 'render-one', + formData: formData, + index: context.config.suggestions.indexOf( query ) + } ); + if ( $el.children().length === 0 ) { $el .append( @@ -280,9 +290,20 @@ }, special: { render: specialRenderFunction, - select: function ( $input ) { - $input.closest( 'form' ) - .append( $( '' ) ); + select: function ( $input, source ) { + var context = $input.data( 'suggestionsContext' ), + text = $input.val(); + if ( source === 'mouse' ) { + // mouse click won't trigger form submission, so we need to send a click event + mw.track( 'mediawiki.searchSuggest', { + action: 'click-result', + numberOfResults: context.config.suggestions.length, + index: context.config.suggestions.indexOf( text ) + } ); + } else { + $input.closest( 'form' ) + .append( $( '' ) ); + } return true; // allow the form to be submitted } }, @@ -297,7 +318,10 @@ action: 'submit-form', numberOfResults: context.config.suggestions.length, $form: context.config.$region.closest( 'form' ), - inputLocation: getInputLocation( context ) + inputLocation: getInputLocation( context ), + index: context.config.suggestions.indexOf( + context.data.$textbox.val() + ) } ); } ) // If the form includes any fallback fulltext search buttons, remove them