Mforns has uploaded a new change for review. https://gerrit.wikimedia.org/r/176304
Change subject: Add promise support to get method ...................................................................... Add promise support to get method To get the results and the thrown errors, get method was accepting the success and error parameters respectively. However, Dashiki uses jQuery promises returned by the apis to subscribe callbacks. To adapt to Dashiki and to implement a common interface, this fix implements promise support for get method. Bug: 68448 Change-Id: I719960b677b66f60fbe2c28e8028b1a220b5567e --- M bower.json M dist/mediawiki-storage.js M dist/mediawiki-storage.min.js M doc/api.md M package.json M src/mediawiki-storage.js M test/mediawiki-storage-spec.js 7 files changed, 195 insertions(+), 70 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/analytics/mediawiki-storage refs/changes/04/176304/1 diff --git a/bower.json b/bower.json index 85aa395..89dc058 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "mediawiki-storage", - "version": "0.0.0", + "version": "0.0.1", "authors": [ "Marcel Ruiz Forns <mfo...@wikimedia.org>" ], diff --git a/dist/mediawiki-storage.js b/dist/mediawiki-storage.js index 70c2e42..7c78e25 100644 --- a/dist/mediawiki-storage.js +++ b/dist/mediawiki-storage.js @@ -13,32 +13,28 @@ * - the page id * - the revision id * - * Note that: - * The page contents must be valid json. - * The retrieval of the page contents is done via jsonp. - * The return value is passed as parameter for the success callback. - * Errors occurring before jsonp call (TypeError) will be thrown - * synchronously, and errors occurring within or after jsonp call - * will be handed as parameter for error callback. - * * Parameter: options {object} * { - * host: {string}, // i.e. www.wikipedia.org - * // at least one of the following three: - * pageName: {string}, - * pageId: {string}, - * revId: {string}, - * success: {function}, // callback [optional] - * // parameter: object with page contents - * error: {function} // callback [optional] - * // parameter: error thrown + * host: {string}, + * // specify one of the following 3 params + * pageName: {string} [optional], + * pageId: {string} [optional], + * revId: {string} [optional], + * success: {function} [optional], + * error: {function} [optional] * } + * + * Returns: {Promise} + * + * For more information, see the api documentation: + * https://github.com/wikimedia/analytics-mediawiki-storage/blob/master/doc/api.md */ MediawikiStorage.prototype.get = function (options) { if (typeof options !== 'object') { throw new TypeError('function must receive an object'); } + var deferred = $.Deferred(); var url = this._createQueryUrl(options); var that = this; @@ -48,23 +44,35 @@ contentType: 'application/json', success: function (data) { - if (typeof options.success === 'function') { - var pageJson; - try { - pageJson = that._getPageJson(data); - } catch (e) { - return options.error(e); + var pageJson; + + // try to parse page contents + try { + pageJson = that._getPageJson(data); + } catch (e) { + if (typeof options.success === 'function') { + options.error(e); } + deferred.reject(e); + return; + } + + // return parsed page contents + if (typeof options.success === 'function') { options.success(pageJson); } + deferred.resolve(pageJson); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, e) { if (typeof options.error === 'function') { - options.error(errorThrown); + options.error(e); } + deferred.reject(e); } }); + + return deferred.promise(); }; /** diff --git a/dist/mediawiki-storage.min.js b/dist/mediawiki-storage.min.js index bfb3a4e..c76ef81 100644 --- a/dist/mediawiki-storage.min.js +++ b/dist/mediawiki-storage.min.js @@ -1 +1 @@ -"use strict";!function(e){var t=function(){};t.prototype.get=function(e){if("object"!=typeof e)throw new TypeError("function must receive an object");var t=this._createQueryUrl(e),r=this;$.ajax({url:t,dataType:"jsonp",contentType:"application/json",success:function(t){if("function"==typeof e.success){var o;try{o=r._getPageJson(t)}catch(n){return e.error(n)}e.success(o)}},error:function(t,r,o){"function"==typeof e.error&&e.error(o)}})},t.prototype._createQueryUrl=function(e){if("string"!=typeof e.host)throw new TypeError("host must be a string");var t;if("string"==typeof e.pageName)t="&titles="+e.pageName;else if("string"==typeof e.pageId)t="&pageids="+e.pageId;else{if("string"!=typeof e.revId)throw new TypeError("one of (pageName|pageId|revId) must be a string");t="&revids="+e.revId}return["http://",e.host,"/w/api.php","?action=query","&prop=revisions","&format=json","&rvprop=content",t].join("")},t.prototype._getPageJson=function(e){var t,r;try{var o=e.query.pages,n=Object.keys(o)[0];t=o[n].revisions[0]["*"]}catch(i){throw new SyntaxError("unexpected query result")}try{r=JSON.parse(t)}catch(i){throw new SyntaxError("page contents are not a valid json")}return r};var r=new t;"function"==typeof define&&define.amd?define(function(){return r}):"undefined"!=typeof module&&module.exports?module.exports=r:e.mediawikiStorage=r}(window); \ No newline at end of file +"use strict";!function(e){var r=function(){};r.prototype.get=function(e){if("object"!=typeof e)throw new TypeError("function must receive an object");var r=$.Deferred(),t=this._createQueryUrl(e),o=this;return $.ajax({url:t,dataType:"jsonp",contentType:"application/json",success:function(t){var n;try{n=o._getPageJson(t)}catch(s){return"function"==typeof e.success&&e.error(s),void r.reject(s)}"function"==typeof e.success&&e.success(n),r.resolve(n)},error:function(t,o,n){"function"==typeof e.error&&e.error(n),r.reject(n)}}),r.promise()},r.prototype._createQueryUrl=function(e){if("string"!=typeof e.host)throw new TypeError("host must be a string");var r;if("string"==typeof e.pageName)r="&titles="+e.pageName;else if("string"==typeof e.pageId)r="&pageids="+e.pageId;else{if("string"!=typeof e.revId)throw new TypeError("one of (pageName|pageId|revId) must be a string");r="&revids="+e.revId}return["http://",e.host,"/w/api.php","?action=query","&prop=revisions","&format=json","&rvprop=content",r].join("")},r.prototype._getPageJson=function(e){var r,t;try{var o=e.query.pages,n=Object.keys(o)[0];r=o[n].revisions[0]["*"]}catch(s){throw new SyntaxError("unexpected query result")}try{t=JSON.parse(r)}catch(s){throw new SyntaxError("page contents are not a valid json")}return t};var t=new r;"function"==typeof define&&define.amd?define(function(){return t}):"undefined"!=typeof module&&module.exports?module.exports=t:e.mediawikiStorage=t}(window); \ No newline at end of file diff --git a/doc/api.md b/doc/api.md index 5f23489..922d6f0 100644 --- a/doc/api.md +++ b/doc/api.md @@ -1,11 +1,10 @@ # API Documentation - mediawiki-storage should run within the following export environments: AMD (define), Node.js (require) and browser (global). After you get mediawiki-storage object, you can call the following methods on it: -### get +## get __Returns the contents of the specified mediawiki page.__ -The mediawiki page is specified using mediawiki's `host` and one of: +The mediawiki page is specified using mediawiki's `host` and _one_ of: * `pageName` * `pageId` @@ -13,8 +12,7 @@ _Note that the page contents must be valid json._ -##### Parameters: - +#### Parameters: 1. options _{object}_ Dictionary with all options ``` @@ -28,17 +26,49 @@ } ``` -##### Return value: +#### Retrieving the results: +There are two options to retrieve the results: -Is passed as a single parameter to `success` callback. +##### Via _success_ parameter: +``` +mediawikiStorage.get({ + // ... + success: function (results) { + // do something with results + } +}); +``` -##### Errors thrown: +##### Via promise _done_: +``` +mediawikiStorage.get({ + // ... +}).done(function (results) { + // do something with results +}); +``` -Errors occurring before jsonp call will be thrown synchronously. -If an error occures within or after the jsonp call, it will be passed to the `error` callback as a single parameter, and execution will be aborted. +#### Errors thrown: +Errors occurring before jsonp call will be thrown synchronously. Error occurring within or after the jsonp call, will be passed to the corresponding callback. There are two options to catch the error: +##### Via _error_ parameter: +``` +mediawikiStorage.get({ + // ... + error: function (error) { + // do something with error + } +}); +``` +##### Via promise _fail_: +``` +mediawikiStorage.get({ + // ... +}).fail(function (error) { + // do something with error +}); +``` -##### Example: - +#### Full example: ``` mediawiki-storage.get({ host: 'meta.wikimedia.org', diff --git a/package.json b/package.json index e222011..7dfca35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mediawiki-storage", - "version": "0.0.0", + "version": "0.0.1", "devDependencies": { "del": "^0.1.3", "gulp": "^3.8.10", diff --git a/src/mediawiki-storage.js b/src/mediawiki-storage.js index 70c2e42..7c78e25 100644 --- a/src/mediawiki-storage.js +++ b/src/mediawiki-storage.js @@ -13,32 +13,28 @@ * - the page id * - the revision id * - * Note that: - * The page contents must be valid json. - * The retrieval of the page contents is done via jsonp. - * The return value is passed as parameter for the success callback. - * Errors occurring before jsonp call (TypeError) will be thrown - * synchronously, and errors occurring within or after jsonp call - * will be handed as parameter for error callback. - * * Parameter: options {object} * { - * host: {string}, // i.e. www.wikipedia.org - * // at least one of the following three: - * pageName: {string}, - * pageId: {string}, - * revId: {string}, - * success: {function}, // callback [optional] - * // parameter: object with page contents - * error: {function} // callback [optional] - * // parameter: error thrown + * host: {string}, + * // specify one of the following 3 params + * pageName: {string} [optional], + * pageId: {string} [optional], + * revId: {string} [optional], + * success: {function} [optional], + * error: {function} [optional] * } + * + * Returns: {Promise} + * + * For more information, see the api documentation: + * https://github.com/wikimedia/analytics-mediawiki-storage/blob/master/doc/api.md */ MediawikiStorage.prototype.get = function (options) { if (typeof options !== 'object') { throw new TypeError('function must receive an object'); } + var deferred = $.Deferred(); var url = this._createQueryUrl(options); var that = this; @@ -48,23 +44,35 @@ contentType: 'application/json', success: function (data) { - if (typeof options.success === 'function') { - var pageJson; - try { - pageJson = that._getPageJson(data); - } catch (e) { - return options.error(e); + var pageJson; + + // try to parse page contents + try { + pageJson = that._getPageJson(data); + } catch (e) { + if (typeof options.success === 'function') { + options.error(e); } + deferred.reject(e); + return; + } + + // return parsed page contents + if (typeof options.success === 'function') { options.success(pageJson); } + deferred.resolve(pageJson); }, - error: function (jqXHR, textStatus, errorThrown) { + error: function (jqXHR, textStatus, e) { if (typeof options.error === 'function') { - options.error(errorThrown); + options.error(e); } + deferred.reject(e); } }); + + return deferred.promise(); }; /** diff --git a/test/mediawiki-storage-spec.js b/test/mediawiki-storage-spec.js index ef1277b..36f6391 100644 --- a/test/mediawiki-storage-spec.js +++ b/test/mediawiki-storage-spec.js @@ -58,7 +58,7 @@ } }); - it('returns a SyntaxError when the query result is not expected', function (done) { + it('returns SyntaxError when query result is not expected (via error parameter)', function (done) { sinon.stub($, 'ajax', function (options) { options.success('bad query result'); }); @@ -77,7 +77,26 @@ }); }); - it('returns a SyntaxError when the page is not a valid json', function (done) { + it('returns SyntaxError when query result is not expected (via promise fail)', function (done) { + sinon.stub($, 'ajax', function (options) { + options.success('bad query result'); + }); + var promise = mediawikiStorage.get({ + host : 'www.wikimedia.org', + pageName : 'Schema:EventCapsule' + }); + promise.done(function () { + expect(0).toEqual(1); // should never get here + }); + promise.fail(function (error) { + expect(error.name).toEqual('SyntaxError'); + expect(error.message).toEqual('unexpected query result'); + $.ajax.restore(); + done(); + }); + }); + + it('returns SyntaxError when page is not valid json (via error parameter)', function (done) { sinon.stub($, 'ajax', function (options) { options.success( {query:{pages:{'12345':{revisions:[{'*':'not a valid json'}]}}}} @@ -98,7 +117,28 @@ }); }); - it('propagates any jQuery.ajax errors', function (done) { + it('returns SyntaxError when page is not valid json (via promise fail)', function (done) { + sinon.stub($, 'ajax', function (options) { + options.success( + {query:{pages:{'12345':{revisions:[{'*':'not a valid json'}]}}}} + ); + }); + var promise = mediawikiStorage.get({ + host : 'www.wikimedia.org', + pageName : 'Schema:EventCapsule' + }); + promise.done(function () { + expect(0).toEqual(1); // should never get here + }); + promise.fail(function (error) { + expect(error.name).toEqual('SyntaxError'); + expect(error.message).toEqual('page contents are not a valid json'); + $.ajax.restore(); + done(); + }); + }); + + it('propagates jQuery.ajax errors (via error parameter)', function (done) { sinon.stub($, 'ajax', function (options) { options.error({}, 'someStatus', new Error('jQuery.ajax error')); }); @@ -117,7 +157,26 @@ }); }); - it('returns object created from page json', function (done) { + it('propagates jQuery.ajax errors (via promise fail)', function (done) { + sinon.stub($, 'ajax', function (options) { + options.error({}, 'someStatus', new Error('jQuery.ajax error')); + }); + var promise = mediawikiStorage.get({ + host : 'www.wikimedia.org', + pageName : 'Schema:EventCapsule' + }); + promise.done(function () { + expect(0).toEqual(1); // should never get here + }); + promise.fail(function (error) { + expect(error.name).toEqual('Error'); + expect(error.message).toEqual('jQuery.ajax error'); + $.ajax.restore(); + done(); + }); + }); + + it('returns results (via success parameter)', function (done) { sinon.stub($, 'ajax', function (options) { options.success( {query:{pages:{'12345':{revisions:[{'*':'{"valid": "json"}'}]}}}} @@ -126,7 +185,7 @@ mediawikiStorage.get({ host : 'www.wikimedia.org', pageName : 'Schema:EventCapsule', - success : function (data, textStatus, jqXHR) { + success : function (data) { expect(data).toEqual({"valid": "json"}); $.ajax.restore(); done(); @@ -137,4 +196,24 @@ }); }); + it('returns results (via promise done)', function (done) { + sinon.stub($, 'ajax', function (options) { + options.success( + {query:{pages:{'12345':{revisions:[{'*':'{"valid": "json"}'}]}}}} + ); + }); + var promise = mediawikiStorage.get({ + host : 'www.wikimedia.org', + pageName : 'Schema:EventCapsule' + }); + promise.done(function (data) { + expect(data).toEqual({"valid": "json"}); + $.ajax.restore(); + done(); + }); + promise.fail(function () { + expect(0).toEqual(1); // should never get here + }); + }); + }); -- To view, visit https://gerrit.wikimedia.org/r/176304 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I719960b677b66f60fbe2c28e8028b1a220b5567e Gerrit-PatchSet: 1 Gerrit-Project: analytics/mediawiki-storage 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