jenkins-bot has submitted this change and it was merged.

Change subject: Track the most recent upload time for performance events
......................................................................


Track the most recent upload time for performance events

Change-Id: I673f9487deea15dc148452a3a4d6b91563a2c417
Bug: T76035
---
M MultimediaViewer.php
M resources/mmv/logging/mmv.logging.PerformanceLogger.js
M resources/mmv/mmv.js
M resources/mmv/mmv.lightboximage.js
M resources/mmv/model/mmv.model.Image.js
M resources/mmv/provider/mmv.provider.Image.js
M tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js
M tests/qunit/mmv/mmv.EmbedFileFormatter.test.js
M tests/qunit/mmv/mmv.test.js
M tests/qunit/mmv/model/mmv.model.Image.test.js
M tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js
11 files changed, 181 insertions(+), 86 deletions(-)

Approvals:
  Gergő Tisza: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/MultimediaViewer.php b/MultimediaViewer.php
index f69e4ae..e8f8628 100644
--- a/MultimediaViewer.php
+++ b/MultimediaViewer.php
@@ -1063,7 +1063,7 @@
 $wgHooks['EventLoggingRegisterSchemas'][] = function( array &$schemas ) {
        $schemas += array(
                'MediaViewer' => 10536413,
-               'MultimediaViewerNetworkPerformance' => 7917896,
+               'MultimediaViewerNetworkPerformance' => 10596581,
                'MultimediaViewerDuration' => 10427980,
                'MultimediaViewerAttribution' => 9758179,
                'MultimediaViewerDimensions' => 10014238,
diff --git a/resources/mmv/logging/mmv.logging.PerformanceLogger.js 
b/resources/mmv/logging/mmv.logging.PerformanceLogger.js
index bfde462..2ac6e1b 100644
--- a/resources/mmv/logging/mmv.logging.PerformanceLogger.js
+++ b/resources/mmv/logging/mmv.logging.PerformanceLogger.js
@@ -62,9 +62,10 @@
         * cached by the browser, as it will consume unnecessary bandwidth for 
the user.
         * @param {string} type the type of request to be measured
         * @param {string} url URL to be measured
+        * @param {jQuery.Deferred.<string>} [extraStatsDeferred] A promise 
which resolves to the extra stats.
         * @returns {jQuery.Promise} A promise that resolves when the contents 
of the URL have been fetched
         */
-       PL.record = function ( type, url ) {
+       PL.record = function ( type, url, extraStatsDeferred ) {
                var deferred = $.Deferred(),
                        request,
                        perf = this,
@@ -89,7 +90,7 @@
                                if ( request.readyState === 4 ) {
                                        deferred.notify( request.response, 100 
);
                                        deferred.resolve( request.response );
-                                       perf.recordEntryDelayed( type, total, 
url, request );
+                                       perf.recordEntryDelayed( type, total, 
url, request, extraStatsDeferred );
                                }
                        };
 
@@ -111,9 +112,11 @@
         * @param {number} total the total load time tracked with a basic 
technique
         * @param {string} url URL of that was measured
         * @param {XMLHttpRequest} request HTTP request that just completed
+        * @param {jQuery.Deferred.<string>} [extraStatsDeferred] A promise 
which resolves to extra stats to be included.
         */
-       PL.recordEntry = function ( type, total, url, request ) {
+       PL.recordEntry = function ( type, total, url, request, 
extraStatsDeferred ) {
                var matches,
+                       logger = this,
                        stats = { type: type,
                                contentHost: window.location.host,
                                isHttps: window.location.protocol === 'https:',
@@ -165,7 +168,11 @@
                        }
                }
 
-               this.log( stats );
+               ( extraStatsDeferred || $.Deferred().reject() ).done( function 
( extraStats ) {
+                       stats = $.extend( stats, extraStats );
+               } ).always( function () {
+                       logger.log( stats );
+               } );
        };
 
        /**
@@ -305,14 +312,15 @@
         * @param {number} total the total load time tracked with a basic 
technique
         * @param {string} url URL of that was measured
         * @param {XMLHttpRequest} request HTTP request that just completed
+        * @param {jQuery.Promise.<string>} extraStatsDeferred A promise which 
resolves to extra stats.
         */
-       PL.recordEntryDelayed = function ( type, total, url, request ) {
+       PL.recordEntryDelayed = function ( type, total, url, request, 
extraStatsDeferred ) {
                var perf = this;
 
                // The timeout is necessary because if there's an entry in 
window.performance,
                // it hasn't been added yet at this point
                setTimeout( function() {
-                       perf.recordEntry( type, total, url, request );
+                       perf.recordEntry( type, total, url, request, 
extraStatsDeferred );
                }, 0 );
        };
 
@@ -402,6 +410,15 @@
                return new XMLHttpRequest();
        };
 
+       /**
+        * @override
+        * @inheritdoc
+        */
+       PL.log = function ( data ) {
+               mw.log( 'mw.mmv.logging.PerformanceLogger', data );
+               return mw.mmv.logging.Logger.prototype.log.call( this, data );
+       };
+
        new PerformanceLogger().init();
 
        mw.mmv.logging.PerformanceLogger = PerformanceLogger;
diff --git a/resources/mmv/mmv.js b/resources/mmv/mmv.js
index 99ebb92..00ed4af 100644
--- a/resources/mmv/mmv.js
+++ b/resources/mmv/mmv.js
@@ -157,6 +157,8 @@
                                thumb.thumb,
                                thumb.caption
                        );
+
+                       thumb.extraStatsDeferred = $.Deferred();
                }
        };
 
@@ -249,9 +251,9 @@
                        imagePromise,
                        metadataPromise,
                        start,
-                       uploadTimestamp,
                        viewer = this,
-                       $initialImage = $( initialImage );
+                       $initialImage = $( initialImage ),
+                       extraStatsDeferred = $.Deferred();
 
                this.currentIndex = image.index;
 
@@ -282,7 +284,8 @@
                start = $.now();
 
                mw.mmv.dimensionLogger.logDimensions( imageWidths, 
canvasDimensions, 'show' );
-               imagePromise = this.fetchThumbnailForLightboxImage( image, 
imageWidths.real );
+
+               imagePromise = this.fetchThumbnailForLightboxImage( image, 
imageWidths.real, extraStatsDeferred );
 
                this.resetBlurredThumbnailStates();
                if ( imagePromise.state() === 'pending' ) {
@@ -302,19 +305,12 @@
                                mw.mmv.durationLogger.stop( 
'click-to-first-image' );
 
                                metadataPromise.done( function ( imageInfo ) {
-                                       if ( !imageInfo || 
!imageInfo.uploadDateTime ) {
+                                       if ( !imageInfo || 
!imageInfo.anonymizedUploadDateTime ) {
                                                return;
                                        }
 
-                                       uploadTimestamp = 
imageInfo.uploadDateTime.toString();
-                                       // Convert to "timestamp" format 
commonly used in EventLogging
-                                       uploadTimestamp = 
uploadTimestamp.replace( /[:\s]/g, '' );
-                                       // Anonymise the timestamp to avoid 
making the file identifiable
-                                       // We only need to know the day
-                                       uploadTimestamp = 
uploadTimestamp.substr( 0, uploadTimestamp.length - 6 ) + '000000';
-
                                        mw.mmv.durationLogger.record( 
'click-to-first-image', {
-                                               uploadTimestamp: uploadTimestamp
+                                               uploadTimestamp: 
imageInfo.anonymizedUploadDateTime
                                        } );
                                } );
                        }
@@ -324,6 +320,8 @@
                } );
 
                metadataPromise.done( function ( imageInfo, repoInfo, userInfo 
) {
+                       extraStatsDeferred.resolve( { uploadTimestamp: 
imageInfo.anonymizedUploadDateTime } );
+
                        if ( viewer.currentIndex !== image.index ) {
                                return;
                        }
@@ -337,6 +335,8 @@
                        // File reuse steals a bunch of information from the 
DOM, so do it last
                        viewer.ui.setFileReuseData( imageInfo, repoInfo, 
image.caption );
                } ).fail( function ( error ) {
+                       extraStatsDeferred.reject();
+
                        if ( viewer.currentIndex !== image.index ) {
                                return;
                        }
@@ -579,13 +579,15 @@
                        if ( this.currentIndex + i < this.thumbs.length ) {
                                callback(
                                        this.currentIndex + i,
-                                       this.thumbs[ this.currentIndex + i 
].image
+                                       this.thumbs[ this.currentIndex + i 
].image,
+                                       this.thumbs[ this.currentIndex + i 
].extraStatsDeferred
                                );
                        }
                        if ( i && this.currentIndex - i >= 0 ) { // skip 
duplicate for i==0
                                callback(
                                        this.currentIndex - i,
-                                       this.thumbs[ this.currentIndex - i 
].image
+                                       this.thumbs[ this.currentIndex - i 
].image,
+                                       this.thumbs[ this.currentIndex - i 
].extraStatsDeferred
                                );
                        }
                }
@@ -601,8 +603,8 @@
        MMVP.pushLightboxImagesIntoQueue = function( taskFactory ) {
                var queue = new mw.mmv.model.TaskQueue();
 
-               this.eachPrealoadableLightboxIndex( function( i, lightboxImage 
) {
-                       queue.push( taskFactory( lightboxImage ) );
+               this.eachPrealoadableLightboxIndex( function( i, lightboxImage, 
extraStatsDeferred ) {
+                       queue.push( taskFactory( lightboxImage, 
extraStatsDeferred ) );
                } );
 
                return queue;
@@ -636,9 +638,15 @@
 
                this.cancelImageMetadataPreloading();
 
-               this.metadataPreloadQueue = this.pushLightboxImagesIntoQueue( 
function( lightboxImage ) {
+               this.metadataPreloadQueue = this.pushLightboxImagesIntoQueue( 
function( lightboxImage, extraStatsDeferred ) {
                        return function() {
-                               return viewer.fetchSizeIndependentLightboxInfo( 
lightboxImage.filePageTitle );
+                               var metadatapromise = 
viewer.fetchSizeIndependentLightboxInfo( lightboxImage.filePageTitle );
+                               metadatapromise.done( function ( imageInfo ) {
+                                       extraStatsDeferred.resolve( { 
uploadTimestamp: imageInfo.anonymizedUploadDateTime } );
+                               } ).fail( function () {
+                                       extraStatsDeferred.reject();
+                               } );
+                               return metadatapromise;
                        };
                } );
 
@@ -655,7 +663,7 @@
 
                this.cancelThumbnailsPreloading();
 
-               this.thumbnailPreloadQueue = this.pushLightboxImagesIntoQueue( 
function( lightboxImage ) {
+               this.thumbnailPreloadQueue = this.pushLightboxImagesIntoQueue( 
function( lightboxImage, extraStatsDeferred ) {
                        return function() {
                                var imageWidths, canvasDimensions;
 
@@ -670,7 +678,7 @@
 
                                mw.mmv.dimensionLogger.logDimensions( 
imageWidths, canvasDimensions, 'preload' );
 
-                               return viewer.fetchThumbnailForLightboxImage( 
lightboxImage, imageWidths.real );
+                               return viewer.fetchThumbnailForLightboxImage( 
lightboxImage, imageWidths.real, extraStatsDeferred );
                        };
                } );
 
@@ -721,15 +729,17 @@
         * Loads size-dependent components of a lightbox - the thumbnail model 
and the image itself.
         * @param {mw.mmv.LightboxImage} image
         * @param {number} width the width of the requested thumbnail
+        * @param {jQuery.Deferred.<string>} [extraStatsDeferred] Promise that 
resolves to the image's upload timestamp when the metadata is loaded
         * @returns {jQuery.Promise.<mw.mmv.model.Thumbnail, HTMLImageElement>}
         */
-       MMVP.fetchThumbnailForLightboxImage = function ( image, width ) {
+       MMVP.fetchThumbnailForLightboxImage = function ( image, width, 
extraStatsDeferred ) {
                return this.fetchThumbnail(
                        image.filePageTitle,
                        width,
                        image.src,
                        image.originalWidth,
-                       image.originalHeight
+                       image.originalHeight,
+                       extraStatsDeferred
                );
        };
 
@@ -740,11 +750,12 @@
         * @param {string} [sampleUrl] a thumbnail URL for the same file (but 
with different size) (might be missing)
         * @param {number} [originalWidth] the width of the original, 
full-sized file (might be missing)
         * @param {number} [originalHeight] the height of the original, 
full-sized file (might be missing)
+        * @param {jQuery.Deferred.<string>} [extraStatsDeferred] Promise that 
resolves to the image's upload timestamp when the metadata is loaded
         * @returns {jQuery.Promise.<mw.mmv.model.Thumbnail, HTMLImageElement>} 
A promise resolving to
         *  a thumbnail model and an <img> element. It might or might not have 
progress events which
         *  return a single number.
         */
-       MMVP.fetchThumbnail = function ( fileTitle, width, sampleUrl, 
originalWidth, originalHeight ) {
+       MMVP.fetchThumbnail = function ( fileTitle, width, sampleUrl, 
originalWidth, originalHeight, extraStatsDeferred ) {
                var viewer = this,
                        guessing = false,
                        thumbnailPromise,
@@ -771,7 +782,7 @@
                }
 
                imagePromise = thumbnailPromise.then( function ( thumbnail ) {
-                       return viewer.imageProvider.get( thumbnail.url );
+                       return viewer.imageProvider.get( thumbnail.url, 
extraStatsDeferred );
                } );
 
                if ( guessing ) {
@@ -780,7 +791,7 @@
                        // because thumbnailInfoProvider.get is already called 
above when guessedThumbnailInfoProvider.get fails.
                        imagePromise = imagePromise.then( null, function () {
                                return viewer.thumbnailInfoProvider.get( 
fileTitle, width ).then( function ( thumbnail ) {
-                                       return viewer.imageProvider.get( 
thumbnail.url );
+                                       return viewer.imageProvider.get( 
thumbnail.url, extraStatsDeferred );
                                } );
                        } );
                }
diff --git a/resources/mmv/mmv.lightboximage.js 
b/resources/mmv/mmv.lightboximage.js
index 0916720..ddb1418 100644
--- a/resources/mmv/mmv.lightboximage.js
+++ b/resources/mmv/mmv.lightboximage.js
@@ -54,23 +54,5 @@
                this.originalHeight = undefined;
        }
 
-       var LIP = LightboxImage.prototype;
-
-       /**
-        * The URL of the image (in the size we intend use to display the it in 
the lightbox)
-        * @type {String}
-        * @protected
-        */
-       LIP.src = null;
-
-       /**
-        * The URL of a placeholder while the image loads. Typically a smaller 
version of the image, which is already
-        * loaded in the browser.
-        * @type {String}
-        * @return {jQuery.Promise.<mw.mmv.LightboxImage, HTMLImageElement>}
-        * @protected
-        */
-       LIP.initialSrc = null;
-
        mw.mmv.LightboxImage = LightboxImage;
 }( mediaWiki, jQuery ) );
diff --git a/resources/mmv/model/mmv.model.Image.js 
b/resources/mmv/model/mmv.model.Image.js
index 59382ed..c5c3b06 100644
--- a/resources/mmv/model/mmv.model.Image.js
+++ b/resources/mmv/model/mmv.model.Image.js
@@ -33,6 +33,7 @@
         * @param {string} repo The repository this image belongs to
         * @param {string} lastUploader The last person to upload a version of 
this image.
         * @param {string} uploadDateTime The time and date the last upload 
occurred
+        * @param {string} anonymizedUploadDateTime Anonymized and EL-friendly 
version of uploadDateTime
         * @param {string} creationDateTime The time and date the original 
upload occurred
         * @param {string} description
         * @param {string} source
@@ -55,6 +56,7 @@
                        repo,
                        lastUploader,
                        uploadDateTime,
+                       anonymizedUploadDateTime,
                        creationDateTime,
                        description,
                        source,
@@ -97,6 +99,9 @@
 
                /** @property {string} uploadDateTime The date and time of the 
last upload */
                this.uploadDateTime = uploadDateTime;
+
+               /** @property {string} anonymizedUploadDateTime The anonymized 
date and time of the last upload */
+               this.anonymizedUploadDateTime = anonymizedUploadDateTime;
 
                /** @property {string} creationDateTime The date and time that 
the image was created */
                this.creationDateTime = creationDateTime;
@@ -144,7 +149,7 @@
         * @returns {mw.mmv.model.Image}
         */
        Image.newFromImageInfo = function ( title, imageInfo ) {
-               var name, uploadDateTime, creationDateTime, imageData,
+               var name, uploadDateTime, anonymizedUploadDateTime, 
creationDateTime, imageData,
                        description, source, author, authorCount, license, 
permission,
                        latitude, longitude,
                        innerInfo = imageInfo.imageinfo[0],
@@ -152,7 +157,15 @@
 
                if ( extmeta ) {
                        creationDateTime = this.parseExtmeta( 
extmeta.DateTimeOriginal, 'plaintext' );
-                       uploadDateTime = this.parseExtmeta( extmeta.DateTime, 
'plaintext' );
+                       uploadDateTime = this.parseExtmeta( extmeta.DateTime, 
'plaintext' ).toString();
+
+                       // Convert to "timestamp" format commonly used in 
EventLogging
+                       anonymizedUploadDateTime = uploadDateTime.replace( 
/[^\d]/g, '' );
+
+                       // Anonymise the timestamp to avoid making the file 
identifiable
+                       // We only need to know the day
+                       anonymizedUploadDateTime = 
anonymizedUploadDateTime.substr( 0, anonymizedUploadDateTime.length - 6 ) + 
'000000';
+
                        name = this.parseExtmeta( extmeta.ObjectName, 
'plaintext' );
 
                        description = this.parseExtmeta( 
extmeta.ImageDescription, 'string' );
@@ -172,6 +185,7 @@
                        name = title.getNameText();
                }
 
+
                imageData = new Image(
                        title,
                        name,
@@ -184,6 +198,7 @@
                        imageInfo.imagerepository,
                        innerInfo.user,
                        uploadDateTime,
+                       anonymizedUploadDateTime,
                        creationDateTime,
                        description,
                        source,
diff --git a/resources/mmv/provider/mmv.provider.Image.js 
b/resources/mmv/provider/mmv.provider.Image.js
index 2eccd43..fcd3ddf 100644
--- a/resources/mmv/provider/mmv.provider.Image.js
+++ b/resources/mmv/provider/mmv.provider.Image.js
@@ -44,11 +44,12 @@
         * Loads an image and returns it. Includes performance metrics via 
mw.mmv.logging.PerformanceLogger.
         * When the browser supports it, the image is loaded as an AJAX request.
         * @param {string} url
+        * @param {jQuery.Deferred.<string>} extraStatsDeferred A promise which 
resolves to extra statistics.
         * @return {jQuery.Promise.<HTMLImageElement>} A promise which resolves 
to the image object.
         *  When loaded via AJAX, it has progress events, which return an array 
with the content loaded
         *  so far and with the progress as a floating-point number between 0 
and 100.
         */
-       Image.prototype.get = function ( url ) {
+       Image.prototype.get = function ( url, extraStatsDeferred ) {
                var provider = this,
                        cacheKey = url,
                        extraParam = {},
@@ -65,12 +66,12 @@
                if ( !this.cache[cacheKey] ) {
                        if ( this.imagePreloadingSupported() ) {
                                rawGet = $.proxy( provider.rawGet, provider, 
url, true );
-                               this.cache[cacheKey] = this.performance.record( 
'image', url ).then( rawGet, rawGet );
+                               this.cache[cacheKey] = this.performance.record( 
'image', url, extraStatsDeferred ).then( rawGet, rawGet );
                        } else {
                                start = $.now();
                                this.cache[cacheKey] = this.rawGet( url );
                                this.cache[cacheKey].always( function () {
-                                       provider.performance.recordEntry( 
'image', $.now() - start, url );
+                                       provider.performance.recordEntry( 
'image', $.now() - start, url, undefined, extraStatsDeferred );
                                } );
                        }
                        this.cache[cacheKey].fail( function ( error ) {
diff --git a/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js 
b/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js
index be38879..3d97453 100644
--- a/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js
+++ b/tests/qunit/mmv/logging/mmv.logging.PerformanceLogger.test.js
@@ -186,6 +186,49 @@
                assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 1 
].bandwidth, Math.round( bandwidth ), 'bandwidth is correct' );
        } );
 
+       QUnit.test( 'recordEntry: with async extra stats', 11, function ( 
assert ) {
+               var performance = new mw.mmv.logging.PerformanceLogger(),
+                       fakeEventLog = { logEvent: this.sandbox.stub() },
+                       type = 'gender',
+                       total = 100,
+                       overriddenType = 'image',
+                       foo = 'bar',
+                       extraStatsPromise = $.Deferred();
+
+               this.sandbox.stub( performance, 'loadDependencies' ).returns( 
$.Deferred().resolve() );
+               this.sandbox.stub( performance, 'isInSample' );
+               performance.setEventLog( fakeEventLog );
+
+               performance.isInSample.returns( true );
+
+               performance.recordEntry( type, total, 'URL1', undefined, 
extraStatsPromise );
+
+               assert.strictEqual( fakeEventLog.logEvent.callCount, 0, 'Stats 
should not be logged if the promise hasn\'t completed yet' );
+
+               extraStatsPromise.reject();
+
+               assert.strictEqual( fakeEventLog.logEvent.callCount, 1, 'Stats 
should be logged' );
+
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 0 
], 'MultimediaViewerNetworkPerformance', 'EventLogging schema is correct' );
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 1 
].type, type, 'type is correct' );
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 0 ).args[ 1 
].total, total, 'total is correct' );
+
+               extraStatsPromise = $.Deferred();
+
+               performance.recordEntry( type, total, 'URL2', undefined, 
extraStatsPromise );
+
+               assert.strictEqual( fakeEventLog.logEvent.callCount, 1, 'Stats 
should not be logged if the promise hasn\'t been resolved yet' );
+
+               extraStatsPromise.resolve( { type: overriddenType, foo: foo } );
+
+               assert.strictEqual( fakeEventLog.logEvent.callCount, 2, 'Stats 
should be logged' );
+
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 0 
], 'MultimediaViewerNetworkPerformance', 'EventLogging schema is correct' );
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 1 
].type, overriddenType, 'type is correct' );
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 1 
].total, total, 'total is correct' );
+               assert.strictEqual( fakeEventLog.logEvent.getCall( 1 ).args[ 1 
].foo, foo, 'extra stat is correct' );
+       } );
+
        QUnit.test( 'parseVarnishXCacheHeader', 15, function ( assert ) {
                var varnish1 = 'cp1061',
                        varnish2 = 'cp3006',
diff --git a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js 
b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js
index 10d6aca..b7875a0 100644
--- a/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js
+++ b/tests/qunit/mmv/mmv.EmbedFileFormatter.test.js
@@ -6,7 +6,7 @@
                                options.licenseInternalName, 
options.licenseLongName, options.licenseUrl ) : undefined,
                        imageInfo = new mw.mmv.model.Image( options.title, 
options.title.getNameText(), undefined,
                                undefined, undefined, undefined, 
options.imgUrl, options.filePageUrl, 'repo', undefined,
-                               undefined, undefined, undefined, 
options.source, options.author, options.authorCount, license ),
+                               undefined, undefined, undefined, undefined, 
options.source, options.author, options.authorCount, license ),
                        repoInfo = { displayName: options.siteName, getSiteLink:
                                function () { return options.siteUrl; } };
 
diff --git a/tests/qunit/mmv/mmv.test.js b/tests/qunit/mmv/mmv.test.js
index 50b1e09..28019f9 100644
--- a/tests/qunit/mmv/mmv.test.js
+++ b/tests/qunit/mmv/mmv.test.js
@@ -102,21 +102,25 @@
 
        QUnit.test( 'Progress', 4, function ( assert ) {
                var imageDeferred = $.Deferred(),
-                       viewer = new mw.mmv.MultimediaViewer( { get : $.noop } 
);
+                       viewer = new mw.mmv.MultimediaViewer( { get : $.noop } 
),
+                       fakeImage = {
+                               filePageTitle: new mw.Title( 'File:Stuff.jpg' ),
+                               extraStatsDeferred: $.Deferred().reject()
+                       };
 
                viewer.thumbs = [];
                viewer.displayPlaceholderThumbnail = $.noop;
                viewer.setImage = $.noop;
                viewer.scroll = $.noop;
                viewer.preloadFullscreenThumbnail = $.noop;
-               viewer.fetchSizeIndependentLightboxInfo = function () { return 
$.Deferred().resolve(); };
+               viewer.fetchSizeIndependentLightboxInfo = function () { return 
$.Deferred().resolve( {} ); };
                viewer.ui = {
                        setFileReuseData: $.noop,
                        setupForLoad: $.noop,
-                       canvas: { set : $.noop,
+                       canvas: { set: $.noop,
                                unblurWithAnimation: $.noop,
                                unblur: $.noop,
-                               getCurrentImageWidths: function () { return { 
real : 0 }; },
+                               getCurrentImageWidths: function () { return { 
real: 0 }; },
                                getDimensions: function () { return {}; }
                        },
                        panel: {
@@ -132,10 +136,10 @@
                        open: $.noop };
 
                viewer.imageProvider.get = function() { return 
imageDeferred.promise(); };
-               viewer.imageInfoProvider.get = function() { return 
$.Deferred().resolve(); };
+               viewer.imageInfoProvider.get = function() { return 
$.Deferred().resolve( {} ); };
                viewer.thumbnailInfoProvider.get = function() { return 
$.Deferred().resolve( {} ); };
 
-               viewer.loadImage( { filePageTitle : new mw.Title( 
'File:Stuff.jpg' ) }, new Image() );
+               viewer.loadImage( fakeImage, new Image() );
                assert.ok( 
viewer.ui.panel.progressBar.jumpTo.lastCall.calledWith( 0 ),
                        'Percentage correctly reset by loadImage' );
 
@@ -156,8 +160,16 @@
        QUnit.test( 'Progress when switching images', 11, function ( assert ) {
                var firstImageDeferred = $.Deferred(),
                        secondImageDeferred = $.Deferred(),
-                       firstImage = { index: 1, filePageTitle : new mw.Title( 
'File:First.jpg' ) },
-                       secondImage = { index: 2, filePageTitle : new mw.Title( 
'File:Second.jpg' ) },
+                       firstImage = {
+                               index: 1,
+                               filePageTitle: new mw.Title( 'File:First.jpg' ),
+                               extraStatsDeferred: $.Deferred().reject()
+                       },
+                       secondImage = {
+                               index: 2,
+                               filePageTitle: new mw.Title( 'File:Second.jpg' 
),
+                               extraStatsDeferred: $.Deferred().reject()
+                       },
                        viewer = new mw.mmv.MultimediaViewer( { get : $.noop } 
);
 
                viewer.thumbs = [];
@@ -167,7 +179,7 @@
                viewer.preloadFullscreenThumbnail = $.noop;
                viewer.preloadImagesMetadata = $.noop;
                viewer.preloadThumbnails = $.noop;
-               viewer.fetchSizeIndependentLightboxInfo = function () { return 
$.Deferred().resolve(); };
+               viewer.fetchSizeIndependentLightboxInfo = function () { return 
$.Deferred().resolve( {} ); };
                viewer.ui = {
                        setFileReuseData: $.noop,
                        setupForLoad : $.noop,
@@ -191,7 +203,7 @@
                        open : $.noop,
                        empty: $.noop };
 
-               viewer.imageInfoProvider.get = function() { return 
$.Deferred().resolve(); };
+               viewer.imageInfoProvider.get = function() { return 
$.Deferred().resolve( {} ); };
                viewer.thumbnailInfoProvider.get = function() { return 
$.Deferred().resolve( {} ); };
 
                // load some image
@@ -361,7 +373,17 @@
                        firstImageDeferred = $.Deferred(),
                        secondImageDeferred = $.Deferred(),
                        firstLigthboxInfoDeferred = $.Deferred(),
-                       secondLigthboxInfoDeferred = $.Deferred();
+                       secondLigthboxInfoDeferred = $.Deferred(),
+                       firstImage = {
+                               filePageTitle: new mw.Title( 'File:Foo.jpg' ),
+                               index: 0,
+                               extraStatsDeferred: $.Deferred().reject()
+                       },
+                       secondImage = {
+                               filePageTitle: new mw.Title( 'File:Bar.jpg' ),
+                               index: 1,
+                               extraStatsDeferred: $.Deferred().reject()
+                       };
 
                viewer.preloadFullscreenThumbnail = $.noop;
                viewer.fetchSizeIndependentLightboxInfo = this.sandbox.stub();
@@ -395,25 +417,25 @@
 
                viewer.imageProvider.get.returns( firstImageDeferred.promise() 
);
                viewer.fetchSizeIndependentLightboxInfo.returns( 
firstLigthboxInfoDeferred.promise() );
-               viewer.loadImage( { filePageTitle : new mw.Title( 
'File:Foo.jpg' ), index : 0 }, new Image() );
+               viewer.loadImage( firstImage, new Image() );
                assert.ok( !viewer.animateMetadataDivOnce.called, 'Metadata of 
the first image should not be animated' );
                assert.ok( !viewer.ui.panel.setImageInfo.called, 'Metadata of 
the first image should not be shown' );
 
                viewer.imageProvider.get.returns( secondImageDeferred.promise() 
);
                viewer.fetchSizeIndependentLightboxInfo.returns( 
secondLigthboxInfoDeferred.promise() );
-               viewer.loadImage( { filePageTitle : new mw.Title( 
'File:Bar.jpg' ), index : 1 }, new Image() );
+               viewer.loadImage( secondImage, new Image() );
 
                viewer.ui.panel.progressBar.animateTo.reset();
                firstImageDeferred.notify( undefined, 45 );
                assert.ok( !viewer.ui.panel.progressBar.animateTo.reset.called, 
'Progress of the first image should not be shown' );
 
                firstImageDeferred.resolve();
-               firstLigthboxInfoDeferred.resolve();
+               firstLigthboxInfoDeferred.resolve( {} );
                assert.ok( !viewer.displayRealThumbnail.called, 'The first 
image being done loading should have no effect');
 
                viewer.displayRealThumbnail = this.sandbox.spy( function () { 
viewer.close(); } );
                secondImageDeferred.resolve();
-               secondLigthboxInfoDeferred.resolve();
+               secondLigthboxInfoDeferred.resolve( {} );
                assert.ok( viewer.displayRealThumbnail.called, 'The second 
image being done loading should result in the image being shown');
        } );
 
@@ -438,8 +460,9 @@
                viewer.preloadFullscreenThumbnail = $.noop;
                viewer.initWithThumbs( [] );
 
-               viewer.loadImage( { filePageTitle : new mw.Title( 
'File:Stuff.jpg' ),
-                       thumbnail : new mw.mmv.model.Thumbnail( 'foo', 10, 10 ) 
},
+               viewer.loadImage( { filePageTitle: new mw.Title( 
'File:Stuff.jpg' ),
+                       thumbnail: new mw.mmv.model.Thumbnail( 'foo', 10, 10 ),
+                       extraStatsDeferred: $.Deferred().reject() },
                        new Image() );
 
                viewer.ui.$closeButton.click();
@@ -534,7 +557,7 @@
                assert.ok( !guessedThumbnailInfoStub.called, 'When we lack 
sample URL and original dimensions, GuessedThumbnailInfoProvider is not called' 
);
                assert.ok( thumbnailInfoStub.calledOnce, 'When we lack sample 
URL and original dimensions, ThumbnailInfoProvider is called once' );
                assert.ok( imageStub.calledOnce, 'When we lack sample URL and 
original dimensions, ImageProvider is called once' );
-               assert.ok( imageStub.calledWithExactly( 'apiURL' ), 'When we 
lack sample URL and original dimensions, ImageProvider is called with the API 
url' );
+               assert.ok( imageStub.calledWithExactly( 'apiURL', undefined ), 
'When we lack sample URL and original dimensions, ImageProvider is called with 
the API url' );
                assert.strictEqual( promise.state(), 'resolved', 'When we lack 
sample URL and original dimensions, fetchThumbnail resolves' );
 
                // When the guesser bails out, the classic provider should be 
used
@@ -546,7 +569,7 @@
                assert.ok( guessedThumbnailInfoStub.calledOnce, 'When the 
guesser bails out, GuessedThumbnailInfoProvider is called once' );
                assert.ok( thumbnailInfoStub.calledOnce, 'When the guesser 
bails out, ThumbnailInfoProvider is called once' );
                assert.ok( imageStub.calledOnce, 'When the guesser bails out, 
ImageProvider is called once' );
-               assert.ok( imageStub.calledWithExactly( 'apiURL' ), 'When the 
guesser bails out, ImageProvider is called with the API url' );
+               assert.ok( imageStub.calledWithExactly( 'apiURL', undefined ), 
'When the guesser bails out, ImageProvider is called with the API url' );
                assert.strictEqual( promise.state(), 'resolved', 'When the 
guesser bails out, fetchThumbnail resolves' );
 
                // When the guesser returns an URL, that should be used
@@ -558,7 +581,7 @@
                assert.ok( guessedThumbnailInfoStub.calledOnce, 'When the 
guesser returns an URL, GuessedThumbnailInfoProvider is called once' );
                assert.ok( !thumbnailInfoStub.called, 'When the guesser returns 
an URL, ThumbnailInfoProvider is not called' );
                assert.ok( imageStub.calledOnce, 'When the guesser returns an 
URL, ImageProvider is called once' );
-               assert.ok( imageStub.calledWithExactly( 'guessedURL' ), 'When 
the guesser returns an URL, ImageProvider is called with the guessed url' );
+               assert.ok( imageStub.calledWithExactly( 'guessedURL', undefined 
), 'When the guesser returns an URL, ImageProvider is called with the guessed 
url' );
                assert.strictEqual( promise.state(), 'resolved', 'When the 
guesser returns an URL, fetchThumbnail resolves' );
 
                // When the guesser returns an URL, but that returns 404, image 
loading should be retried with the classic provider
@@ -571,8 +594,8 @@
                assert.ok( guessedThumbnailInfoStub.calledOnce, 'When the 
guesser returns an URL, but that returns 404, GuessedThumbnailInfoProvider is 
called once' );
                assert.ok( thumbnailInfoStub.calledOnce, 'When the guesser 
returns an URL, but that returns 404, ThumbnailInfoProvider is called once' );
                assert.ok( imageStub.calledTwice, 'When the guesser returns an 
URL, but that returns 404, ImageProvider is called twice' );
-               assert.ok( imageStub.getCall( 0 ).calledWithExactly( 
'guessedURL' ), 'When the guesser returns an URL, but that returns 404, 
ImageProvider is called first with the guessed url' );
-               assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL' 
), 'When the guesser returns an URL, but that returns 404, ImageProvider is 
called second with the guessed url' );
+               assert.ok( imageStub.getCall( 0 ).calledWithExactly( 
'guessedURL', undefined ), 'When the guesser returns an URL, but that returns 
404, ImageProvider is called first with the guessed url' );
+               assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL', 
undefined ), 'When the guesser returns an URL, but that returns 404, 
ImageProvider is called second with the guessed url' );
                assert.strictEqual( promise.state(), 'resolved', 'When the 
guesser returns an URL, but that returns 404, fetchThumbnail resolves' );
 
                // When even the retry fails, fetchThumbnail() should reject
@@ -585,8 +608,8 @@
                assert.ok( guessedThumbnailInfoStub.calledOnce, 'When even the 
retry fails, GuessedThumbnailInfoProvider is called once' );
                assert.ok( thumbnailInfoStub.calledOnce, 'When even the retry 
fails, ThumbnailInfoProvider is called once' );
                assert.ok( imageStub.calledTwice, 'When even the retry fails, 
ImageProvider is called twice' );
-               assert.ok( imageStub.getCall( 0 ).calledWithExactly( 
'guessedURL' ), 'When even the retry fails, ImageProvider is called first with 
the guessed url' );
-               assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL' 
), 'When even the retry fails, ImageProvider is called second with the guessed 
url' );
+               assert.ok( imageStub.getCall( 0 ).calledWithExactly( 
'guessedURL', undefined ), 'When even the retry fails, ImageProvider is called 
first with the guessed url' );
+               assert.ok( imageStub.getCall( 1 ).calledWithExactly( 'apiURL', 
undefined ), 'When even the retry fails, ImageProvider is called second with 
the guessed url' );
                assert.strictEqual( promise.state(), 'rejected', 'When even the 
retry fails, fetchThumbnail rejects' );
 
                mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = 
false;
@@ -600,7 +623,7 @@
                assert.ok( !guessedThumbnailInfoStub.called, 'When guessing is 
disabled, GuessedThumbnailInfoProvider is not called' );
                assert.ok( thumbnailInfoStub.calledOnce, 'When guessing is 
disabled, ThumbnailInfoProvider is called once' );
                assert.ok( imageStub.calledOnce, 'When guessing is disabled, 
ImageProvider is called once' );
-               assert.ok( imageStub.calledWithExactly( 'apiURL' ), 'When 
guessing is disabled, ImageProvider is called with the API url' );
+               assert.ok( imageStub.calledWithExactly( 'apiURL', undefined ), 
'When guessing is disabled, ImageProvider is called with the API url' );
                assert.strictEqual( promise.state(), 'resolved', 'When guessing 
is disabled, fetchThumbnail resolves' );
 
                mw.config.get( 'wgMultimediaViewer' ).useThumbnailGuessing = 
oldUseThumbnailGuessing;
diff --git a/tests/qunit/mmv/model/mmv.model.Image.test.js 
b/tests/qunit/mmv/model/mmv.model.Image.test.js
index 33fda90..7686ffd 100644
--- a/tests/qunit/mmv/model/mmv.model.Image.test.js
+++ b/tests/qunit/mmv/model/mmv.model.Image.test.js
@@ -18,7 +18,7 @@
 ( function( mw ) {
        QUnit.module( 'mmv.model.Image', QUnit.newMwEnvironment() );
 
-       QUnit.test( 'Image model constructor sanity check', 21, function ( 
assert ) {
+       QUnit.test( 'Image model constructor sanity check', 22, function ( 
assert ) {
                var
                        title = mw.Title.newFromText( 'File:Foobar.jpg' ),
                        name = 'Foo bar',
@@ -31,6 +31,7 @@
                        repo = 'wikimediacommons',
                        user = 'Kaldari',
                        datetime = '2011-07-04T23:31:14Z',
+                       anondatetime = '20110704000000',
                        origdatetime = '2010-07-04T23:31:14Z',
                        description = 'This is a test file.',
                        source = 'WMF',
@@ -42,7 +43,7 @@
                        longitude = 100.983829,
                        imageData = new mw.mmv.model.Image(
                                title, name, size, width, height, mime, url,
-                               descurl, repo, user, datetime, origdatetime,
+                               descurl, repo, user, datetime, anondatetime, 
origdatetime,
                                description, source, author, authorCount, 
license, permission,
                                latitude, longitude );
 
@@ -57,6 +58,7 @@
                assert.strictEqual( imageData.repo, repo, 'Repository name is 
set correctly' );
                assert.strictEqual( imageData.lastUploader, user, 'Name of last 
uploader is set correctly' );
                assert.strictEqual( imageData.uploadDateTime, datetime, 'Date 
and time of last upload is set correctly' );
+               assert.strictEqual( imageData.anonymizedUploadDateTime, 
anondatetime, 'Anonymized date and time of last upload is set correctly' );
                assert.strictEqual( imageData.creationDateTime, origdatetime, 
'Date and time of original upload is set correctly' );
                assert.strictEqual( imageData.description, description, 
'Description is set correctly' );
                assert.strictEqual( imageData.source, source, 'Source is set 
correctly' );
@@ -74,13 +76,13 @@
                        firstImageData = new mw.mmv.model.Image(
                                mw.Title.newFromText( 'File:Foobar.pdf.jpg' ), 
'Foo bar',
                                10, 10, 10, 'image/jpeg', 'http://example.org', 
'http://example.com',
-                               'example', 'tester', '2013-11-10', 
'2013-11-09', 'Blah blah blah',
+                               'example', 'tester', '2013-11-10', '20131110', 
'2013-11-09', 'Blah blah blah',
                                'A person', 'Another person', 1, 
'CC-BY-SA-3.0', 'Permitted'
                        ),
                        secondImageData = new mw.mmv.model.Image(
                                mw.Title.newFromText( 'File:Foobar.pdf.jpg' ), 
'Foo bar',
                                10, 10, 10, 'image/jpeg', 'http://example.org', 
'http://example.com',
-                               'example', 'tester', '2013-11-10', 
'2013-11-09', 'Blah blah blah',
+                               'example', 'tester', '2013-11-10', '20131110', 
'2013-11-09', 'Blah blah blah',
                                'A person', 'Another person', 1, 
'CC-BY-SA-3.0', 'Permitted',
                                '39.91820938', '78.09812938'
                        );
diff --git a/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js 
b/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js
index e9062b1..6dd3d74 100644
--- a/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js
+++ b/tests/qunit/mmv/provider/mmv.provider.ImageInfo.test.js
@@ -25,7 +25,7 @@
                assert.ok( imageInfoProvider );
        } );
 
-       QUnit.asyncTest( 'ImageInfo get test', 26, function ( assert ) {
+       QUnit.asyncTest( 'ImageInfo get test', 27, function ( assert ) {
                var apiCallCount = 0,
                        api = { get: function() {
                                apiCallCount++;
@@ -142,6 +142,7 @@
                        assert.strictEqual( image.repo, 'shared', 'repo is set 
correctly' );
                        assert.strictEqual( image.lastUploader, 'Dylanbot11', 
'lastUploader is set correctly' );
                        assert.strictEqual( image.uploadDateTime, 
'2013-08-25T14:41:02Z', 'uploadDateTime is set correctly' );
+                       assert.strictEqual( image.anonymizedUploadDateTime, 
'20130825000000', 'anonymizedUploadDateTime is set correctly' );
                        assert.strictEqual( image.creationDateTime, '18 
February 2009\u00a0(according to EXIF data)', 'creationDateTime is set 
correctly' );
                        assert.strictEqual( image.description, 'Wikis stuff', 
'description is set correctly' );
                        assert.strictEqual( image.source, 'Wikipedia', 'source 
is set correctly' );

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

Gerrit-MessageType: merged
Gerrit-Change-Id: I673f9487deea15dc148452a3a4d6b91563a2c417
Gerrit-PatchSet: 12
Gerrit-Project: mediawiki/extensions/MultimediaViewer
Gerrit-Branch: master
Gerrit-Owner: Gilles <gdu...@wikimedia.org>
Gerrit-Reviewer: Gergő Tisza <gti...@wikimedia.org>
Gerrit-Reviewer: Gilles <gdu...@wikimedia.org>
Gerrit-Reviewer: MarkTraceur <mtrac...@member.fsf.org>
Gerrit-Reviewer: jenkins-bot <>

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

Reply via email to