Mooeypoo has uploaded a new change for review. https://gerrit.wikimedia.org/r/252597
Change subject: [wip] Add a NetworkHandler as a central API authority to Echo ...................................................................... [wip] Add a NetworkHandler as a central API authority to Echo This is in preparation for dealing with cross-wiki notifications where we may need several types of operations to extract bundled notifications from local and external APIs. Also, organize all network-related helpers in their own folder. Change-Id: Ib730c780ea52c93a6026c5d0b22012b6f39bb50d --- M Resources.php M modules/ext.echo.init.js R modules/viewmodel/handlers/mw.echo.dm.APIHandler.js A modules/viewmodel/handlers/mw.echo.dm.ForeignAPIHandler.js R modules/viewmodel/handlers/mw.echo.dm.LocalAPIHandler.js A modules/viewmodel/handlers/mw.echo.dm.NetworkHandler.js M modules/viewmodel/mw.echo.dm.NotificationsModel.js M tests/qunit/viewmodel/test_mw.echo.dm.NotificationsModel.js 8 files changed, 154 insertions(+), 54 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo refs/changes/97/252597/1 diff --git a/Resources.php b/Resources.php index 9bbe643..7b09f6e 100644 --- a/Resources.php +++ b/Resources.php @@ -84,11 +84,12 @@ 'ext.echo.dm' => $echoResourceTemplate + array( 'scripts' => array( 'viewmodel/mw.echo.dm.js', - 'viewmodel/mw.echo.dm.NotificationItem.js', - 'viewmodel/mw.echo.dm.AbstractAPIHandler.js', - 'viewmodel/mw.echo.dm.APIHandler.js', 'viewmodel/mw.echo.dm.List.js', 'viewmodel/mw.echo.dm.SortedList.js', + 'viewmodel/handlers/mw.echo.dm.APIHandler.js', + 'viewmodel/handlers/mw.echo.dm.LocalAPIHandler.js', + 'viewmodel/handlers/mw.echo.dm.NetworkHandler.js', + 'viewmodel/mw.echo.dm.NotificationItem.js', 'viewmodel/mw.echo.dm.NotificationList.js', 'viewmodel/mw.echo.dm.NotificationsModel.js', ), diff --git a/modules/ext.echo.init.js b/modules/ext.echo.init.js index 0d84e4f..0376691 100644 --- a/modules/ext.echo.init.js +++ b/modules/ext.echo.init.js @@ -34,7 +34,8 @@ // Respond to click on the notification button and load the UI on demand $( '.mw-echo-notification-badge-nojs' ).click( function ( e ) { - var myType = $( this ).parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message', + var alertNetworkHandler, msgNetworkHandler, + myType = $( this ).parent().prop( 'id' ) === 'pt-notifications-alert' ? 'alert' : 'message', time = mw.now(); if ( e.which !== 1 ) { @@ -46,10 +47,10 @@ // Fire the notification API requests apiRequest = new mw.Api( { ajax: { cache: false } } ).get( $.extend( { notsections: myType }, mw.echo.apiCallParams ) ) - .then( function ( data ) { - mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - time ); - return data; - } ); + .then( function ( data ) { + mw.track( 'timing.MediaWiki.echo.overlay.api', mw.now() - time ); + return data; + } ); // Load the ui mw.loader.using( 'ext.echo.ui', function () { @@ -57,13 +58,12 @@ // Load message button and popup if messages exist if ( $existingMessageLink.length ) { + msgNetworkHandler = new mw.echo.dm.NetworkHandler( { + type: 'message', + baseParams: mw.echo.apiCallParams + } ); messageNotificationsModel = new mw.echo.dm.NotificationsModel( - new mw.echo.dm.APIHandler( { - type: 'message', - limit: 25, - userLang: mw.config.get( 'wgUserLanguage' ), - baseParams: mw.echo.apiCallParams - } ), + msgNetworkHandler, { type: 'message' } @@ -88,15 +88,15 @@ .text( mw.msg( 'mytalk' ) ); } ); } - // Load alerts popup and button + + // Create a network handler + alertNetworkHandler = new mw.echo.dm.NetworkHandler( { + type: 'alert', + baseParams: mw.echo.apiCallParams + } ); alertNotificationsModel = new mw.echo.dm.NotificationsModel( - new mw.echo.dm.APIHandler( { - type: 'alert', - limit: 25, - userLang: mw.config.get( 'wgUserLanguage' ), - baseParams: mw.echo.apiCallParams - } ), + alertNetworkHandler, { type: 'alert' } diff --git a/modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js b/modules/viewmodel/handlers/mw.echo.dm.APIHandler.js similarity index 77% rename from modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js rename to modules/viewmodel/handlers/mw.echo.dm.APIHandler.js index 1ce0a38..97b6320 100644 --- a/modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js +++ b/modules/viewmodel/handlers/mw.echo.dm.APIHandler.js @@ -14,7 +14,7 @@ * @cfg {string} [type='alert'] Notification type * @cfg {string} [userLang='en'] User language */ - mw.echo.dm.AbstractAPIHandler = function MwEchoDmAPIHandler( config ) { + mw.echo.dm.APIHandler = function MwEchoDmAPIHandler( config ) { config = config || {}; // Mixin constructor @@ -33,8 +33,8 @@ /* Setup */ - OO.initClass( mw.echo.dm.AbstractAPIHandler ); - OO.mixinClass( mw.echo.dm.AbstractAPIHandler, OO.EventEmitter ); + OO.initClass( mw.echo.dm.APIHandler ); + OO.mixinClass( mw.echo.dm.APIHandler, OO.EventEmitter ); /** * Fetch notifications from the API. @@ -46,7 +46,7 @@ * @return {jQuery.Promise} A promise that resolves with an object containing the * notification items */ - mw.echo.dm.AbstractAPIHandler.prototype.fetchNotifications = null; + mw.echo.dm.APIHandler.prototype.fetchNotifications = null; /** * Update the seen timestamp @@ -55,7 +55,7 @@ * an array of both. * @return {jQuery.Promise} A promise that resolves with the seen timestamp */ - mw.echo.dm.AbstractAPIHandler.prototype.updateSeenTime = null; + mw.echo.dm.APIHandler.prototype.updateSeenTime = null; /** * Mark all notifications as read @@ -63,7 +63,7 @@ * @return {jQuery.Promise} A promise that resolves when all notifications * are marked as read. */ - mw.echo.dm.AbstractAPIHandler.prototype.markAllRead = null; + mw.echo.dm.APIHandler.prototype.markAllRead = null; /** * Update the read status of a notification item in the API @@ -72,7 +72,7 @@ * @return {jQuery.Promise} A promise that resolves when the notifications * are marked as read. */ - mw.echo.dm.AbstractAPIHandler.prototype.markItemRead = null; + mw.echo.dm.APIHandler.prototype.markItemRead = null; /** * Query the API for unread count of the notifications in this model @@ -80,14 +80,14 @@ * @return {jQuery.Promise} jQuery promise that's resolved when the unread count is fetched * and the badge label is updated. */ - mw.echo.dm.AbstractAPIHandler.prototype.fetchUnreadCount = null; + mw.echo.dm.APIHandler.prototype.fetchUnreadCount = null; /** * Check whether the model is fetching notifications from the API * * @return {boolean} The model is in the process of fetching from the API */ - mw.echo.dm.AbstractAPIHandler.prototype.isFetchingNotifications = function () { + mw.echo.dm.APIHandler.prototype.isFetchingNotifications = function () { return !!this.fetchNotificationsPromise; }; @@ -96,7 +96,7 @@ * * @return {boolean} The model is in API error state */ - mw.echo.dm.AbstractAPIHandler.prototype.isFetchingErrorState = function () { + mw.echo.dm.APIHandler.prototype.isFetchingErrorState = function () { return !!this.apiErrorState; }; @@ -105,7 +105,7 @@ * @return {jQuery.Promise} Promise that is resolved when notifications are * fetched from the API. */ - mw.echo.dm.AbstractAPIHandler.prototype.getFetchNotificationPromise = function () { + mw.echo.dm.APIHandler.prototype.getFetchNotificationPromise = function () { return this.fetchNotificationsPromise; }; @@ -114,7 +114,7 @@ * * @return {Object} Base API params */ - mw.echo.dm.AbstractAPIHandler.prototype.getBaseParams = function () { + mw.echo.dm.APIHandler.prototype.getBaseParams = function () { return this.baseParams; }; } )( mediaWiki ); diff --git a/modules/viewmodel/handlers/mw.echo.dm.ForeignAPIHandler.js b/modules/viewmodel/handlers/mw.echo.dm.ForeignAPIHandler.js new file mode 100644 index 0000000..46890be --- /dev/null +++ b/modules/viewmodel/handlers/mw.echo.dm.ForeignAPIHandler.js @@ -0,0 +1,23 @@ +( function ( mw, $ ) { + /** + * Foreign notification API handler + * + * @class + * @extends mw.echo.dm.LocalAPIHandler + * + * @constructor + * @param {Object} [config] Configuration object + */ + mw.echo.dm.ForeignAPIHandler = function MwEchoDmForeignAPIHandler( apiUrl, config ) { + config = config || {}; + + // Parent constructor + mw.echo.dm.ForeignAPIHandler.parent.call( this, config ); + + this.api = new mw.ForeignApi( apiUrl ); + }; + + /* Setup */ + + OO.inheritClass( mw.echo.dm.ForeignAPIHandler, mw.echo.dm.LocalAPIHandler ); +} )( mediaWiki, jQuery ); diff --git a/modules/viewmodel/mw.echo.dm.APIHandler.js b/modules/viewmodel/handlers/mw.echo.dm.LocalAPIHandler.js similarity index 78% rename from modules/viewmodel/mw.echo.dm.APIHandler.js rename to modules/viewmodel/handlers/mw.echo.dm.LocalAPIHandler.js index 7818daa..b85281b 100644 --- a/modules/viewmodel/mw.echo.dm.APIHandler.js +++ b/modules/viewmodel/handlers/mw.echo.dm.LocalAPIHandler.js @@ -3,28 +3,28 @@ * Notification API handler * * @class - * @extends mw.echo.dm.AbstractAPIHandler + * @extends mw.echo.dm.APIHandler * * @constructor * @param {Object} [config] Configuration object */ - mw.echo.dm.APIHandler = function MwEchoDmAPIHandler( config ) { + mw.echo.dm.LocalAPIHandler = function MwEchoDmAPIHandler( config ) { config = config || {}; // Parent constructor - mw.echo.dm.APIHandler.parent.call( this, config ); + mw.echo.dm.LocalAPIHandler.parent.call( this, config ); this.api = new mw.Api( { ajax: { cache: false } } ); }; /* Setup */ - OO.inheritClass( mw.echo.dm.APIHandler, mw.echo.dm.AbstractAPIHandler ); + OO.inheritClass( mw.echo.dm.LocalAPIHandler, mw.echo.dm.APIHandler ); /** * @inheritdoc */ - mw.echo.dm.APIHandler.prototype.fetchNotifications = function ( apiPromise ) { + mw.echo.dm.LocalAPIHandler.prototype.fetchNotifications = function ( apiPromise ) { var helper = this, params = $.extend( { notsections: this.type }, this.getBaseParams() ); @@ -46,7 +46,7 @@ /** * @inheritdoc */ - mw.echo.dm.APIHandler.prototype.updateSeenTime = function ( type ) { + mw.echo.dm.LocalAPIHandler.prototype.updateSeenTime = function ( type ) { type = type || this.type; return this.api.postWithToken( 'edit', { @@ -63,7 +63,7 @@ /** * @inheritdoc */ - mw.echo.dm.APIHandler.prototype.markAllRead = function () { + mw.echo.dm.LocalAPIHandler.prototype.markAllRead = function () { var model = this, data = { action: 'echomarkread', @@ -80,7 +80,7 @@ /** * @inheritdoc */ - mw.echo.dm.APIHandler.prototype.markItemRead = function ( itemId ) { + mw.echo.dm.LocalAPIHandler.prototype.markItemRead = function ( itemId ) { var model = this, data = { action: 'echomarkread', @@ -97,7 +97,7 @@ /** * @inheritdoc */ - mw.echo.dm.APIHandler.prototype.fetchUnreadCount = function () { + mw.echo.dm.LocalAPIHandler.prototype.fetchUnreadCount = function () { var apiData = { action: 'query', meta: 'notifications', diff --git a/modules/viewmodel/handlers/mw.echo.dm.NetworkHandler.js b/modules/viewmodel/handlers/mw.echo.dm.NetworkHandler.js new file mode 100644 index 0000000..9636296 --- /dev/null +++ b/modules/viewmodel/handlers/mw.echo.dm.NetworkHandler.js @@ -0,0 +1,68 @@ +( function ( mw, $ ) { + /** + * Network handler for echo notifications. Manages multiple APIHandlers + * according to their sources. + * + * @class + * @mixins OO.EventEmitter + * + * @constructor + * @param {Object} [config] Configuration object + */ + mw.echo.dm.NetworkHandler = function MwEchoDmNetworkHandler( config ) { + config = config || {}; + + // Mixin constructor + OO.EventEmitter.call( this ); + + this.type = config.type || 'alert'; + this.baseParams = config.baseParams || {}; + this.handlers = {}; + + // Add initial local handler + this.addApiHandler( 'local', {} ); + }; + + /* Setup */ + + OO.initClass( mw.echo.dm.NetworkHandler ); + OO.mixinClass( mw.echo.dm.NetworkHandler, OO.EventEmitter ); + + /* Methods */ + + /** + * Get the API handler that matches the symbolic name + * + * @param {string} name Symbolic name of the API handler + * @return {mw.echo.dm.APIHandler|undefined} API handler, if exists + */ + mw.echo.dm.NetworkHandler.prototype.getApiHandler = function ( name ) { + return this.handlers[ name ]; + }; + + /** + * Add an API handler + * + * @param {string} name Symbolic name + * @param {Object} details Configuration details + * @param {boolean} isExternal Is an external API + * @throws {Error} If no URL was given for a foreign API + */ + mw.echo.dm.NetworkHandler.prototype.addApiHandler = function ( name, config, isExternal ) { + if ( !this.handlers[ name ] ) { + config.baseParams = $.extend( {}, config.baseParams, this.baseParams ); + + if ( isExternal ) { + if ( !config.url ) { + throw new Error( 'External APIs must have a valid url.' ); + } else { + this.handlers[ name ] = new mw.echo.dm.ForeignAPIHandler( config.url, config ); + } + } else { + this.handlers[ name ] = new mw.echo.dm.LocalAPIHandler( config ); + } + } + + }; + +} )( mediaWiki, jQuery ); diff --git a/modules/viewmodel/mw.echo.dm.NotificationsModel.js b/modules/viewmodel/mw.echo.dm.NotificationsModel.js index 4904297..f6a3656 100644 --- a/modules/viewmodel/mw.echo.dm.NotificationsModel.js +++ b/modules/viewmodel/mw.echo.dm.NotificationsModel.js @@ -6,14 +6,16 @@ * @mixins OO.EventEmitter * * @constructor - * @param {mw.echo.dm.AbstractAPIHandler} apiHandler API handler + * @param {mw.echo.dm.NetworkHandler} networkHandler Network handler * @param {Object} [config] Configuration object * @cfg {string|string[]} [type='alert'] Notification type 'alert', 'message' * or an array [ 'alert', 'message' ] + * @cfg {string} [source='local'] Model source, 'local' or some symbolic name identifying + * the source of the notification items for the network handler. * @cfg {number} [limit=25] Notification limit * @cfg {string} [userLang] User language */ - mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel( apiHandler, config ) { + mw.echo.dm.NotificationsModel = function MwEchoDmNotificationsModel( networkHandler, config ) { config = config || {}; // Mixin constructor @@ -23,8 +25,9 @@ mw.echo.dm.SortedList.call( this ); this.type = config.type || 'alert'; + this.source = config.source || 'local'; - this.apiHandler = apiHandler; + this.networkHandler = networkHandler; this.seenTime = mw.config.get( 'wgEchoSeenTime' ) || {}; @@ -244,7 +247,7 @@ } this.emit( 'updateSeenTime' ); - return this.apiHandler.updateSeenTime( type ) + return this.getApi().updateSeenTime( type ) .then( this.setSeenTime.bind( this ) ); }; @@ -261,7 +264,7 @@ return $.Deferred().resolve( 0 ).promise(); } - return this.apiHandler.markAllRead() + return this.getApi().markAllRead() .then( function () { var i, len, items = model.unreadNotifications.getItems(); @@ -286,7 +289,7 @@ return $.Deferred().resolve( 0 ).promise(); } - return this.apiHandler.markItemRead( itemId ); + return this.getApi().markItemRead( itemId ); }; /** @@ -304,7 +307,7 @@ // Rebuild the notifications promise either when it is null or when // it exists in a failed state - return this.apiHandler.fetchNotifications( apiPromise ) + return this.getApi().fetchNotifications( apiPromise ) .then( function ( result ) { var notifData, i, len, t, tlen, $content, notificationModel, types, @@ -410,7 +413,7 @@ * and the badge label is updated. */ mw.echo.dm.NotificationsModel.prototype.fetchUnreadCountFromApi = function () { - return this.apiHandler.fetchUnreadCount(); + return this.getApi().fetchUnreadCount(); }; /** @@ -419,7 +422,7 @@ * @return {boolean} The model is in the process of fetching from the API */ mw.echo.dm.NotificationsModel.prototype.isFetchingNotifications = function () { - return this.apiHandler.isFetchingNotifications(); + return this.getApi().isFetchingNotifications(); }; /** @@ -428,7 +431,7 @@ * @return {boolean} The model is in api error state */ mw.echo.dm.NotificationsModel.prototype.isFetchingErrorState = function () { - return this.apiHandler.isFetchingErrorState(); + return this.getApi().isFetchingErrorState(); }; /** @@ -437,6 +440,11 @@ * fetched from the API. */ mw.echo.dm.NotificationsModel.prototype.getFetchNotificationPromise = function () { - return this.apiHandler.getFetchNotificationPromise(); + return this.getApi().getFetchNotificationPromise(); }; + + mw.echo.dm.NotificationsModel.prototype.getApi = function () { + return this.networkHandler.getApiHandler( this.source ); + }; + } )( mediaWiki, jQuery ); diff --git a/tests/qunit/viewmodel/test_mw.echo.dm.NotificationsModel.js b/tests/qunit/viewmodel/test_mw.echo.dm.NotificationsModel.js index 7a6e53d..b1ba4f5 100644 --- a/tests/qunit/viewmodel/test_mw.echo.dm.NotificationsModel.js +++ b/tests/qunit/viewmodel/test_mw.echo.dm.NotificationsModel.js @@ -22,7 +22,7 @@ TestApiHandler.parent.call( this ); } /* Setup */ - OO.inheritClass( TestApiHandler, mw.echo.dm.AbstractAPIHandler ); + OO.inheritClass( TestApiHandler, mw.echo.dm.APIHandler ); // Override api call TestApiHandler.prototype.markItemRead = function () { return $.Deferred().resolve( 0 ); -- To view, visit https://gerrit.wikimedia.org/r/252597 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ib730c780ea52c93a6026c5d0b22012b6f39bb50d Gerrit-PatchSet: 1 Gerrit-Project: mediawiki/extensions/Echo Gerrit-Branch: master Gerrit-Owner: Mooeypoo <mor...@gmail.com> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits