Hygiene: Split and organize the gateways

* Create new namespace mw.popups.gateway to contain them
* Create folder resources/ext.popups/gateway/
* Split gateway{,.test}.js into gateway/{mediawiki,rest}{.test,}.js
* Extract stateless functions out of factory scope now that there are no
  name collisions between the factories.

Bug: T123445
Change-Id: Ib256871c3e4cfe3f13361cb66d4e9a67e9823c7b
This commit is contained in:
joakin 2017-02-08 12:16:20 +01:00
parent 557fd113c8
commit 955189230a
8 changed files with 311 additions and 290 deletions

View file

@ -83,7 +83,10 @@
"resources/ext.popups/preview/index.js",
"resources/ext.popups/preview/model.js",
"resources/ext.popups/gateway.js",
"resources/ext.popups/gateway/index.js",
"resources/ext.popups/gateway/mediawiki.js",
"resources/ext.popups/gateway/rest.js",
"resources/ext.popups/renderer.js",
"resources/ext.popups/schema.js",

View file

@ -17,9 +17,9 @@
*/
function createGateway( config ) {
if ( config.get( 'wgPopupsAPIUseRESTBase' ) ) {
return mw.popups.createRESTBaseGateway( $.ajax );
return mw.popups.gateway.createRESTBaseGateway( $.ajax );
}
return mw.popups.createMediaWikiApiGateway( new mw.Api() );
return mw.popups.gateway.createMediaWikiApiGateway( new mw.Api() );
}
/**

View file

@ -1,186 +0,0 @@
( function ( mw, $ ) {
// FIXME: These constants (and others like 'em) should be in some top-level
// configuration file.
var EXTRACT_LENGTH = 525,
THUMBNAIL_SIZE = 300 * $.bracketedDevicePixelRatio(),
CACHE_LIFETIME = 300, // Public and private cache lifetime (5 minutes)
RESTBASE_ENDPOINT = '/api/rest_v1/page/summary/',
RESTBASE_PROFILE = 'https://www.mediawiki.org/wiki/Specs/Summary/1.0.0';
/**
* Interface for API gateway that fetches page summary
*
* @interface ext.popups.Gateway
*/
/**
* Returns a preview model fetched from the api
* @function
* @name ext.popups.Gateway#getPageSummary
* @param {String} title Page title we're querying
* @returns {jQuery.Promise} that resolves with {ext.popups.PreviewModel}
* if the request is successful and the response is not empty; otherwise
* it rejects.
*/
/**
* MediaWiki API gateway factory
*
* @param {mw.Api} api
* @returns {ext.popups.Gateway}
*/
function createMediaWikiApiGateway( api ) {
/**
* Fetch page data from the API
*
* @param {String} title
* @return {jQuery.Promise}
*/
function fetch( title ) {
return api.get( {
action: 'query',
prop: 'info|extracts|pageimages|revisions|info',
formatversion: 2,
redirects: true,
exintro: true,
exchars: EXTRACT_LENGTH,
// There is an added geometric limit on .mwe-popups-extract
// so that text does not overflow from the card.
explaintext: true,
piprop: 'thumbnail',
pithumbsize: THUMBNAIL_SIZE,
rvprop: 'timestamp',
inprop: 'url',
titles: title,
smaxage: CACHE_LIFETIME,
maxage: CACHE_LIFETIME,
uselang: 'content'
}, {
headers: {
'X-Analytics': 'preview=1'
}
} );
}
/**
* Extract page data from the response
*
* @param {Object} data API response data
* @throws {Error} Throw an error if page data cannot be extracted,
* i.e. if the response is empty,
* @returns {Object}
*/
function extractPageFromResponse( data ) {
if (
data.query &&
data.query.pages &&
data.query.pages.length
) {
return data.query.pages[ 0 ];
}
throw new Error( 'API response `query.pages` is empty.' );
}
/**
* Transform the API response to a preview model
*
* @param {Object} page
* @returns {ext.popups.PreviewModel}
*/
function convertPageToModel( page ) {
return mw.popups.preview.createModel(
page.title,
page.canonicalurl,
page.pagelanguagehtmlcode,
page.pagelanguagedir,
page.extract,
page.thumbnail
);
}
/**
* Get the page summary from the api and transform the data
*
* @param {String} title
* @returns {jQuery.Promise<ext.popups.PreviewModel>}
*/
function getPageSummary( title ) {
return fetch( title )
.then( extractPageFromResponse )
.then( convertPageToModel );
}
return {
fetch: fetch,
extractPageFromResponse: extractPageFromResponse,
convertPageToModel: convertPageToModel,
getPageSummary: getPageSummary
};
}
/**
* RESTBase gateway factory
*
* @param {Function} ajax function from jQuery for example
* @returns {ext.popups.Gateway}
*/
function createRESTBaseGateway( ajax ) {
/**
* Fetch page data from the API
*
* @param {String} title
* @return {jQuery.Promise}
*/
function fetch( title ) {
return ajax( {
url: RESTBASE_ENDPOINT + encodeURIComponent( title ),
headers: {
Accept: 'application/json; charset=utf-8' +
'profile="' + RESTBASE_PROFILE + '"'
}
} );
}
/**
* Transform the API response to a preview model
*
* @param {Object} page
* @returns {ext.popups.PreviewModel}
*/
function convertPageToModel( page ) {
return mw.popups.preview.createModel(
page.title,
new mw.Title( page.title ).getUrl(),
page.lang,
page.dir,
page.extract,
page.thumbnail
);
}
/**
* Get the page summary from the api and transform the data
*
* @param {String} title
* @returns {jQuery.Promise<ext.popups.PreviewModel>}
*/
function getPageSummary( title ) {
return fetch( title )
.then( convertPageToModel );
}
return {
fetch: fetch,
convertPageToModel: convertPageToModel,
getPageSummary: getPageSummary
};
}
mw.popups.createMediaWikiApiGateway = createMediaWikiApiGateway;
mw.popups.createRESTBaseGateway = createRESTBaseGateway;
}( mediaWiki, jQuery ) );

View file

@ -0,0 +1,21 @@
( function ( mw ) {
mw.popups.gateway = {};
/**
* Interface for API gateway that fetches page summary
*
* @interface ext.popups.Gateway
*/
/**
* Returns a preview model fetched from the api
* @function
* @name ext.popups.Gateway#getPageSummary
* @param {String} title Page title we're querying
* @returns {jQuery.Promise} that resolves with {ext.popups.PreviewModel}
* if the request is successful and the response is not empty; otherwise
* it rejects.
*/
}( mediaWiki ) );

View file

@ -0,0 +1,109 @@
( function ( mw, $ ) {
var EXTRACT_LENGTH = 525,
THUMBNAIL_SIZE = 300 * $.bracketedDevicePixelRatio(),
// Public and private cache lifetime (5 minutes)
CACHE_LIFETIME = 300;
/**
* MediaWiki API gateway factory
*
* @param {mw.Api} api
* @returns {ext.popups.Gateway}
*/
function createMediaWikiApiGateway( api ) {
/**
* Fetch page data from the API
*
* @param {String} title
* @return {jQuery.Promise}
*/
function fetch( title ) {
return api.get( {
action: 'query',
prop: 'info|extracts|pageimages|revisions|info',
formatversion: 2,
redirects: true,
exintro: true,
exchars: EXTRACT_LENGTH,
// There is an added geometric limit on .mwe-popups-extract
// so that text does not overflow from the card.
explaintext: true,
piprop: 'thumbnail',
pithumbsize: THUMBNAIL_SIZE,
rvprop: 'timestamp',
inprop: 'url',
titles: title,
smaxage: CACHE_LIFETIME,
maxage: CACHE_LIFETIME,
uselang: 'content'
}, {
headers: {
'X-Analytics': 'preview=1'
}
} );
}
/**
* Get the page summary from the api and transform the data
*
* @param {String} title
* @returns {jQuery.Promise<ext.popups.PreviewModel>}
*/
function getPageSummary( title ) {
return fetch( title )
.then( extractPageFromResponse )
.then( convertPageToModel );
}
return {
fetch: fetch,
extractPageFromResponse: extractPageFromResponse,
convertPageToModel: convertPageToModel,
getPageSummary: getPageSummary
};
}
/**
* Extract page data from the MediaWiki API response
*
* @param {Object} data API response data
* @throws {Error} Throw an error if page data cannot be extracted,
* i.e. if the response is empty,
* @returns {Object}
*/
function extractPageFromResponse( data ) {
if (
data.query &&
data.query.pages &&
data.query.pages.length
) {
return data.query.pages[ 0 ];
}
throw new Error( 'API response `query.pages` is empty.' );
}
/**
* Transform the MediaWiki API response to a preview model
*
* @param {Object} page
* @returns {ext.popups.PreviewModel}
*/
function convertPageToModel( page ) {
return mw.popups.preview.createModel(
page.title,
page.canonicalurl,
page.pagelanguagehtmlcode,
page.pagelanguagedir,
page.extract,
page.thumbnail
);
}
mw.popups.gateway.createMediaWikiApiGateway = createMediaWikiApiGateway;
}( mediaWiki, jQuery ) );

View file

@ -0,0 +1,67 @@
( function ( mw ) {
var RESTBASE_ENDPOINT = '/api/rest_v1/page/summary/',
RESTBASE_PROFILE = 'https://www.mediawiki.org/wiki/Specs/Summary/1.0.0';
/**
* RESTBase gateway factory
*
* @param {Function} ajax function from jQuery for example
* @returns {ext.popups.Gateway}
*/
function createRESTBaseGateway( ajax ) {
/**
* Fetch page data from the API
*
* @param {String} title
* @return {jQuery.Promise}
*/
function fetch( title ) {
return ajax( {
url: RESTBASE_ENDPOINT + encodeURIComponent( title ),
headers: {
Accept: 'application/json; charset=utf-8' +
'profile="' + RESTBASE_PROFILE + '"'
}
} );
}
/**
* Get the page summary from the api and transform the data
*
* @param {String} title
* @returns {jQuery.Promise<ext.popups.PreviewModel>}
*/
function getPageSummary( title ) {
return fetch( title )
.then( convertPageToModel );
}
return {
fetch: fetch,
convertPageToModel: convertPageToModel,
getPageSummary: getPageSummary
};
}
/**
* Transform the rest API response to a preview model
*
* @param {Object} page
* @returns {ext.popups.PreviewModel}
*/
function convertPageToModel( page ) {
return mw.popups.preview.createModel(
page.title,
new mw.Title( page.title ).getUrl(),
page.lang,
page.dir,
page.extract,
page.thumbnail
);
}
mw.popups.gateway.createRESTBaseGateway = createRESTBaseGateway;
}( mediaWiki ) );

View file

@ -1,8 +1,7 @@
( function ( mw, $ ) {
var createModel = mw.popups.preview.createModel,
createMediaWikiApiGateway = mw.popups.createMediaWikiApiGateway,
createRESTBaseGateway = mw.popups.createRESTBaseGateway,
createMediaWikiApiGateway = mw.popups.gateway.createMediaWikiApiGateway,
MEDIAWIKI_API_RESPONSE = {
query: {
pages: [
@ -44,34 +43,9 @@
source: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Rick_Astley_-_Pepsifest_2009.jpg/200px-Rick_Astley_-_Pepsifest_2009.jpg',
width: 200
}
),
RESTBASE_RESPONSE = {
title: 'Barack Obama',
extract: 'Barack Hussein Obama II born August 4, 1961) ...',
thumbnail: {
source: 'https://upload.wikimedia.org/someImage.jpg',
width: 256,
height: 320
},
lang: 'en',
dir: 'ltr',
timestamp: '2017-01-30T10:17:41Z',
description: '44th President of the United States of America'
},
RESTBASE_RESPONSE_PREVIEW_MODEL = createModel(
'Barack Obama',
new mw.Title( 'Barack Obama' ).getUrl(),
'en',
'ltr',
'Barack Hussein Obama II born August 4, 1961) ...',
{
source: 'https://upload.wikimedia.org/someImage.jpg',
width: 256,
height: 320
}
);
QUnit.module( 'ext.popups/gateway' );
QUnit.module( 'ext.popups/gateway/mediawiki' );
QUnit.test( 'MediaWiki API gateway is called with correct arguments', function ( assert ) {
var spy = this.sandbox.spy(),
@ -234,77 +208,4 @@
} );
} );
QUnit.test( 'RESTBase gateway is called with correct arguments', function ( assert ) {
var getSpy = this.sandbox.spy(),
gateway = createRESTBaseGateway( getSpy ),
expectedOptions = {
url: '/api/rest_v1/page/summary/' + encodeURIComponent( 'Test Title' ),
headers: {
Accept: 'application/json; charset=utf-8' +
'profile="https://www.mediawiki.org/wiki/Specs/Summary/1.0.0"'
}
};
gateway.fetch( 'Test Title' );
assert.deepEqual( getSpy.getCall( 0 ).args[ 0 ], expectedOptions, 'options' );
} );
QUnit.test( 'RESTBase gateway is correctly converting the page data to a model ', function ( assert ) {
var gateway = createRESTBaseGateway();
assert.deepEqual(
gateway.convertPageToModel( RESTBASE_RESPONSE ),
RESTBASE_RESPONSE_PREVIEW_MODEL
);
} );
QUnit.test( 'RESTBase gateway handles the API failure', function ( assert ) {
var deferred = $.Deferred(),
api = this.sandbox.stub().returns( deferred.promise() ),
gateway = createRESTBaseGateway( api ),
done = assert.async( 1 );
gateway.getPageSummary( 'Test Title' ).fail( function () {
assert.ok( true );
done();
} );
deferred.reject();
} );
QUnit.test( 'RESTBase gateway returns the correct data ', function ( assert ) {
var api = this.sandbox.stub().returns(
$.Deferred().resolve( RESTBASE_RESPONSE ).promise()
),
gateway = createRESTBaseGateway( api ),
done = assert.async( 1 );
gateway.getPageSummary( 'Test Title' ).done( function ( result ) {
assert.deepEqual( result, RESTBASE_RESPONSE_PREVIEW_MODEL );
done();
} );
} );
QUnit.test( 'RESTBase gateway handles missing pages ', function ( assert ) {
var response = {
type: 'https://mediawiki.org/wiki/HyperSwitch/errors/not_found',
title: 'Not found.',
method: 'get',
detail: 'Page or revision not found.',
uri: '/en.wikipedia.org/v1/page/summary/Missing_page'
},
api = this.sandbox.stub().returns(
$.Deferred().rejectWith( response ).promise()
),
gateway = createRESTBaseGateway( api ),
done = assert.async( 1 );
gateway.getPageSummary( 'Missing Page' ).fail( function () {
assert.ok( true );
done();
} );
} );
}( mediaWiki, jQuery ) );

View file

@ -0,0 +1,106 @@
( function ( mw, $ ) {
var createModel = mw.popups.preview.createModel,
createRESTBaseGateway = mw.popups.gateway.createRESTBaseGateway,
RESTBASE_RESPONSE = {
title: 'Barack Obama',
extract: 'Barack Hussein Obama II born August 4, 1961) ...',
thumbnail: {
source: 'https://upload.wikimedia.org/someImage.jpg',
width: 256,
height: 320
},
lang: 'en',
dir: 'ltr',
timestamp: '2017-01-30T10:17:41Z',
description: '44th President of the United States of America'
},
RESTBASE_RESPONSE_PREVIEW_MODEL = createModel(
'Barack Obama',
new mw.Title( 'Barack Obama' ).getUrl(),
'en',
'ltr',
'Barack Hussein Obama II born August 4, 1961) ...',
{
source: 'https://upload.wikimedia.org/someImage.jpg',
width: 256,
height: 320
}
);
QUnit.module( 'ext.popups/gateway/rest' );
QUnit.test( 'RESTBase gateway is called with correct arguments', function ( assert ) {
var getSpy = this.sandbox.spy(),
gateway = createRESTBaseGateway( getSpy ),
expectedOptions = {
url: '/api/rest_v1/page/summary/' + encodeURIComponent( 'Test Title' ),
headers: {
Accept: 'application/json; charset=utf-8' +
'profile="https://www.mediawiki.org/wiki/Specs/Summary/1.0.0"'
}
};
gateway.fetch( 'Test Title' );
assert.deepEqual( getSpy.getCall( 0 ).args[ 0 ], expectedOptions, 'options' );
} );
QUnit.test( 'RESTBase gateway is correctly converting the page data to a model ', function ( assert ) {
var gateway = createRESTBaseGateway();
assert.deepEqual(
gateway.convertPageToModel( RESTBASE_RESPONSE ),
RESTBASE_RESPONSE_PREVIEW_MODEL
);
} );
QUnit.test( 'RESTBase gateway handles the API failure', function ( assert ) {
var deferred = $.Deferred(),
api = this.sandbox.stub().returns( deferred.promise() ),
gateway = createRESTBaseGateway( api ),
done = assert.async( 1 );
gateway.getPageSummary( 'Test Title' ).fail( function () {
assert.ok( true );
done();
} );
deferred.reject();
} );
QUnit.test( 'RESTBase gateway returns the correct data ', function ( assert ) {
var api = this.sandbox.stub().returns(
$.Deferred().resolve( RESTBASE_RESPONSE ).promise()
),
gateway = createRESTBaseGateway( api ),
done = assert.async( 1 );
gateway.getPageSummary( 'Test Title' ).done( function ( result ) {
assert.deepEqual( result, RESTBASE_RESPONSE_PREVIEW_MODEL );
done();
} );
} );
QUnit.test( 'RESTBase gateway handles missing pages ', function ( assert ) {
var response = {
type: 'https://mediawiki.org/wiki/HyperSwitch/errors/not_found',
title: 'Not found.',
method: 'get',
detail: 'Page or revision not found.',
uri: '/en.wikipedia.org/v1/page/summary/Missing_page'
},
api = this.sandbox.stub().returns(
$.Deferred().rejectWith( response ).promise()
),
gateway = createRESTBaseGateway( api ),
done = assert.async( 1 );
gateway.getPageSummary( 'Missing Page' ).fail( function () {
assert.ok( true );
done();
} );
} );
}( mediaWiki, jQuery ) );