Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
( function ( mw, $ ) {
/**
* @class OresApi
*/
/**
* @property {Object} defaultOptions Default options for #ajax calls. Can be overridden by passing
* `options` to OresApi constructor.
* @property {Object} defaultOptions.parameters Default query parameters for API requests.
* @property {Object} defaultOptions.ajax Default options for jQuery#ajax.
* @private
*/
var defaultOptions = {
parameters: {
format: 'json'
},
ajax: {
timeout: 30 * 1000, // 30 seconds
dataType: 'json',
type: 'GET'
},
score: {
batchSize: 50,
parallelConnections: 2
}
},
OresApi;
/**
* Constructor to create an object to interact with the API of an ORES server.
* OresApi objects represent the API of a particular ORES server.
*
* var ores = require('ext.ores.api');
* ores.get( {
* revids: [1234, 1235],
* models: [ 'damaging', 'articlequality' ] // same effect as 'damaging|articlequality'
* } ).done( function ( data ) {
* console.log( data );
* } );
*
* @constructor
* @param {Object} [options] See #defaultOptions documentation above.
* @param {Object} config configuration of the ores service.
*/
OresApi = function ( options, config ) {
var defaults = $.extend( {}, options ),
setsUrl = options && options.ajax && options.ajax.url !== undefined;
defaults.parameters = $.extend( {}, defaultOptions.parameters, defaults.parameters );
defaults.ajax = $.extend( {}, defaultOptions.ajax, defaults.ajax );
if ( setsUrl ) {
defaults.ajax.url = String( defaults.ajax.url );
} else {
defaults.ajax.url = config.baseUrl + '/v3/scores/' + config.wikiId;
}
this.defaults = defaults;
this.requests = [];
};
OresApi.prototype = {
/**
* Abort all unfinished requests issued by this Api object.
*
* @method
*/
abort: function () {
this.requests.forEach( function ( request ) {
if ( request ) {
request.abort();
}
} );
},
/**
* Massage parameters from the nice format we accept into a format suitable for the API.
*
* @private
* @param {Object} parameters (modified in-place)
*/
preprocessParameters: function ( parameters ) {
var key;
for ( key in parameters ) {
if ( Array.isArray( parameters[ key ] ) ) {
parameters[ key ] = parameters[ key ].join( '|' );
} else if ( parameters[ key ] === false || parameters[ key ] === undefined ) {
// Boolean values are only false when not given at all
delete parameters[ key ];
}
}
},
/**
* Perform an API call.
*
* @param {Object} parameters
* @return {jQuery.Promise} Done: API response data and the jqXHR object.
* Fail: Error code
*/
get: function ( parameters ) {
var requestIndex,
api = this,
apiDeferred = $.Deferred(),
xhr,
ajaxOptions;
parameters = $.extend( {}, this.defaults.parameters, parameters );
ajaxOptions = $.extend( {}, this.defaults.ajax );
this.preprocessParameters( parameters );
ajaxOptions.data = $.param( parameters );
xhr = $.ajax( ajaxOptions )
.done( function ( result, textStatus, jqXHR ) {
var code;
if ( result.error ) {
code = result.error.code === undefined ? 'unknown' : result.error.code;
apiDeferred.reject( code, result, jqXHR );
} else {
apiDeferred.resolve( result, jqXHR );
}
} );
requestIndex = this.requests.length;
this.requests.push( xhr );
xhr.always( function () {
api.requests[ requestIndex ] = null;
} );
return apiDeferred.promise( { abort: xhr.abort } ).fail( function ( code, details ) {
if ( !( code === 'http' && details && details.textStatus === 'abort' ) ) {
mw.log( 'OresApi error: ', code, details );
}
} );
},
/**
* Prepare an array of revision IDs or objects containing an "id" parameter
* for batching. Accepts either an array of integers or an array of obj.
* Returns an array of obj.
*
* @param {Array} ids
* @return {Array}
*/
prepareIdDataResults: function( ids ){
var idDataResults = [];
for ( i = 0; i < ids.length; i++ ) {
if ( isFinite(ids[i]) ) {
idDataResults.push({id: ids[i]});
} else {
idDataResults.push(ids[i]);
}
}
return idDataResults;
},
/**
* Score a batch of ids if there are any remaining batches to score. This
* function will recurse until there are no more batches to process.
*
* @param {Array} idBatches An array of objects containing "id" and maybe also "data"
* @param {Array} models An array of model names
* @param {jQuery.Deferred} deferred A Deferred object to notify() score results to
* @return null
*/
scoreBatch: function( idBatches, models, deferred ) {
var idBatch, rawIds;
if(ifBatches.length > 0 ) {
idBatch = idBatches.shift();
rawIds = [];
for ( i = 0; i < idBatch.length; i++){
rawIds.push(idBatch[i].id);
}
this.get({revids: rawIds, models: models})
.done(function(result){
for ( i = 0; i < idBatch.length; i++ ) {
deferred.notify(result[config.wikiId].scores[idBatch[i].id],
idBatch[i].data);
}
});
setTimeout(function(){
this.scoreBatch(idBatches, models, deferred);
}.bind(this), 0)
}
},
/**
* Score set of ids in batches and return a jQuery.Promise object that will
* repreatedly call a function passed to progress() with results.
*
* @param {Array} ids An array of integers or objects with an "id" and "data" tag. "Data" will be passed to progress() as results come in.
* @param {Array} models An array of model names
* @param {Object} options Options for processing. E.g. "batchSize" and "parallelConnections"
* @return null
*/
score: function ( ids, models, options ) {
var idBatches = [],
ids = this.prepareIdDataResults(ids),
batchSize = option.batchSize || this.defaults.score.batchSize,
parallelConnections = options.parallelConnections || this.defaults.score.parallelConnections,
deferred = new $.Deferred();
// Initialize batches
for ( var i = 0, j=ids.length; i < j; i += batchSize ) {
idBatch = ids.slice(i, i + batchSize);
idBatches.push(idBatch);
}
// Start workers
for ( var i = 0; i < parallelConnections; i += 1 ) {
setTimeout(function(){
this.scoreBatch(idBatches, models, deferred, i);
}.bind(this), 0);
}
return deferred.promise();
}
};
module.exports = new OresApi( {}, mw.config.get( 'extOresApiConfig' ) );
}( mediaWiki, jQuery ) );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.