Merge lp:~allenap/maas/notifications-web into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5683
Proposed branch: lp:~allenap/maas/notifications-web
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 331 lines (+242/-3)
9 files modified
Makefile (+3/-3)
src/maasserver/static/js/angular/directives/notifications.js (+38/-0)
src/maasserver/static/js/angular/directives/tests/test_notifications.js (+98/-0)
src/maasserver/static/js/angular/factories/notifications.js (+34/-0)
src/maasserver/static/js/angular/factories/tests/test_notifications.js (+47/-0)
src/maasserver/static/partials/dashboard.html (+3/-0)
src/maasserver/static/partials/nodes-list.html (+2/-0)
src/maasserver/testing/sampledata.py (+15/-0)
src/maasserver/views/combo.py (+2/-0)
To merge this branch: bzr merge lp:~allenap/maas/notifications-web
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+315776@code.launchpad.net

Commit message

Rudimentary UI for notifications.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Most of the directive can be placed into a NotificationsManager that provides all the logic built-in.

review: Needs Fixing
Revision history for this message
Gavin Panella (allenap) wrote :

> Most of the directive can be placed into a NotificationsManager that
> provides all the logic built-in.

Ah, brilliant. I've done that. Can you have another quick look to make
sure I'm on the right track? Then I'll write some tests, and perhaps
make it prettier.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Close. The manager looks correct, but have another improvement to the directive that will make it even easier.

Revision history for this message
Gavin Panella (allenap) wrote :

I think I've got it now! I've also added tests. Please take another look.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Are you sure that the <maas-notifications /> directive is showing on all angular pages? We can do some simple work to make it work on non-angular pages as well.

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

> Are you sure that the <maas-notifications /> directive is showing on
> all angular pages? We can do some simple work to make it work on non-
> angular pages as well.

I've only put it on the dashboard and the node list page for now; I'll
put it everywhere in a follow-up. Is there a good place to get these
everywhere, both on AngularJS and non-AngularJS pages?

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2017-01-26 15:57:00 +0000
3+++ Makefile 2017-02-01 14:20:57 +0000
4@@ -566,13 +566,13 @@
5
6 services/database/@deps: bin/database
7
8-services/rackd/@deps: bin/twistd.rack
9+services/rackd/@deps: bin/twistd.rack bin/maas-rack
10
11 services/reloader/@deps:
12
13-services/regiond/@deps: bin/maas-region
14+services/regiond/@deps: bin/maas-region bin/maas-rack
15
16-services/regiond2/@deps: bin/maas-region
17+services/regiond2/@deps: bin/maas-region bin/maas-rack
18
19 #
20 # Package building
21
22=== added file 'src/maasserver/static/js/angular/directives/notifications.js'
23--- src/maasserver/static/js/angular/directives/notifications.js 1970-01-01 00:00:00 +0000
24+++ src/maasserver/static/js/angular/directives/notifications.js 2017-02-01 14:20:57 +0000
25@@ -0,0 +1,38 @@
26+/* Copyright 2017 Canonical Ltd. This software is licensed under the
27+ * GNU Affero General Public License version 3 (see the file LICENSE).
28+ *
29+ * Notifications.
30+ */
31+
32+angular.module('MAAS').run(['$templateCache', function ($templateCache) {
33+ // Inject notifications.html into the template cache.
34+ $templateCache.put('directive/templates/notifications.html', [
35+ '<div class="p-notification--error" ',
36+ 'ng-repeat="n in notifications">',
37+ '<p class="p-notification__response">',
38+ '<span class="p-notification__status"></span>',
39+ '<span>{$ n.message $}</span> — ',
40+ '<a ng-click="dismiss(n)">Dismiss</a>',
41+ '<br><small>(id: {$ n.id $}, ',
42+ 'ident: {$ n.ident || "-" $}, user: {$ n.user || "-" $}, ',
43+ 'users: {$ n.users $}, admins: {$ n.admins $}, ',
44+ 'created: {$ n.created $}, updated: {$ n.updated $})</small>',
45+ '</p>',
46+ '</div>'
47+ ].join(''));
48+}]);
49+
50+angular.module('MAAS').directive('maasNotifications', [
51+ "NotificationsManager", "ManagerHelperService",
52+ function(NotificationsManager, ManagerHelperService) {
53+ return {
54+ restrict: "E",
55+ templateUrl: 'directive/templates/notifications.html',
56+ link: function($scope, element, attrs) {
57+ ManagerHelperService.loadManager($scope, NotificationsManager);
58+ $scope.notifications = NotificationsManager.getItems();
59+ $scope.dismiss = angular.bind(
60+ NotificationsManager, NotificationsManager.dismiss);
61+ }
62+ };
63+ }]);
64
65=== added file 'src/maasserver/static/js/angular/directives/tests/test_notifications.js'
66--- src/maasserver/static/js/angular/directives/tests/test_notifications.js 1970-01-01 00:00:00 +0000
67+++ src/maasserver/static/js/angular/directives/tests/test_notifications.js 2017-02-01 14:20:57 +0000
68@@ -0,0 +1,98 @@
69+/* Copyright 2017 Canonical Ltd. This software is licensed under the
70+ * GNU Affero General Public License version 3 (see the file LICENSE).
71+ *
72+ * Unit tests for notifications directive.
73+ */
74+
75+describe("maasNotifications", function() {
76+
77+ // Load the MAAS module.
78+ beforeEach(module("MAAS"));
79+
80+ // Some example notifications as sent from the server.
81+ var exampleNotifications = [
82+ {
83+ "id": 1,
84+ "ident": null,
85+ "message": "Attention admins!",
86+ "user": null,
87+ "users": false,
88+ "admins": true,
89+ "created": "Fri, 27 Jan. 2017 12:19:52",
90+ "updated": "Fri, 27 Jan. 2017 12:19:52"
91+ },
92+ {
93+ "id": 2,
94+ "ident": null,
95+ "message": "Dear users, ...",
96+ "user": null,
97+ "users": true,
98+ "admins": false,
99+ "created": "Fri, 27 Jan. 2017 12:19:52",
100+ "updated": "Fri, 27 Jan. 2017 12:19:52"
101+ },
102+ {
103+ "id": 3,
104+ "ident": null,
105+ "message": "Greetings, Individual!",
106+ "user": 1,
107+ "users": false,
108+ "admins": false,
109+ "created": "Fri, 27 Jan. 2017 12:19:52",
110+ "updated": "Fri, 27 Jan. 2017 12:19:52"
111+ }
112+ ];
113+
114+ // Load the NotificationsManager and
115+ // create a new scope before each test.
116+ var theNotificationsManager;
117+ var $scope;
118+
119+ beforeEach(inject(function($rootScope, NotificationsManager) {
120+ theNotificationsManager = NotificationsManager;
121+ $scope = $rootScope.$new();
122+ }));
123+
124+ describe("maas-notifications", function() {
125+
126+ // Return the compiled directive.
127+ function compileDirective() {
128+ var directive;
129+ var html = '<maas-notifications />';
130+
131+ // Compile the directive.
132+ inject(function($compile) {
133+ directive = $compile(html)($scope);
134+ });
135+
136+ // Perform the digest cycle to finish the compile.
137+ $scope.$digest();
138+ return directive;
139+ }
140+
141+ it("renders notifications", function() {
142+ theNotificationsManager._items = exampleNotifications;
143+ var directive = compileDirective();
144+ // The directive renders an outer div for each notification.
145+ expect(directive.find("div").length).toBe(
146+ exampleNotifications.length, directive.html());
147+ // Messages are rendered in the nested tree.
148+ var messages = directive.find(
149+ "div > p > span:nth-child(2)").map(
150+ function() { return $(this).text(); }).get();
151+ expect(messages).toEqual(exampleNotifications.map(
152+ function(notification) { return notification.message; }));
153+ });
154+
155+ it("dismisses when dismiss link is clicked", function() {
156+ var notification = exampleNotifications[0];
157+ theNotificationsManager._items = [notification];
158+ var dismiss = spyOn(theNotificationsManager, "dismiss");
159+ var directive = compileDirective();
160+ directive.find("div > p > a").click();
161+ expect(dismiss).toHaveBeenCalledWith(notification);
162+ });
163+
164+ });
165+
166+});
167
168=== added file 'src/maasserver/static/js/angular/factories/notifications.js'
169--- src/maasserver/static/js/angular/factories/notifications.js 1970-01-01 00:00:00 +0000
170+++ src/maasserver/static/js/angular/factories/notifications.js 2017-02-01 14:20:57 +0000
171@@ -0,0 +1,34 @@
172+/* Copyright 2017 Canonical Ltd. This software is licensed under the
173+ * GNU Affero General Public License version 3 (see the file LICENSE).
174+ *
175+ * MAAS Notifications Manager.
176+ *
177+ * Manages notifications in the browser. Uses RegionConnection to load
178+ * notifications, await new, updated, and deleted notifications, and to
179+ * dismiss them.
180+ */
181+
182+angular.module('MAAS').factory(
183+ 'NotificationsManager',
184+ ['$q', '$rootScope', 'RegionConnection', 'Manager',
185+ function($q, $rootScope, RegionConnection, Manager) {
186+
187+ function NotificationsManager() {
188+ Manager.call(this);
189+
190+ this._pk = "id";
191+ this._handler = "notification";
192+
193+ // Listen for notify events for the notification object.
194+ RegionConnection.registerNotifier(
195+ "notification", angular.bind(this, this.onNotify));
196+ }
197+
198+ NotificationsManager.prototype = new Manager();
199+ NotificationsManager.prototype.dismiss = function(notification) {
200+ return RegionConnection.callMethod(
201+ "notification.dismiss", {id: notification.id});
202+ };
203+
204+ return new NotificationsManager();
205+ }]);
206
207=== added file 'src/maasserver/static/js/angular/factories/tests/test_notifications.js'
208--- src/maasserver/static/js/angular/factories/tests/test_notifications.js 1970-01-01 00:00:00 +0000
209+++ src/maasserver/static/js/angular/factories/tests/test_notifications.js 2017-02-01 14:20:57 +0000
210@@ -0,0 +1,47 @@
211+/* Copyright 2017 Canonical Ltd. This software is licensed under the
212+ * GNU Affero General Public License version 3 (see the file LICENSE).
213+ *
214+ * Unit tests for NotificationsManager.
215+ */
216+
217+describe("NotificationsManager", function() {
218+
219+ // Load the MAAS module.
220+ beforeEach(module("MAAS"));
221+
222+ // Load the NotificationsManager and RegionConnection.
223+ var NotificationsManager;
224+ var RegionConnection;
225+ beforeEach(inject(function($injector) {
226+ RegionConnection = $injector.get("RegionConnection");
227+ spyOn(RegionConnection, "registerNotifier");
228+ NotificationsManager = $injector.get("NotificationsManager");
229+ }));
230+
231+ // Make a random notification.
232+ function makeNotification(id, selected) {
233+ var notification = {
234+ name: makeName("name"),
235+ authoritative: true
236+ };
237+ if(angular.isDefined(id)) {
238+ notification.id = id;
239+ } else {
240+ notification.id = makeInteger(1, 100);
241+ }
242+ if(angular.isDefined(selected)) {
243+ notification.$selected = selected;
244+ }
245+ return notification;
246+ }
247+
248+ it("set requires attributes", function() {
249+ expect(NotificationsManager._pk).toBe("id");
250+ expect(NotificationsManager._handler).toBe("notification");
251+ });
252+
253+ it("listens for updates", function() {
254+ expect(RegionConnection.registerNotifier).toHaveBeenCalled();
255+ });
256+
257+});
258
259=== modified file 'src/maasserver/static/partials/dashboard.html'
260--- src/maasserver/static/partials/dashboard.html 2017-01-27 11:51:54 +0000
261+++ src/maasserver/static/partials/dashboard.html 2017-02-01 14:20:57 +0000
262@@ -32,6 +32,9 @@
263 </div>
264 </section>
265 </div>
266+
267+<maas-notifications />
268+
269 <div class="ng-hide" data-ng-show="loaded">
270 <header class="page-header" sticky media-query="min-width: 769px">
271 <div class="wrapper--inner">
272
273=== modified file 'src/maasserver/static/partials/nodes-list.html'
274--- src/maasserver/static/partials/nodes-list.html 2017-01-27 14:51:29 +0000
275+++ src/maasserver/static/partials/nodes-list.html 2017-02-01 14:20:57 +0000
276@@ -1,3 +1,5 @@
277+<maas-notifications />
278+
279 <header class="page-header" sticky media-query="min-width: 769px">
280 <div class="wrapper--inner">
281 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
282
283=== modified file 'src/maasserver/testing/sampledata.py'
284--- src/maasserver/testing/sampledata.py 2017-01-10 04:08:06 +0000
285+++ src/maasserver/testing/sampledata.py 2017-02-01 14:20:57 +0000
286@@ -23,6 +23,7 @@
287 Fabric,
288 Node,
289 RackController,
290+ User,
291 VersionedTextFile,
292 )
293 from maasserver.storage_layouts import STORAGE_LAYOUTS
294@@ -519,3 +520,17 @@
295 filename "test-boot";
296 server-name "boot.from.me";
297 """)), node=device)
298+
299+ # Add notifications for admins, users, and each individual user.
300+ factory.make_Notification(
301+ "Attention admins! Core critical! Meltdown imminent! Evacuate "
302+ "habitat immediately!", admins=True)
303+ factory.make_Notification(
304+ "Dear users, rumours of a core meltdown are unfounded. Please "
305+ "return to your home-pods and places of business.", users=True)
306+ for user in User.objects.all():
307+ context = {"name": user.username.capitalize()}
308+ factory.make_Notification(
309+ "Greetings, {name}! Get away from the habitat for the weekend and "
310+ "visit the Mare Nubium with MAAS Tours. Use the code METAL to "
311+ "claim a special gift!", user=user, context=context)
312
313=== modified file 'src/maasserver/views/combo.py'
314--- src/maasserver/views/combo.py 2016-12-14 11:07:08 +0000
315+++ src/maasserver/views/combo.py 2017-02-01 14:20:57 +0000
316@@ -81,6 +81,7 @@
317 "js/angular/factories/spaces.js",
318 "js/angular/factories/vlans.js",
319 "js/angular/factories/fabrics.js",
320+ "js/angular/factories/notifications.js",
321 "js/angular/services/search.js",
322 "js/angular/services/manager.js",
323 "js/angular/services/managerhelper.js",
324@@ -111,6 +112,7 @@
325 "js/angular/directives/placeholder.js",
326 "js/angular/directives/version_reloader.js",
327 "js/angular/directives/ipranges.js",
328+ "js/angular/directives/notifications.js",
329 "js/angular/filters/nodes.js",
330 "js/angular/filters/by_fabric.js",
331 "js/angular/filters/by_vlan.js",