Mooeypoo has uploaded a new change for review.
https://gerrit.wikimedia.org/r/247021
Change subject: Add an APIHandler to Echo notifications
......................................................................
Add an APIHandler to Echo notifications
This simplifies the operation of the API a bit, but more importantly
this will let us create a demo where we can manipulate the API result
and test various new notification formats while the work on the
backend api responses is ongoing, and also will allow us to have a
constant test for all notifications types, including backwards
compatibility.
Change-Id: I6081329a287cda4f5f1f1604ace5d04ff8d9fe3d
---
M Resources.php
A modules/viewmodel/mw.echo.dm.APIHandler.js
A modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js
M modules/viewmodel/mw.echo.dm.NotificationsModel.js
4 files changed, 307 insertions(+), 129 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Echo
refs/changes/21/247021/1
diff --git a/Resources.php b/Resources.php
index 335c2f7..131c1bd 100644
--- a/Resources.php
+++ b/Resources.php
@@ -87,6 +87,8 @@
'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.NotificationList.js',
'viewmodel/mw.echo.dm.NotificationsModel.js',
diff --git a/modules/viewmodel/mw.echo.dm.APIHandler.js
b/modules/viewmodel/mw.echo.dm.APIHandler.js
new file mode 100644
index 0000000..81f2976
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.APIHandler.js
@@ -0,0 +1,112 @@
+( function ( mw, $ ) {
+ /**
+ * Notification API handler
+ *
+ * @class
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration object
+ */
+ mw.echo.dm.APIHandler = function MwEchoDmAPIHandler( config ) {
+ config = config || {};
+
+ // Parent constructor
+ mw.echo.dm.APIHandler.parent.call( this, config );
+
+ this.api = new mw.Api( { ajax: { cache: false } } );
+ };
+
+ /* Setup */
+
+ OO.inheritClass( mw.echo.dm.APIHandler, mw.echo.dm.AbstractAPIHandler );
+
+ /**
+ * @inheritdoc
+ */
+ mw.echo.dm.APIHandler.prototype.fetchNotifications = function (
apiPromise ) {
+ var helper = this,
+ params = $.extend( { notsections: this.type },
mw.echo.apiCallParams );
+
+ if ( !this.fetchNotificationsPromise ||
this.isFetchingErrorState() ) {
+ this.apiErrorState = false;
+ this.fetchNotificationsPromise = ( apiPromise ||
this.api.get( params ) )
+ .fail( function () {
+ // Mark API error state
+ helper.apiErrorState = true;
+ } )
+ .always( function () {
+ helper.fetchNotificationsPromise = null;
+ } );
+ }
+
+ return this.fetchNotificationsPromise;
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.echo.dm.APIHandler.prototype.updateSeenTime = function () {
+ return this.api.postWithToken( 'edit', {
+ action: 'echomarkseen',
+ type: this.type
+ } )
+ .then( function ( data ) {
+ return data.query.echomarkseen.timestamp;
+ } );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.echo.dm.APIHandler.prototype.markAllRead = function () {
+ var model = this,
+ data = {
+ action: 'echomarkread',
+ uselang: this.userLang,
+ sections: this.type
+ };
+
+ return this.api.postWithToken( 'edit', data )
+ .then( function ( result ) {
+ return
result.query.echomarkread[model.type].rawcount || 0;
+ } );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.echo.dm.APIHandler.prototype.markItemRead = function ( itemId ) {
+ var model = this,
+ data = {
+ action: 'echomarkread',
+ uselang: this.userLang,
+ list: itemId
+ };
+
+ return this.api.postWithToken( 'edit', data )
+ .then( function ( result ) {
+ return
result.query.echomarkread[model.type].rawcount || 0;
+ } );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ mw.echo.dm.APIHandler.prototype.fetchUnreadCount = function () {
+ var apiData = {
+ action: 'query',
+ meta: 'notifications',
+ notsections: this.type,
+ notmessageunreadfirst: 1,
+ notlimit: this.limit,
+ notprop: 'index|count',
+ uselang: this.userLang
+ };
+
+ return this.api.get( apiData )
+ .then( function ( result ) {
+ return OO.getProp( result.query,
'notifications', 'rawcount' ) || 0;
+ } );
+ };
+} )( mediaWiki, jQuery );
diff --git a/modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js
b/modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js
new file mode 100644
index 0000000..bc0de44
--- /dev/null
+++ b/modules/viewmodel/mw.echo.dm.AbstractAPIHandler.js
@@ -0,0 +1,113 @@
+( function ( mw, $ ) {
+ /**
+ * Abstract notification API handler
+ *
+ * @abstract
+ * @class
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration object
+ */
+ mw.echo.dm.AbstractAPIHandler = function MwEchoDmAPIHandler( config ) {
+ config = config || {};
+
+ // Mixin constructor
+ OO.EventEmitter.call( this );
+
+ this.baseParams = config.baseParams || {};
+ this.fetchNotificationsPromise = null;
+ this.apiErrorState = false;
+
+ this.type = config.type || 'alert';
+ this.limit = config.limit || 25;
+ this.userLang = config.userLang || 'en';
+
+ this.api = new mw.Api( { ajax: { cache: false } } );
+ };
+
+ /* Setup */
+
+ OO.initClass( mw.echo.dm.AbstractAPIHandler );
+ OO.mixinClass( mw.echo.dm.AbstractAPIHandler, OO.EventEmitter );
+
+ /**
+ * Fetch notifications from the API.
+ *
+ * @param {jQuery.Promise} An existing promise querying the API for
notifications.
+ * This allows us to send an API request external to the DM and have
the model
+ * handle the operation as if it asked for the request itself,
updating all that
+ * needs to be updated and emitting all proper events.
+ * @return {jQuery.Promise} A promise that resolves with an object
containing the
+ * notification items
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.fetchNotifications = null;
+
+ /**
+ * Update the seen timestamp
+ *
+ * @return {jQuery.Promise} A promise that resolves with the seen
timestamp
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.updateSeenTime = null;
+
+ /**
+ * Mark all notifications as read
+ *
+ * @return {jQuery.Promise} A promise that resolves when all
notifications
+ * were marked as read.
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.markAllRead = null;
+
+ /**
+ * Update the read status of a notification item in the API
+ *
+ * @param {string} itemId Item id
+ * @return {jQuery.Promise} A promise that resolves when the
notifications
+ * were marked as read.
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.markItemRead = null;
+
+ /**
+ * Query the API for unread count of the notifications in this model
+ *
+ * @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;
+
+ /**
+ * 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 () {
+ return !!this.fetchNotificationsPromise;
+ };
+
+ /**
+ * Check whether the model has an api error state flagged
+ *
+ * @return {boolean} The model is in api error state
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.isFetchingErrorState = function
() {
+ return !!this.apiErrorState;
+ };
+
+ /**
+ * Return the fetch notifications promise
+ * @return {jQuery.Promise} Promise that is resolved when notifications
were
+ * fetched from the API.
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.getFetchNotificationPromise =
function () {
+ return this.fetchNotificationsPromise;
+ };
+
+ /**
+ * Get the base params associated with this API handler
+ *
+ * @return {Object} Base API params
+ */
+ mw.echo.dm.AbstractAPIHandler.prototype.getBaseParams = function () {
+ return this.baseParams;
+ };
+} )( mediaWiki, jQuery );
diff --git a/modules/viewmodel/mw.echo.dm.NotificationsModel.js
b/modules/viewmodel/mw.echo.dm.NotificationsModel.js
index f3c8a24..b768bd9 100644
--- a/modules/viewmodel/mw.echo.dm.NotificationsModel.js
+++ b/modules/viewmodel/mw.echo.dm.NotificationsModel.js
@@ -21,12 +21,16 @@
mw.echo.dm.List.call( this );
this.type = config.type || 'alert';
- this.limit = config.limit || 25;
- this.userLang = config.userLang || 'en';
- this.api = new mw.Api( { ajax: { cache: false } } );
- this.fetchNotificationsPromise = null;
- this.apiErrorState = false;
+ // TODO: This should be given to the model
+ this.apiHandler = new mw.echo.dm.APIHandler( {
+ type: this.type,
+ limit: config.limit || 25,
+ userLang: config.userLang || 'en'
+ } );
+ // this.api = new mw.Api( { ajax: { cache: false } } );
+ // this.fetchNotificationsPromise = null;
+ // this.apiErrorState = false;
this.seenTime = mw.config.get( 'wgEchoSeenTime' );
@@ -199,33 +203,6 @@
};
/**
- * 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.NotificationsModel.prototype.isFetchingNotifications =
function () {
- return !!this.fetchNotificationsPromise;
- };
-
- /**
- * Check whether the model has an api error state flagged
- *
- * @return {boolean} The model is in api error state
- */
- mw.echo.dm.NotificationsModel.prototype.isFetchingErrorState = function
() {
- return !!this.apiErrorState;
- };
-
- /**
- * Return the fetch notifications promise
- * @return {jQuery.Promise} Promise that is resolved when notifications
were
- * fetched from the API.
- */
- mw.echo.dm.NotificationsModel.prototype.getFetchNotificationPromise =
function () {
- return this.fetchNotificationsPromise;
- };
-
- /**
* Update the seen timestamp
*
* @return {jQuery.Promise} A promise that resolves with the seen
timestamp
@@ -233,7 +210,6 @@
*/
mw.echo.dm.NotificationsModel.prototype.updateSeenTime = function () {
var i, len,
- model = this,
items = this.unseenNotifications.getItems();
// Update the notifications seen status
@@ -242,16 +218,8 @@
}
this.emit( 'updateSeenTime' );
- return this.api.postWithToken( 'edit', {
- action: 'echomarkseen',
- type: this.type
- } )
- .then( function ( data ) {
- var time = data.query.echomarkseen.timestamp;
-
- // Update seen time from the server
- model.setSeenTime( time );
- } );
+ return this.apiHandler.updateSeenTime()
+ .then( this.setSeenTime.bind( this ) );
};
/**
@@ -261,21 +229,13 @@
* were marked as read.
*/
mw.echo.dm.NotificationsModel.prototype.markAllRead = function () {
- var model = this,
- data = {
- action: 'echomarkread',
- uselang: this.userLang,
- sections: this.type
- };
+ var model = this;
if ( !this.unreadNotifications.getItemCount() ) {
return $.Deferred().resolve( 0 ).promise();
}
- return this.api.postWithToken( 'edit', data )
- .then( function ( result ) {
- return
result.query.echomarkread[model.type].rawcount || 0;
- } )
+ return this.apiHAndler.markAllRead()
.then( function () {
var i, len,
items =
model.unreadNotifications.getItems();
@@ -296,21 +256,11 @@
* were marked as read.
*/
mw.echo.dm.NotificationsModel.prototype.markItemReadInApi = function (
itemId ) {
- var model = this,
- data = {
- action: 'echomarkread',
- uselang: this.userLang,
- list: itemId
- };
-
if ( !this.unreadNotifications.getItemCount() ) {
return $.Deferred().resolve( 0 ).promise();
}
- return this.api.postWithToken( 'edit', data )
- .then( function ( result ) {
- return
result.query.echomarkread[model.type].rawcount || 0;
- } );
+ return this.api.markItemRead();
};
/**
@@ -324,65 +274,51 @@
* id's.
*/
mw.echo.dm.NotificationsModel.prototype.fetchNotifications = function (
apiPromise ) {
- var model = this,
- params = $.extend( { notsections: this.type },
mw.echo.apiCallParams );
+ var model = this;
// Rebuild the notifications promise either when it is null or
when
// it exists in a failed state
- if ( !this.fetchNotificationsPromise ||
this.isFetchingErrorState() ) {
- this.apiErrorState = false;
- this.fetchNotificationsPromise = ( apiPromise ||
this.api.get( params ) )
- .then( function ( result ) {
- var notifData, i, len, $content,
wasSeen, wasRead, notificationModel,
- optionItems = [],
- idArray = [],
- data =
result.query.notifications[model.type];
+ return this.apiHandler.fetchNotifications( apiPromise )
+ .then( function ( result ) {
+ var notifData, i, len, $content, wasSeen,
wasRead, notificationModel,
+ optionItems = [],
+ idArray = [],
+ data =
result.query.notifications[model.type];
- for ( i = 0, len = data.index.length; i
< len; i++ ) {
- notifData = data.list[
data.index[i] ];
- if ( model.getItemById(
notifData.id ) ) {
- // Skip if we already
have the item
- continue;
- }
- // TODO: This should really be
formatted better, and the OptionWidget
- // should be the one that
displays whatever icon relates to this notification
- // according to its type.
- $content = $( $.parseHTML(
notifData['*'] ) );
-
- wasRead = !!notifData.read;
- wasSeen =
notifData.timestamp.mw <= model.getSeenTime();
- notificationModel = new
mw.echo.dm.NotificationItem(
- notifData.id,
- {
- read: wasRead,
- seen: wasSeen,
- timestamp:
notifData.timestamp.mw,
- category:
notifData.category,
- content:
$content,
- type:
model.getType(),
- // Hack: Get
the primary link from the $content
- primaryUrl:
$content.find( '.mw-echo-notification-primary-link' ).attr( 'href' )
- }
- );
-
- idArray.push( notifData.id );
- optionItems.push(
notificationModel );
+ for ( i = 0, len = data.index.length; i < len;
i++ ) {
+ notifData = data.list[ data.index[i] ];
+ if ( model.getItemById( notifData.id )
) {
+ // Skip if we already have the
item
+ continue;
}
- model.addItems( optionItems, 0 );
+ // TODO: This should really be
formatted better, and the OptionWidget
+ // should be the one that displays
whatever icon relates to this notification
+ // according to its type.
+ $content = $( $.parseHTML(
notifData['*'] ) );
- return idArray;
- } )
- .fail( function () {
- // Mark API error state
- model.apiErrorState = true;
- } )
- .always( function ( idArray ) {
- model.fetchNotificationsPromise = null;
+ wasRead = !!notifData.read;
+ wasSeen = notifData.timestamp.mw <=
model.getSeenTime();
+ notificationModel = new
mw.echo.dm.NotificationItem(
+ notifData.id,
+ {
+ read: wasRead,
+ seen: wasSeen,
+ timestamp:
notifData.timestamp.mw,
+ category:
notifData.category,
+ content: $content,
+ type: model.getType(),
+ // Hack: Get the
primary link from the $content
+ primaryUrl:
$content.find( '.mw-echo-notification-primary-link' ).attr( 'href' )
+ }
+ );
- return idArray;
- } );
- }
- return this.fetchNotificationsPromise;
+ idArray.push( notifData.id );
+ optionItems.push( notificationModel );
+ }
+ model.addItems( optionItems, 0 );
+
+ return idArray;
+ } );
};
/**
@@ -443,19 +379,34 @@
* and the badge label is updated.
*/
mw.echo.dm.NotificationsModel.prototype.fetchUnreadCountFromApi =
function () {
- var apiData = {
- action: 'query',
- meta: 'notifications',
- notsections: this.getType(),
- notmessageunreadfirst: 1,
- notlimit: this.limit,
- notprop: 'index|count',
- uselang: this.userLang
- };
+ return this.apiHandler.fetchUnreadCount();
+ };
- return this.api.get( apiData )
- .then( function ( result ) {
- return OO.getProp( result.query,
'notifications', 'rawcount' ) || 0;
- } );
+
+ /**
+ * 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.NotificationsModel.prototype.isFetchingNotifications =
function () {
+ return this.apiHandler.isFetchingNotifications();
+ };
+
+ /**
+ * Check whether the model has an api error state flagged
+ *
+ * @return {boolean} The model is in api error state
+ */
+ mw.echo.dm.NotificationsModel.prototype.isFetchingErrorState = function
() {
+ return this.apiHandler.isFetchingErrorState();
+ };
+
+ /**
+ * Return the fetch notifications promise
+ * @return {jQuery.Promise} Promise that is resolved when notifications
were
+ * fetched from the API.
+ */
+ mw.echo.dm.NotificationsModel.prototype.getFetchNotificationPromise =
function () {
+ return this.apiHandler.getFetchNotificationPromise();
};
} )( mediaWiki, jQuery );
--
To view, visit https://gerrit.wikimedia.org/r/247021
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I6081329a287cda4f5f1f1604ace5d04ff8d9fe3d
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Echo
Gerrit-Branch: master
Gerrit-Owner: Mooeypoo <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits