Fetch unread notifications number
Replace 20sec polling of the whole notification collection by polling of notification statistics (GET /api/notifications/stats) which includes number of unread notifications. This number is rendered in notifications badge in Fuel UI. Also, notifications are marked as read by a new handler PUT /api/notifications/change_status with {status: 'read'} payload. Partial-Bug: #1657348 Depends-On: I2e6a0daaf8712ab3064df728a8fb463ef805aa06 Change-Id: I6a7eae7abf2b43143039db7ca262ae40ce5a30b4
This commit is contained in:
parent
d5b2ee5330
commit
44fa844e61
|
@ -170,8 +170,8 @@ class App {
|
|||
this.version = new models.FuelVersion();
|
||||
this.fuelSettings = new models.FuelSettings();
|
||||
this.user = new models.User();
|
||||
this.statistics = new models.NodesStatistics();
|
||||
this.notifications = new models.Notifications();
|
||||
this.nodeStatistics = new models.NodeStatistics();
|
||||
this.notificationStatistics = new models.NotificationStatistics();
|
||||
this.releases = new models.Releases();
|
||||
this.keystoneClient = new KeystoneClient('/keystone');
|
||||
}
|
||||
|
@ -231,7 +231,9 @@ class App {
|
|||
var wrappedRootComponent = ReactDOM.render(
|
||||
React.createElement(
|
||||
RootComponent,
|
||||
_.pick(this, 'version', 'user', 'fuelSettings', 'statistics', 'notifications')
|
||||
_.pick(this,
|
||||
'version', 'user', 'fuelSettings', 'nodeStatistics', 'notificationStatistics'
|
||||
)
|
||||
),
|
||||
this.mountNode[0]
|
||||
);
|
||||
|
|
|
@ -568,8 +568,8 @@ models.Nodes = BaseCollection.extend({
|
|||
}
|
||||
});
|
||||
|
||||
models.NodesStatistics = BaseModel.extend({
|
||||
constructorName: 'NodesStatistics',
|
||||
models.NodeStatistics = BaseModel.extend({
|
||||
constructorName: 'NodeStatistics',
|
||||
urlRoot: '/api/nodes/allocation/stats'
|
||||
});
|
||||
|
||||
|
@ -757,6 +757,11 @@ models.Notifications = BaseCollection.extend({
|
|||
}
|
||||
});
|
||||
|
||||
models.NotificationStatistics = BaseModel.extend({
|
||||
constructorName: 'NotificationStatistics',
|
||||
urlRoot: '/api/notifications/stats'
|
||||
});
|
||||
|
||||
models.Settings = BaseModel
|
||||
.extend(deepModelMixin)
|
||||
.extend(cacheMixin)
|
||||
|
|
|
@ -1062,6 +1062,9 @@ input[type=range] {
|
|||
.font-semibold;
|
||||
font-size: @base-font-size;
|
||||
}
|
||||
.progress {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.user-popover {
|
||||
|
|
|
@ -21,8 +21,9 @@ import Backbone from 'backbone';
|
|||
import React from 'react';
|
||||
import utils from 'utils';
|
||||
import models from 'models';
|
||||
import dispatcher from 'dispatcher';
|
||||
import {backboneMixin, pollingMixin, dispatcherMixin} from 'component_mixins';
|
||||
import {Popover, Link} from 'views/controls';
|
||||
import {Popover, Link, ProgressBar} from 'views/controls';
|
||||
import {ChangePasswordDialog, ShowNodeInfoDialog} from 'views/dialogs';
|
||||
|
||||
export var Navbar = React.createClass({
|
||||
|
@ -31,8 +32,8 @@ export var Navbar = React.createClass({
|
|||
dispatcherMixin('updateNotifications', 'updateNotifications'),
|
||||
backboneMixin('user'),
|
||||
backboneMixin('version'),
|
||||
backboneMixin('statistics'),
|
||||
backboneMixin('notifications', 'update change:status'),
|
||||
backboneMixin('nodeStatistics'),
|
||||
backboneMixin('notificationStatistics'),
|
||||
pollingMixin(20)
|
||||
],
|
||||
togglePopover(popoverName) {
|
||||
|
@ -53,15 +54,15 @@ export var Navbar = React.createClass({
|
|||
},
|
||||
fetchData() {
|
||||
return Promise.all([
|
||||
this.props.statistics.fetch(),
|
||||
this.props.notifications.fetch({limit: this.props.notificationsDisplayCount})
|
||||
this.props.nodeStatistics.fetch(),
|
||||
this.props.notificationStatistics.fetch()
|
||||
]);
|
||||
},
|
||||
updateNodeStats() {
|
||||
return this.props.statistics.fetch();
|
||||
return this.props.nodeStatistics.fetch();
|
||||
},
|
||||
updateNotifications() {
|
||||
return this.props.notifications.fetch({limit: this.props.notificationsDisplayCount});
|
||||
return this.props.notificationStatistics.fetch();
|
||||
},
|
||||
componentDidMount() {
|
||||
this.props.user.on('change:authenticated', (model, value) => {
|
||||
|
@ -69,14 +70,13 @@ export var Navbar = React.createClass({
|
|||
this.startPolling();
|
||||
} else {
|
||||
this.stopPolling();
|
||||
this.props.statistics.clear();
|
||||
this.props.notifications.reset();
|
||||
this.props.nodeStatistics.clear();
|
||||
this.props.notificationStatistics.clear();
|
||||
}
|
||||
});
|
||||
},
|
||||
getDefaultProps() {
|
||||
return {
|
||||
notificationsDisplayCount: 5,
|
||||
elements: [
|
||||
{label: 'environments', url: '/clusters'},
|
||||
{label: 'equipment', url: '/equipment'},
|
||||
|
@ -87,15 +87,16 @@ export var Navbar = React.createClass({
|
|||
};
|
||||
},
|
||||
getInitialState() {
|
||||
return {};
|
||||
return {notifications: new models.Notifications()};
|
||||
},
|
||||
scrollToTop() {
|
||||
$('html, body').animate({scrollTop: 0}, 'fast');
|
||||
},
|
||||
render() {
|
||||
var unreadNotificationsCount = this.props.notifications.filter({status: 'unread'}).length;
|
||||
var authenticationEnabled = this.props.version.get('auth_required') &&
|
||||
this.props.user.get('authenticated');
|
||||
var {
|
||||
user, version, elements, activeElement, nodeStatistics, notificationStatistics
|
||||
} = this.props;
|
||||
var authenticationEnabled = version.get('auth_required') && user.get('authenticated');
|
||||
|
||||
return (
|
||||
<div className='navigation-box'>
|
||||
|
@ -107,11 +108,11 @@ export var Navbar = React.createClass({
|
|||
</div>
|
||||
<div className='col-xs-6'>
|
||||
<ul className='nav navbar-nav pull-left'>
|
||||
{_.map(this.props.elements, (element) => {
|
||||
{_.map(elements, (element) => {
|
||||
return (
|
||||
<li
|
||||
className={utils.classNames({
|
||||
active: this.props.activeElement === element.url.slice(1)
|
||||
active: activeElement === element.url.slice(1)
|
||||
})}
|
||||
key={element.label}
|
||||
>
|
||||
|
@ -138,16 +139,16 @@ export var Navbar = React.createClass({
|
|||
</li>
|
||||
<li
|
||||
key='statistics-icon'
|
||||
className={
|
||||
'statistics-icon ' +
|
||||
(this.props.statistics.get('unallocated') ? '' : 'no-unallocated')
|
||||
}
|
||||
className={utils.classNames({
|
||||
'statistics-icon': true,
|
||||
'no-unallocated': !nodeStatistics.get('unallocated')
|
||||
})}
|
||||
onClick={this.togglePopover('statistics')}
|
||||
>
|
||||
{!!this.props.statistics.get('unallocated') &&
|
||||
<div className='unallocated'>{this.props.statistics.get('unallocated')}</div>
|
||||
{!!nodeStatistics.get('unallocated') &&
|
||||
<div className='unallocated'>{nodeStatistics.get('unallocated')}</div>
|
||||
}
|
||||
<div className='total'>{this.props.statistics.get('total')}</div>
|
||||
<div className='total'>{nodeStatistics.get('total')}</div>
|
||||
</li>
|
||||
{authenticationEnabled &&
|
||||
<li
|
||||
|
@ -162,9 +163,12 @@ export var Navbar = React.createClass({
|
|||
onClick={this.togglePopover('notifications')}
|
||||
>
|
||||
<span
|
||||
className={utils.classNames({badge: true, visible: unreadNotificationsCount})}
|
||||
className={utils.classNames({
|
||||
badge: true,
|
||||
visible: !!notificationStatistics.get('unread')
|
||||
})}
|
||||
>
|
||||
{unreadNotificationsCount}
|
||||
{notificationStatistics.get('unread')}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
|
@ -177,22 +181,22 @@ export var Navbar = React.createClass({
|
|||
{this.state.statisticsPopoverVisible &&
|
||||
<StatisticsPopover
|
||||
key='statistics-popover'
|
||||
statistics={this.props.statistics}
|
||||
statistics={nodeStatistics}
|
||||
toggle={this.togglePopover('statistics')}
|
||||
/>
|
||||
}
|
||||
{this.state.userPopoverVisible &&
|
||||
<UserPopover
|
||||
key='user-popover'
|
||||
user={this.props.user}
|
||||
user={user}
|
||||
toggle={this.togglePopover('user')}
|
||||
/>
|
||||
}
|
||||
{this.state.notificationsPopoverVisible &&
|
||||
<NotificationsPopover
|
||||
notifications={this.state.notifications}
|
||||
notificationStatistics={notificationStatistics}
|
||||
key='notifications-popover'
|
||||
notifications={this.props.notifications}
|
||||
displayCount={this.props.notificationsDisplayCount}
|
||||
toggle={this.togglePopover('notifications')}
|
||||
/>
|
||||
}
|
||||
|
@ -293,7 +297,26 @@ var UserPopover = React.createClass({
|
|||
});
|
||||
|
||||
var NotificationsPopover = React.createClass({
|
||||
mixins: [backboneMixin('notifications')],
|
||||
mixins: [
|
||||
backboneMixin('notifications'),
|
||||
backboneMixin('notificationStatistics', 'change:total')
|
||||
],
|
||||
getDefaultProps() {
|
||||
return {visibleNotificationsNumber: 5};
|
||||
},
|
||||
componentWillMount() {
|
||||
this.updateNotifications()
|
||||
.then(() => this.setState({loading: false}));
|
||||
},
|
||||
componentWillReceiveProps() {
|
||||
this.updateNotifications();
|
||||
},
|
||||
updateNotifications() {
|
||||
var {notifications} = this.props;
|
||||
//FIXME(jkirnosova): need to fetch limited number of notifications
|
||||
//according to visibleNotificationsNumber prop
|
||||
return notifications.fetch().then(() => this.markAsRead());
|
||||
},
|
||||
showNodeInfo(id) {
|
||||
this.props.toggle(false);
|
||||
var node = new models.Node({id});
|
||||
|
@ -306,20 +329,19 @@ var NotificationsPopover = React.createClass({
|
|||
);
|
||||
if (notificationsToMark.length) {
|
||||
this.setState({unreadNotificationsIds: notificationsToMark.map('id')});
|
||||
notificationsToMark.toJSON = function() {
|
||||
return notificationsToMark.map((notification) => {
|
||||
notification.set({status: 'read'});
|
||||
return _.pick(notification.attributes, 'id', 'status');
|
||||
});
|
||||
};
|
||||
Backbone.sync('update', notificationsToMark);
|
||||
notificationsToMark.invokeMap('set', {status: 'read'});
|
||||
Backbone.sync('update', notificationsToMark, {
|
||||
url: _.result(notificationsToMark, 'url') + '/change_status',
|
||||
data: JSON.stringify({status: 'read'})
|
||||
})
|
||||
.then(() => dispatcher.trigger('updateNotifications'));
|
||||
}
|
||||
},
|
||||
componentDidMount() {
|
||||
this.markAsRead();
|
||||
},
|
||||
getInitialState() {
|
||||
return {unreadNotificationsIds: []};
|
||||
return {
|
||||
loading: true,
|
||||
unreadNotificationsIds: []
|
||||
};
|
||||
},
|
||||
renderNotification(notification) {
|
||||
var nodeId = notification.get('node_id');
|
||||
|
@ -352,14 +374,21 @@ var NotificationsPopover = React.createClass({
|
|||
);
|
||||
},
|
||||
render() {
|
||||
var {loading} = this.state;
|
||||
var {notifications, toggle, visibleNotificationsNumber} = this.props;
|
||||
var showMore = Backbone.history.getHash() !== 'notifications';
|
||||
var {notifications, displayCount, toggle} = this.props;
|
||||
return (
|
||||
<Popover toggle={toggle} className='notifications-popover'>
|
||||
{_.map(notifications.take(displayCount), this.renderNotification)}
|
||||
{showMore &&
|
||||
<div className='show-more'>
|
||||
<Link to='/notifications'>{i18n('notifications_popover.view_all_button')}</Link>
|
||||
{loading ?
|
||||
<ProgressBar />
|
||||
:
|
||||
<div>
|
||||
{notifications.take(visibleNotificationsNumber).map(this.renderNotification)}
|
||||
{showMore &&
|
||||
<div className='show-more'>
|
||||
<Link to='/notifications'>{i18n('notifications_popover.view_all_button')}</Link>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</Popover>
|
||||
|
|
|
@ -30,10 +30,8 @@ NotificationsPage = React.createClass({
|
|||
navbarActiveElement: null,
|
||||
breadcrumbsPath: [['home', '/'], 'notifications'],
|
||||
fetchData() {
|
||||
var notifications = app.notifications;
|
||||
return notifications.fetch().then(() =>
|
||||
({notifications: notifications})
|
||||
);
|
||||
var notifications = new models.Notifications();
|
||||
return notifications.fetch().then(() => ({notifications}));
|
||||
}
|
||||
},
|
||||
checkDateIsToday(date) {
|
||||
|
|
|
@ -75,7 +75,9 @@ var RootComponent = React.createClass({
|
|||
key='navbar'
|
||||
ref='navbar'
|
||||
activeElement={Page.navbarActiveElement}
|
||||
{... _.pick(this.props, 'version', 'user', 'statistics', 'notifications')}
|
||||
{... _.pick(this.props,
|
||||
'version', 'user', 'nodeStatistics', 'notificationStatistics'
|
||||
)}
|
||||
/>,
|
||||
<Breadcrumbs
|
||||
key='breadcrumbs'
|
||||
|
|
Loading…
Reference in New Issue