From 2d50d938112c0dde825dc95aa7311a8798af78ba Mon Sep 17 00:00:00 2001 From: Alexander Kislitsky Date: Thu, 26 Jan 2017 17:25:33 +0300 Subject: [PATCH] Handler for counting notifications statuses added For calculation of notifications statuses we made requests in the UI and fetch all notifications data and process them on the UI side. We want to replace a polling of the whole notification collection by a polling of unread notifications number. This dramatically decrease Fuel UI load in case of a big amount of notifications. Change-Id: I8f83d4e2d7f58beaf06c489b2264ccb69f9927ce Partial-Bug: #1657348 (cherry picked from commit d4bf85957e13abecc519c84f43122e55df510eec) --- .../nailgun/api/v1/handlers/notifications.py | 30 ++++++++++++++ nailgun/nailgun/api/v1/urls.py | 4 ++ nailgun/nailgun/objects/notification.py | 28 ++++++++++--- .../test/unit/test_notification_handler.py | 39 +++++++++++++++++++ 4 files changed, 96 insertions(+), 5 deletions(-) diff --git a/nailgun/nailgun/api/v1/handlers/notifications.py b/nailgun/nailgun/api/v1/handlers/notifications.py index 6113c8fd89..c516b4ea61 100644 --- a/nailgun/nailgun/api/v1/handlers/notifications.py +++ b/nailgun/nailgun/api/v1/handlers/notifications.py @@ -59,3 +59,33 @@ class NotificationCollectionHandler(CollectionHandler): self.collection.single.update(notif, nd) notifications_updated.append(notif) return self.collection.to_list(notifications_updated) + + +class NotificationCollectionStatsHandler(CollectionHandler): + + collection = objects.NotificationCollection + validator = NotificationValidator + + @handle_errors + @validate + @serialize + def GET(self): + """Calculates notifications statuses + + Counts all presented notifications in the DB and returns dict + with structure {'total': count, 'unread': count, ...} + + :returns: dict with notifications statuses count + + :http: * 200 (OK) + """ + return self.collection.single.get_statuses_with_count() + + @handle_errors + @validate + def POST(self): + """Update notification statuses is not allowed + + :http: * 405 (Method not allowed) + """ + raise self.http(405) diff --git a/nailgun/nailgun/api/v1/urls.py b/nailgun/nailgun/api/v1/urls.py index d4249db6f9..78dbeba013 100644 --- a/nailgun/nailgun/api/v1/urls.py +++ b/nailgun/nailgun/api/v1/urls.py @@ -86,6 +86,8 @@ from nailgun.api.v1.handlers.plugin_link import PluginLinkCollectionHandler from nailgun.api.v1.handlers.plugin_link import PluginLinkHandler from nailgun.api.v1.handlers.notifications import NotificationCollectionHandler +from nailgun.api.v1.handlers.notifications import \ + NotificationCollectionStatsHandler from nailgun.api.v1.handlers.notifications import NotificationHandler from nailgun.api.v1.handlers.orchestrator import DefaultDeploymentInfo @@ -336,6 +338,8 @@ urls = ( NotificationCollectionHandler, r'/notifications/(?P\d+)/?$', NotificationHandler, + r'/notifications/stats/?$', + NotificationCollectionStatsHandler, r'/dump/(?P[A-Za-z0-9-_.]+)$', SnapshotDownloadHandler, diff --git a/nailgun/nailgun/objects/notification.py b/nailgun/nailgun/objects/notification.py index 94d711f362..e7d2bb83e2 100644 --- a/nailgun/nailgun/objects/notification.py +++ b/nailgun/nailgun/objects/notification.py @@ -16,17 +16,17 @@ from datetime import datetime -from nailgun.db.sqlalchemy import models +from sqlalchemy import func +from nailgun import consts +from nailgun.db import db +from nailgun.db.sqlalchemy import models from nailgun import errors from nailgun.logger import logger - from nailgun.objects import NailgunCollection from nailgun.objects import NailgunObject - -from nailgun.objects import Task - from nailgun.objects.serializers.notification import NotificationSerializer +from nailgun.objects import Task class Notification(NailgunObject): @@ -87,6 +87,24 @@ class Notification(NailgunObject): notif_dict['date'] = instance.datetime.strftime('%d-%m-%Y') return notif_dict + @classmethod + def get_statuses_with_count(cls): + """Counts notifications statuses in DB + + Calculates number of notifications and adds total count to the + result. All notifications statuses described in the + consts.NOTIFICATION_STATUSES will be present in the result. + + :return: dict with structure {'total': count, 'unread': count, ...} + """ + result = dict.fromkeys(consts.NOTIFICATION_STATUSES, 0) + query = db().query(cls.model.status, func.count(cls.model.status)).\ + group_by(cls.model.status) + for status, num in query.all(): + result[status] = num + result['total'] = sum(result.values()) + return result + class NotificationCollection(NailgunCollection): diff --git a/nailgun/nailgun/test/unit/test_notification_handler.py b/nailgun/nailgun/test/unit/test_notification_handler.py index c82100d0e2..69fd57ee3a 100644 --- a/nailgun/nailgun/test/unit/test_notification_handler.py +++ b/nailgun/nailgun/test/unit/test_notification_handler.py @@ -101,3 +101,42 @@ class TestHandlers(BaseIntegrationTest): expect_errors=True ) self.assertEqual(404, resp.status_code) + + def test_get_notification_status(self): + resp = self.app.get( + reverse( + 'NotificationCollectionStatsHandler', + ), + headers=self.default_headers + ) + self.assertEqual({'total': 0, 'read': 0, 'unread': 0}, resp.json_body) + self.assertEqual(200, resp.status_code) + + self.env.create_notification() + resp = self.app.get( + reverse( + 'NotificationCollectionStatsHandler', + ), + headers=self.default_headers + ) + self.assertEqual({'total': 1, 'read': 0, 'unread': 1}, resp.json_body) + + self.env.create_notification(status='read') + self.env.create_notification(status='read') + resp = self.app.get( + reverse( + 'NotificationCollectionStatsHandler', + ), + headers=self.default_headers + ) + self.assertEqual({'total': 3, 'read': 2, 'unread': 1}, resp.json_body) + + def test_notification_statuses_post_not_allowed(self): + resp = self.app.post( + reverse( + 'NotificationCollectionStatsHandler', + ), + headers=self.default_headers, + expect_errors=True + ) + self.assertEqual(405, resp.status_code)