Add pagination to the notifications API and make the UI only show a maximum of 5 notifications (beyond that, it shows "5+").

This commit is contained in:
Joseph Schorr 2014-08-26 15:19:39 -04:00
parent c1b0b2383a
commit d76d4704a0
8 changed files with 67 additions and 26 deletions

View file

@ -1717,19 +1717,20 @@ def create_notification(kind_name, target, metadata={}):
def create_unique_notification(kind_name, target, metadata={}): def create_unique_notification(kind_name, target, metadata={}):
with config.app_config['DB_TRANSACTION_FACTORY'](db): with config.app_config['DB_TRANSACTION_FACTORY'](db):
if list_notifications(target, kind_name).count() == 0: if list_notifications(target, kind_name, limit=1).count() == 0:
create_notification(kind_name, target, metadata) create_notification(kind_name, target, metadata)
def lookup_notification(user, uuid): def lookup_notification(user, uuid):
results = list(list_notifications(user, id_filter=uuid, include_dismissed=True)) results = list(list_notifications(user, id_filter=uuid, include_dismissed=True, limit=1))
if not results: if not results:
return None return None
return results[0] return results[0]
def list_notifications(user, kind_name=None, id_filter=None, include_dismissed=False): def list_notifications(user, kind_name=None, id_filter=None, include_dismissed=False,
page=None, limit=None):
Org = User.alias() Org = User.alias()
AdminTeam = Team.alias() AdminTeam = Team.alias()
AdminTeamMember = TeamMember.alias() AdminTeamMember = TeamMember.alias()
@ -1767,6 +1768,11 @@ def list_notifications(user, kind_name=None, id_filter=None, include_dismissed=F
.switch(Notification) .switch(Notification)
.where(Notification.uuid == id_filter)) .where(Notification.uuid == id_filter))
if page:
query = query.paginate(page, limit)
elif limit:
query = query.limit(limit)
return query return query

View file

@ -7,8 +7,9 @@ from flask.ext.principal import identity_changed, AnonymousIdentity
from app import app, billing as stripe, authentication from app import app, billing as stripe, authentication
from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error, from endpoints.api import (ApiResource, nickname, resource, validate_json_request, request_error,
log_action, internal_only, NotFound, require_user_admin, log_action, internal_only, NotFound, require_user_admin, parse_args,
InvalidToken, require_scope, format_date, hide_if, show_if, license_error) query_param, InvalidToken, require_scope, format_date, hide_if, show_if,
license_error)
from endpoints.api.subscribe import subscribe from endpoints.api.subscribe import subscribe
from endpoints.common import common_login from endpoints.common import common_login
from data import model from data import model
@ -403,11 +404,24 @@ class Recovery(ApiResource):
@internal_only @internal_only
class UserNotificationList(ApiResource): class UserNotificationList(ApiResource):
@require_user_admin @require_user_admin
@parse_args
@query_param('page', 'Offset page number. (int)', type=int, default=0)
@query_param('limit', 'Limit on the number of results (int)', type=int, default=5)
@nickname('listUserNotifications') @nickname('listUserNotifications')
def get(self): def get(self, args):
notifications = model.list_notifications(get_authenticated_user()) page = args['page']
limit = args['limit']
notifications = list(model.list_notifications(get_authenticated_user(), page=page, limit=limit + 1))
has_more = False
if len(notifications) > limit:
has_more = True
notifications = notifications[0:limit]
return { return {
'notifications': [notification_view(notification) for notification in notifications] 'notifications': [notification_view(notification) for notification in notifications],
'additional': has_more
} }

View file

@ -184,6 +184,8 @@ class BuildFailureEvent(NotificationEvent):
return 'build_failure' return 'build_failure'
def get_sample_data(self, repository): def get_sample_data(self, repository):
build_uuid = 'fake-build-id'
return build_event_data(repository, { return build_event_data(repository, {
'build_id': build_uuid, 'build_id': build_uuid,
'build_name': 'some-fake-build', 'build_name': 'some-fake-build',

View file

@ -745,7 +745,7 @@ i.toggle-icon:hover {
} }
.user-notification.notification-animated { .user-notification.notification-animated {
width: 21px; min-width: 21px;
transform: scale(0); transform: scale(0);
-moz-transform: scale(0); -moz-transform: scale(0);

View file

@ -37,15 +37,7 @@
<a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown"> <a href="javascript:void(0)" class="dropdown-toggle user-dropdown user-view" data-toggle="dropdown">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=32&d=identicon" /> <img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=32&d=identicon" />
{{ user.username }} {{ user.username }}
<span class="badge user-notification notification-animated" <span class="notifications-bubble"></span>
ng-show="notificationService.notifications.length"
ng-class="notificationService.notificationClasses"
bs-tooltip=""
data-title="User Notifications"
data-placement="left"
data-container="body">
{{ notificationService.notifications.length }}
</span>
<b class="caret"></b> <b class="caret"></b>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
@ -58,11 +50,7 @@
<a href="javascript:void(0)" data-template="/static/directives/notification-bar.html" <a href="javascript:void(0)" data-template="/static/directives/notification-bar.html"
data-animation="am-slide-right" bs-aside="aside" data-container="body"> data-animation="am-slide-right" bs-aside="aside" data-container="body">
Notifications Notifications
<span class="badge user-notification" <span class="notifications-bubble"></span>
ng-class="notificationService.notificationClasses"
ng-show="notificationService.notifications.length">
{{ notificationService.notifications.length }}
</span>
</a> </a>
</li> </li>
<li><a ng-href="/organizations/" target="{{ appLinkTarget() }}">Organizations</a></li> <li><a ng-href="/organizations/" target="{{ appLinkTarget() }}">Organizations</a></li>

View file

@ -3,7 +3,10 @@
<div class="aside-content"> <div class="aside-content">
<div class="aside-header"> <div class="aside-header">
<button type="button" class="close" ng-click="$hide()">&times;</button> <button type="button" class="close" ng-click="$hide()">&times;</button>
<h4 class="aside-title">Notifications</h4> <h4 class="aside-title">
Notifications
<span class="notifications-bubble"></span>
</h4>
</div> </div>
<div class="aside-body"> <div class="aside-body">
<div ng-repeat="notification in notificationService.notifications"> <div ng-repeat="notification in notificationService.notifications">

View file

@ -0,0 +1,7 @@
<span class="notifications-bubble-element">
<span class="badge user-notification notification-animated"
ng-show="notificationService.notifications.length"
ng-class="notificationService.notificationClasses">
{{ notificationService.notifications.length }}<span ng-if="notificationService.additionalNotifications">+</span>
</span>
</span>

View file

@ -1155,7 +1155,8 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
'user': null, 'user': null,
'notifications': [], 'notifications': [],
'notificationClasses': [], 'notificationClasses': [],
'notificationSummaries': [] 'notificationSummaries': [],
'additionalNotifications': false
}; };
var pollTimerHandle = null; var pollTimerHandle = null;
@ -1251,7 +1252,9 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
'uuid': notification.id 'uuid': notification.id
}; };
ApiService.updateUserNotification(notification, params); ApiService.updateUserNotification(notification, params, function() {
notificationService.update();
}, ApiService.errorDisplay('Could not update notification'));
var index = $.inArray(notification, notificationService.notifications); var index = $.inArray(notification, notificationService.notifications);
if (index >= 0) { if (index >= 0) {
@ -1308,6 +1311,7 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
ApiService.listUserNotifications().then(function(resp) { ApiService.listUserNotifications().then(function(resp) {
notificationService.notifications = resp['notifications']; notificationService.notifications = resp['notifications'];
notificationService.additionalNotifications = resp['additional'];
notificationService.notificationClasses = notificationService.getClasses(notificationService.notifications); notificationService.notificationClasses = notificationService.getClasses(notificationService.notifications);
}); });
}; };
@ -5021,6 +5025,23 @@ quayApp.directive('twitterView', function () {
}); });
quayApp.directive('notificationsBubble', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/notifications-bubble.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
},
controller: function($scope, UserService, NotificationService) {
$scope.notificationService = NotificationService;
}
};
return directiveDefinitionObject;
});
quayApp.directive('notificationView', function () { quayApp.directive('notificationView', function () {
var directiveDefinitionObject = { var directiveDefinitionObject = {
priority: 0, priority: 0,