From e9d4aa951dc7f0421e42105c404ae687ff1c45cc Mon Sep 17 00:00:00 2001 From: Julia Aranovich Date: Mon, 30 Jan 2017 12:53:09 +0300 Subject: [PATCH] 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 --- static/app.js | 8 +- static/models.js | 9 ++- static/styles/main.less | 3 + static/views/layout.js | 121 ++++++++++++++++++----------- static/views/notifications_page.js | 6 +- static/views/root.js | 4 +- 6 files changed, 95 insertions(+), 56 deletions(-) diff --git a/static/app.js b/static/app.js index 340b06d6e..85f257b02 100644 --- a/static/app.js +++ b/static/app.js @@ -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] ); diff --git a/static/models.js b/static/models.js index 1e473deae..d0a92af14 100644 --- a/static/models.js +++ b/static/models.js @@ -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) diff --git a/static/styles/main.less b/static/styles/main.less index 4cf8f3134..90f3a39eb 100644 --- a/static/styles/main.less +++ b/static/styles/main.less @@ -1062,6 +1062,9 @@ input[type=range] { .font-semibold; font-size: @base-font-size; } + .progress { + margin-bottom: 0; + } } &.user-popover { diff --git a/static/views/layout.js b/static/views/layout.js index 7d5c7426e..b60aff17a 100644 --- a/static/views/layout.js +++ b/static/views/layout.js @@ -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 (
@@ -107,11 +108,11 @@ export var Navbar = React.createClass({