Mforns has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/181424

Change subject: Add Annotations API
......................................................................

Add Annotations API

Adds an API to get metric annotations from Mediawiki.
The API uses mediawiki-storage to retrieve the pages.
Also, performs the following validations
on annotations read from mediawiki pages:

* Checks that dates are valid or undefined
* Checks that start date <= end date
* Checks that note is a string

If an annotation is not valid, it is filtered
and the others are returned.

Bug: T78151
Change-Id: If372e9d6065aefc7ad0d6abb49894b18f06b7caf
---
A src/app/apis/annotations-api.js
M src/app/require.config.js
M test/app/apis.js
3 files changed, 263 insertions(+), 2 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/analytics/dashiki 
refs/changes/24/181424/1

diff --git a/src/app/apis/annotations-api.js b/src/app/apis/annotations-api.js
new file mode 100644
index 0000000..44af7d8
--- /dev/null
+++ b/src/app/apis/annotations-api.js
@@ -0,0 +1,138 @@
+/**
+ * This module gets metric annotations that reside in Mediawiki.
+ * To get them, it uses mediawiki-storage library.
+ */
+define(['mediawiki-storage', 'moment'], function (mediawikiStorage, moment) {
+    'use strict';
+
+    function AnnotationsApi () {
+        // only fetch annotations for a given metric
+        // once per app life and keep their promise
+        this._promises = {};
+
+        // default promise for empty responses
+        this._emptyPromise = $.Deferred().resolve([]);
+    }
+
+
+    /**
+     * Retrieves the annotations for the given metric.
+     *
+     * Parameters
+     *
+     *   metric  : Metric object containing (or not) the following fields:
+     *             {
+     *                 ...
+     *                 annotations: {
+     *                     host: 'mediawiki.host',
+     *                     pageName: 'PageName'
+     *                 },
+     *                 ...
+     *             }
+     *             If it contains this substructure, the method
+     *             will try to get the data from mediawiki.
+     *             Otherwise, it will return en empty list.
+     *
+     *   success : [optional] A function that will be called when finished
+     *             with the resulting data as single parameter.
+     *
+     *   error   : [optional] A function that will be called in case
+     *             of failure with the risen error as single parameter.
+     *
+     * Returns
+     *
+     *   A jquery promise where 'done' and 'fail' callbacks can be submitted.
+     *   These, will receive the same parameters as success and error callbacks
+     *   respectively. In case of success, this will be the annotation's 
format:
+     *   [
+     *       {
+     *           start     : '2013-01-01 17:32:13',
+     *           end       : '2014-02-02 03:55:08',
+     *           note      : 'Annotation text.'
+     *       },
+     *       ...
+     *   ]
+     **/
+    AnnotationsApi.prototype.get = function (metric, success, error) {
+        if (typeof metric !== 'object') {
+            throw new TypeError('function must receive an object');
+        }
+
+        var params = metric.annotations;
+        if (!this._checkParams(params)) {
+            // accept metrics without annotation params
+            // and just return an empty array
+            return this._emptyPromise.done(success);
+        }
+
+        if (!this._promises[params]) {
+            // the requested annotations have not been found in cache
+            // so they must be retrieved and cached for further use
+            var deferred = $.Deferred(),
+                that = this;
+
+            mediawikiStorage.get({
+                host: params.host,
+                pageName: params.pageName
+            })
+            .fail(deferred.reject)
+            .done(function (data) {
+                that._checkAnnotations(data, deferred);
+            });
+
+            this._promises[params] = deferred.promise();
+        }
+
+        return this._promises[params].done(success).fail(error);
+    };
+
+    AnnotationsApi.prototype._checkParams = function (params) {
+        return (
+            typeof params === 'object' &&
+            typeof params.host === 'string' &&
+            typeof params.pageName === 'string'
+        );
+    };
+
+    AnnotationsApi.prototype._checkDate = function (date) {
+        return (
+            date === void 0 ||
+            typeof date === 'string' &&
+            moment(date).isValid()
+        );
+    };
+
+    AnnotationsApi.prototype._checkInterval = function (start, end) {
+        // assumes both dates have passed _checkDate
+        return (
+            start === void 0 ||
+            end === void 0 ||
+            moment(start) <= moment(end)
+        );
+    };
+
+    AnnotationsApi.prototype._checkAnnotations = function (data, deferred) {
+        if (!(data instanceof Array)) {
+            var error = new TypeError(
+                'Mediawiki page must hold an array of annotations.'
+            );
+            deferred.reject(error);
+        }
+
+        var that = this;
+        // remove annotations that are incorrect
+        var annotations = data.filter(function (annotation) {
+            return (
+                typeof annotation === 'object' &&
+                that._checkDate(annotation.start) &&
+                that._checkDate(annotation.end) &&
+                that._checkInterval(annotation.start, annotation.end) &&
+                typeof annotation.note === 'string'
+            );
+        });
+
+        deferred.resolve(annotations);
+    };
+
+    return new AnnotationsApi();
+});
diff --git a/src/app/require.config.js b/src/app/require.config.js
index 1afcce3..43033e8 100644
--- a/src/app/require.config.js
+++ b/src/app/require.config.js
@@ -24,6 +24,7 @@
         'config'                : 'app/config',
         'logger'                : 'lib/logger',
         'wikimetricsApi'        : 'app/apis/wikimetrics',
+        'annotationsApi'        : 'app/apis/annotations-api',
         'pageviewApi'           : 'app/apis/legacy-pageview-api',
         'configApi'             : 'app/apis/config-api',
         'dataConverterFactory'  : 'app/data-converters/factory',
diff --git a/test/app/apis.js b/test/app/apis.js
index 903ee21..007070f 100644
--- a/test/app/apis.js
+++ b/test/app/apis.js
@@ -1,6 +1,6 @@
 define([
-    'config', 'wikimetricsApi', 'configApi', 'mediawiki-storage', 'jquery'
-], function (siteConfig, wikimetrics, configApi, mediawikiStorage, $) {
+    'config', 'wikimetricsApi', 'configApi', 'annotationsApi', 
'mediawiki-storage', 'jquery'
+], function (siteConfig, wikimetrics, configApi, annotationsApi,  
mediawikiStorage, $) {
 
     describe('Wikimetrics API', function () {
 
@@ -83,5 +83,127 @@
                 done();
             });
         });
+
+        it('should get metric annotations', function (done) {
+            var mediawikiHost = 'some.mediawiki.host',
+                annotationsPage = 'SomeMediawikiPageName',
+                startDate = '2014-01-01 00:00:00',
+                endDate = '2014-01-01 00:00:00',
+                note = 'Some text.';
+                annotations = [{start: startDate, end: endDate, note: note}];
+
+            sinon.stub(mediawikiStorage, 'get', function (options) {
+                expect(options.host).toBe(mediawikiHost);
+                expect(options.pageName).toBe(annotationsPage);
+
+                var deferred = new $.Deferred();
+                deferred.resolve(annotations);
+                return deferred.promise();
+            });
+
+            var metric = {
+                annotations: {
+                    host: mediawikiHost,
+                    pageName: annotationsPage
+                }
+            };
+            annotationsApi.get(metric, function (returned) {
+                expect(returned instanceof Array).toBe(true);
+                expect(returned.length).toBe(1);
+                expect(typeof returned[0]).toBe('object');
+                expect(returned[0].start).toBe(startDate);
+                expect(returned[0].end).toBe(endDate);
+                expect(returned[0].note).toBe(note);
+                mediawikiStorage.get.restore();
+                done();
+            });
+        });
+
+        it('should return empty list when metric has no annotations info', 
function (done) {
+            sinon.stub(mediawikiStorage, 'get', function (options) {
+                expect(true).toBe(false);  // should not get here
+            });
+
+            var metric = {};  // metric has no annotations information
+            annotationsApi.get(metric, function (returned) {
+                expect(returned instanceof Array).toBe(true);
+                expect(returned.length).toBe(0);
+                mediawikiStorage.get.restore();
+                done();
+            });
+        });
+
+        it('should filter out annotations with invalid dates', function (done) 
{
+            sinon.stub(mediawikiStorage, 'get', function (options) {
+                var deferred = new $.Deferred();
+                deferred.resolve([
+                    {start: 'Bad date', note: 'Some note.'},
+                    {start: '2014-01-01 00:00:00', note: 'Some note.'}
+                ]);
+                return deferred.promise();
+            });
+
+            var metric = {
+                annotations: {
+                    host: 'some.mediawiki.host',
+                    pageName: 'SomeMediawikiPageName'
+                }
+            };
+            annotationsApi.get(metric, function (returned) {
+                expect(returned instanceof Array).toBe(true);
+                expect(returned.length).toBe(1);
+                mediawikiStorage.get.restore();
+                done();
+            });
+        });
+
+        it('should filter out annotations with no note', function (done) {
+            sinon.stub(mediawikiStorage, 'get', function (options) {
+                var deferred = new $.Deferred();
+                deferred.resolve([
+                    {start: '2014-01-01 00:00:00'},
+                    {start: '2014-01-01 00:00:00', note: 'Some note.'}
+                ]);
+                return deferred.promise();
+            });
+
+            var metric = {
+                annotations: {
+                    host: 'some.mediawiki.host',
+                    pageName: 'SomeMediawikiPageName'
+                }
+            };
+            annotationsApi.get(metric, function (returned) {
+                expect(returned instanceof Array).toBe(true);
+                expect(returned.length).toBe(1);
+                mediawikiStorage.get.restore();
+                done();
+            });
+        });
+
+        it('should filter out annotations with bad time interval', function 
(done) {
+            sinon.stub(mediawikiStorage, 'get', function (options) {
+                var deferred = new $.Deferred();
+                deferred.resolve([
+                    // end date before start date
+                    {start: '2014-01-01', end: '2013-01-01', note: 'Some 
note.'},
+                    {start: '2014-01-01', end: '2014-01-02', note: 'Some 
note.'}
+                ]);
+                return deferred.promise();
+            });
+
+            var metric = {
+                annotations: {
+                    host: 'some.mediawiki.host',
+                    pageName: 'SomeMediawikiPageName'
+                }
+            };
+            annotationsApi.get(metric, function (returned) {
+                expect(returned instanceof Array).toBe(true);
+                expect(returned.length).toBe(1);
+                mediawikiStorage.get.restore();
+                done();
+            });
+        });
     });
 });

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

Gerrit-MessageType: newchange
Gerrit-Change-Id: If372e9d6065aefc7ad0d6abb49894b18f06b7caf
Gerrit-PatchSet: 1
Gerrit-Project: analytics/dashiki
Gerrit-Branch: master
Gerrit-Owner: Mforns <mfo...@wikimedia.org>

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

Reply via email to