mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/MultimediaViewer
synced 2024-09-29 13:17:35 +00:00
Adds new performance metrics
- duration between early click and replayed click - duration between replayed/post-replay click and first image display - duration between replayed/post-replay click and first metadata display Change-Id: Ia308db7580fd89538b9a9c8b0fea89fa2a4c77d2 Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/515 Mingle: https://wikimedia.mingle.thoughtworks.com/projects/multimedia/cards/508
This commit is contained in:
parent
8f250b4628
commit
8ab6ef23c8
|
@ -730,6 +730,7 @@ $wgResourceModules += array(
|
||||||
'mmv.lightboxinterface',
|
'mmv.lightboxinterface',
|
||||||
'mmv.provider',
|
'mmv.provider',
|
||||||
'mmv.routing',
|
'mmv.routing',
|
||||||
|
'mmv.DurationLogger',
|
||||||
'jquery.fullscreen',
|
'jquery.fullscreen',
|
||||||
'jquery.hidpi',
|
'jquery.hidpi',
|
||||||
'jquery.scrollTo',
|
'jquery.scrollTo',
|
||||||
|
@ -760,6 +761,7 @@ $wgResourceModules += array(
|
||||||
'mediawiki.Title',
|
'mediawiki.Title',
|
||||||
'mmv.logger',
|
'mmv.logger',
|
||||||
'mmv.HtmlUtils',
|
'mmv.HtmlUtils',
|
||||||
|
'mmv.DurationLogger',
|
||||||
'jquery.scrollTo',
|
'jquery.scrollTo',
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -779,6 +781,16 @@ $wgResourceModules += array(
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
'mmv.DurationLogger' => $wgMediaViewerResourceTemplate + array(
|
||||||
|
'scripts' => array(
|
||||||
|
'mmv/mmv.DurationLogger.js',
|
||||||
|
),
|
||||||
|
|
||||||
|
'dependencies' => array(
|
||||||
|
'mmv.base'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
'mmv.head' => $wgMediaViewerResourceTemplate + array(
|
'mmv.head' => $wgMediaViewerResourceTemplate + array(
|
||||||
'scripts' => array(
|
'scripts' => array(
|
||||||
'mmv/mmv.head.js',
|
'mmv/mmv.head.js',
|
||||||
|
@ -786,6 +798,7 @@ $wgResourceModules += array(
|
||||||
|
|
||||||
'dependencies' => array(
|
'dependencies' => array(
|
||||||
'mmv.base',
|
'mmv.base',
|
||||||
|
'mmv.DurationLogger'
|
||||||
),
|
),
|
||||||
|
|
||||||
'position' => 'top',
|
'position' => 'top',
|
||||||
|
@ -820,11 +833,20 @@ $wgExtensionFunctions[] = function () {
|
||||||
'revision' => 7917896,
|
'revision' => 7917896,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$wgResourceModules['schema.MultimediaViewerDuration'] = array(
|
||||||
|
'class' => 'ResourceLoaderSchemaModule',
|
||||||
|
'schema' => 'MultimediaViewerDuration',
|
||||||
|
'revision' => 8318615,
|
||||||
|
);
|
||||||
|
|
||||||
$wgResourceModules['mmv.logger']['dependencies'][] = 'ext.eventLogging';
|
$wgResourceModules['mmv.logger']['dependencies'][] = 'ext.eventLogging';
|
||||||
$wgResourceModules['mmv.logger']['dependencies'][] = 'schema.MediaViewer';
|
$wgResourceModules['mmv.logger']['dependencies'][] = 'schema.MediaViewer';
|
||||||
|
|
||||||
$wgResourceModules['mmv.performance']['dependencies'][] = 'ext.eventLogging';
|
$wgResourceModules['mmv.performance']['dependencies'][] = 'ext.eventLogging';
|
||||||
$wgResourceModules['mmv.performance']['dependencies'][] = 'schema.MultimediaViewerNetworkPerformance';
|
$wgResourceModules['mmv.performance']['dependencies'][] = 'schema.MultimediaViewerNetworkPerformance';
|
||||||
|
|
||||||
|
$wgResourceModules['mmv.DurationLogger']['dependencies'][] = 'ext.eventLogging';
|
||||||
|
$wgResourceModules['mmv.DurationLogger']['dependencies'][] = 'schema.MultimediaViewerDuration';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,7 @@ class MultimediaViewerHooks {
|
||||||
'scripts' => array(
|
'scripts' => array(
|
||||||
'tests/qunit/mmv/mmv.bootstrap.test.js',
|
'tests/qunit/mmv/mmv.bootstrap.test.js',
|
||||||
'tests/qunit/mmv/mmv.test.js',
|
'tests/qunit/mmv/mmv.test.js',
|
||||||
|
'tests/qunit/mmv/mmv.DurationLogger.test.js',
|
||||||
'tests/qunit/mmv/mmv.lightboxinterface.test.js',
|
'tests/qunit/mmv/mmv.lightboxinterface.test.js',
|
||||||
'tests/qunit/mmv/mmv.lightboximage.test.js',
|
'tests/qunit/mmv/mmv.lightboximage.test.js',
|
||||||
'tests/qunit/mmv/mmv.ThumbnailWidthCalculator.test.js',
|
'tests/qunit/mmv/mmv.ThumbnailWidthCalculator.test.js',
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"name": "Base",
|
"name": "Base",
|
||||||
"classes": [
|
"classes": [
|
||||||
"mw.mmv.Api",
|
"mw.mmv.Api",
|
||||||
|
"mw.mmv.DurationLogger",
|
||||||
"mw.mmv.EmbedFileFormatter",
|
"mw.mmv.EmbedFileFormatter",
|
||||||
"mw.mmv.HtmlUtils",
|
"mw.mmv.HtmlUtils",
|
||||||
"mw.mmv.LightboxImage",
|
"mw.mmv.LightboxImage",
|
||||||
|
|
87
resources/mmv/mmv.DurationLogger.js
Normal file
87
resources/mmv/mmv.DurationLogger.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* This file is part of the MediaWiki extension MultimediaViewer.
|
||||||
|
*
|
||||||
|
* MultimediaViewer is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* MultimediaViewer is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with MultimediaViewer. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
( function ( mw, $ ) {
|
||||||
|
var L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes Event Logging entries for duration measurements
|
||||||
|
* @class mw.mmv.DurationLogger
|
||||||
|
*/
|
||||||
|
function DurationLogger() {
|
||||||
|
this.starts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
L = DurationLogger.prototype;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the start of a duration
|
||||||
|
* @param {string|string[]} type_or_types Type(s) of duration being measured.
|
||||||
|
*/
|
||||||
|
L.start = function ( typeOrTypes ) {
|
||||||
|
var i,
|
||||||
|
start = $.now();
|
||||||
|
|
||||||
|
if ( $.isArray( typeOrTypes ) ) {
|
||||||
|
for ( i = 0; i < typeOrTypes.length; i++ ) {
|
||||||
|
// Don't overwrite an existing value
|
||||||
|
if ( !this.starts.hasOwnProperty( typeOrTypes[ i ] ) ) {
|
||||||
|
this.starts[ typeOrTypes[ i ] ] = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't overwrite an existing value
|
||||||
|
} else if ( typeOrTypes && !this.starts.hasOwnProperty( typeOrTypes ) ) {
|
||||||
|
this.starts[ typeOrTypes ] = start;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs a duration if a start was recorded first
|
||||||
|
* @param {string} type Type of duration being measured.
|
||||||
|
*/
|
||||||
|
L.stop = function ( type ) {
|
||||||
|
var e, duration;
|
||||||
|
|
||||||
|
if ( this.starts.hasOwnProperty( type ) ) {
|
||||||
|
duration = $.now() - this.starts[ type ];
|
||||||
|
|
||||||
|
e = {
|
||||||
|
type : type,
|
||||||
|
duration : duration,
|
||||||
|
loggedIn : !mw.user.isAnon()
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( $.isPlainObject( window.Geo ) && typeof window.Geo.country === 'string' ) {
|
||||||
|
e.country = window.Geo.country;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mw.eventLog ) {
|
||||||
|
mw.eventLog.logEvent( 'MultimediaViewerDuration', e );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( window.console && window.console.log ) {
|
||||||
|
window.console.log( type + ': ' + duration + 'ms' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( this.starts.hasOwnProperty( type ) ) {
|
||||||
|
delete this.starts[ type ];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mw.mmv.DurationLogger = new DurationLogger();
|
||||||
|
}( mediaWiki, jQuery ) );
|
|
@ -250,6 +250,8 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mw.mmv.DurationLogger.start( [ 'click-to-first-image', 'click-to-first-metadata' ] );
|
||||||
|
|
||||||
if ( $element.is( 'a.image' ) ) {
|
if ( $element.is( 'a.image' ) ) {
|
||||||
mw.mmv.logger.log( 'thumbnail' );
|
mw.mmv.logger.log( 'thumbnail' );
|
||||||
} else if ( $element.is( '.magnify a' ) ) {
|
} else if ( $element.is( '.magnify a' ) ) {
|
||||||
|
|
|
@ -35,11 +35,15 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mw.mmv.DurationLogger.start( 'early-click-to-replay-click' );
|
||||||
|
|
||||||
// We wait for document readiness because mw.loader.using writes to the DOM
|
// We wait for document readiness because mw.loader.using writes to the DOM
|
||||||
// which can cause a blank page if it happens before DOM readiness
|
// which can cause a blank page if it happens before DOM readiness
|
||||||
$document.ready( function () {
|
$document.ready( function () {
|
||||||
mw.loader.using( 'mmv.bootstrap.autostart', function() {
|
mw.loader.using( 'mmv.bootstrap.autostart', function() {
|
||||||
mw.mmv.bootstrap.whenThumbsReady().then( function () {
|
mw.mmv.bootstrap.whenThumbsReady().then( function () {
|
||||||
|
mw.mmv.DurationLogger.stop( 'early-click-to-replay-click' );
|
||||||
|
|
||||||
// We have to copy the properties, passing e doesn't work. Probably because of preventDefault()
|
// We have to copy the properties, passing e doesn't work. Probably because of preventDefault()
|
||||||
$( e.target ).trigger( { type : 'click', which: 1, replayed: true } );
|
$( e.target ).trigger( { type : 'click', which: 1, replayed: true } );
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -110,6 +110,18 @@
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.ui = new mw.mmv.LightboxInterface();
|
this.ui = new mw.mmv.LightboxInterface();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many sharp images have been displayed in Media Viewer since the pageload
|
||||||
|
* @property {number}
|
||||||
|
*/
|
||||||
|
this.imageDisplayedCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many data-filled metadata panels have been displayed in Media Viewer since the pageload
|
||||||
|
* @property {number}
|
||||||
|
*/
|
||||||
|
this.metadataDisplayedCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
MMVP = MultimediaViewer.prototype;
|
MMVP = MultimediaViewer.prototype;
|
||||||
|
@ -282,6 +294,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( viewer.imageDisplayedCount++ === 0 ) {
|
||||||
|
mw.mmv.DurationLogger.stop( 'click-to-first-image' );
|
||||||
|
}
|
||||||
viewer.displayRealThumbnail( thumbnail, imageElement, imageWidths, $.now() - start );
|
viewer.displayRealThumbnail( thumbnail, imageElement, imageWidths, $.now() - start );
|
||||||
} ).fail( function ( error ) {
|
} ).fail( function ( error ) {
|
||||||
viewer.ui.canvas.showError( error );
|
viewer.ui.canvas.showError( error );
|
||||||
|
@ -294,6 +309,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( viewer.metadataDisplayedCount++ === 0 ) {
|
||||||
|
mw.mmv.DurationLogger.stop( 'click-to-first-metadata' );
|
||||||
|
}
|
||||||
viewer.ui.panel.setImageInfo( image, imageInfo, repoInfo, localUsage, globalUsage, userInfo );
|
viewer.ui.panel.setImageInfo( image, imageInfo, repoInfo, localUsage, globalUsage, userInfo );
|
||||||
} ).fail( function ( error ) {
|
} ).fail( function ( error ) {
|
||||||
if ( viewer.currentIndex !== image.index ) {
|
if ( viewer.currentIndex !== image.index ) {
|
||||||
|
|
74
tests/qunit/mmv/mmv.DurationLogger.test.js
Executable file
74
tests/qunit/mmv/mmv.DurationLogger.test.js
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
( function ( mw, $ ) {
|
||||||
|
QUnit.module( 'mmv.DurationLogger', QUnit.newMwEnvironment({
|
||||||
|
setup: function () {
|
||||||
|
this.clock = this.sandbox.useFakeTimers();
|
||||||
|
}
|
||||||
|
} ) );
|
||||||
|
|
||||||
|
QUnit.test( 'start()', 7, function ( assert ) {
|
||||||
|
var durationLogger = new mw.mmv.DurationLogger.constructor();
|
||||||
|
|
||||||
|
durationLogger.start();
|
||||||
|
assert.ok( $.isEmptyObject( durationLogger.starts ), 'No events saved by DurationLogger' );
|
||||||
|
|
||||||
|
durationLogger.start( 'foo' );
|
||||||
|
assert.strictEqual( durationLogger.starts.foo, 0, 'Event start saved' );
|
||||||
|
|
||||||
|
this.clock.tick( 1000 );
|
||||||
|
durationLogger.start( 'bar' );
|
||||||
|
assert.strictEqual( durationLogger.starts.bar, 1000, 'Later event start saved' );
|
||||||
|
|
||||||
|
durationLogger.start( 'foo' );
|
||||||
|
assert.strictEqual( durationLogger.starts.foo, 0, 'Event start not overritten' );
|
||||||
|
|
||||||
|
this.clock.tick( 666 );
|
||||||
|
durationLogger.start( [ 'baz', 'bob', 'bar' ] );
|
||||||
|
assert.strictEqual( durationLogger.starts.baz, 1666, 'First simultaneous event start saved' );
|
||||||
|
assert.strictEqual( durationLogger.starts.bob, 1666, 'Second simultaneous event start saved' );
|
||||||
|
assert.strictEqual( durationLogger.starts.bar, 1000, 'Third simultaneous event start not overwritten' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
QUnit.test( 'stop()', 6, function ( assert ) {
|
||||||
|
var logEvent,
|
||||||
|
durationLogger = new mw.mmv.DurationLogger.constructor(),
|
||||||
|
fakeEventLogging = false,
|
||||||
|
fakeGeo = false;
|
||||||
|
|
||||||
|
if ( window.Geo === undefined ) {
|
||||||
|
window.Geo = { country : 'FR' };
|
||||||
|
fakeGeo = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( mw.eventLog === undefined ) {
|
||||||
|
mw.eventLog = { logEvent : $.noop };
|
||||||
|
fakeEventLogging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sandbox.stub( mw.user, 'isAnon', function() { return false; } );
|
||||||
|
this.sandbox.stub( window.Geo, 'country', 'FR' );
|
||||||
|
|
||||||
|
logEvent = this.sandbox.stub( mw.eventLog, 'logEvent', function( schema, e ) {
|
||||||
|
assert.strictEqual( e.type, 'bar', 'Type passed to EventLogging is correct' );
|
||||||
|
assert.strictEqual( e.duration, 1000, 'Duration passed to EventLogging is correct' );
|
||||||
|
assert.strictEqual( e.loggedIn, true, 'Loggedin information passed to EventLogging is correct' );
|
||||||
|
assert.strictEqual( e.country, 'FR', 'Country passed to EventLogging is correct' );
|
||||||
|
} );
|
||||||
|
|
||||||
|
durationLogger.stop( 'foo' );
|
||||||
|
assert.ok( !logEvent.called, 'Stop without a start doesn\'t get logged' );
|
||||||
|
|
||||||
|
durationLogger.start( 'bar' );
|
||||||
|
this.clock.tick( 1000 );
|
||||||
|
durationLogger.stop( 'bar' );
|
||||||
|
|
||||||
|
assert.strictEqual( durationLogger.starts.bar, undefined, 'Start value deleted after stop' );
|
||||||
|
|
||||||
|
if ( fakeGeo ) {
|
||||||
|
delete window.Geo;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( fakeEventLogging ) {
|
||||||
|
delete mw.eventLog;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}( mediaWiki, jQuery ) );
|
Loading…
Reference in a new issue