Skip to content

Commit 8316f01

Browse files
Initial stab @ integrating internal notifications with events in the notification drawer
1 parent 5709d8a commit 8316f01

File tree

6 files changed

+591
-635
lines changed

6 files changed

+591
-635
lines changed

app/scripts/directives/notifications/notificationDrawerWrapper.js

+115-151
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,26 @@
4545
// this one is treated separately from the rootScopeWatches as
4646
// it may need to be updated outside of the lifecycle of init/destroy
4747
var notificationListener;
48-
// our internal notifications
49-
// var clientGeneratedNotifications = [];
50-
5148
var eventsWatcher;
52-
var eventsByNameData = {};
53-
var eventsMap = {};
54-
55-
// TODO:
56-
// include both Notifications & Events,
57-
// rather than destroying the map each time maintain it & add new items
49+
// data
50+
var eventsMap = {
51+
// projName: { events }
52+
};
53+
var notificationsMap = {
54+
// projName: { notifications }
55+
};
5856

59-
// final Processed set of notification groups for UI
60-
// IF POSSIBLE, avoid having to convert back to an array.
61-
// var notificationGroupsMap = {};
62-
var notificationGroups = [];
57+
var projects = {};
6358

59+
var hideIfNoProject = function(projectName) {
60+
if(!projectName) {
61+
drawer.drawerHidden = true;
62+
}
63+
};
6464

65-
var projects = {};
65+
var projectChanged = function(next, current) {
66+
return _.get(next, 'params.project') !== _.get(current, 'params.project');
67+
};
6668

6769
var getProject = function(projectName) {
6870
return DataService
@@ -73,98 +75,67 @@
7375
});
7476
};
7577

76-
var ensureProjectGroupExists = function(groups, projectName) {
77-
if(projectName && !groups[projectName]) {
78-
groups[projectName] = {
79-
heading: $filter('displayName')(projects[projectName]) || projectName,
80-
project: projects[projectName],
81-
notifications: []
82-
};
83-
}
84-
};
85-
86-
var deregisterEventsWatch = function() {
87-
if(eventsWatcher) {
88-
DataService.unwatch(eventsWatcher);
89-
}
90-
};
91-
92-
var watchEvents = function(projectName, cb) {
93-
deregisterEventsWatch();
94-
if(projectName) {
95-
eventsWatcher = DataService.watch('events', {namespace: projectName}, _.debounce(cb, 400), { skipDigest: true });
96-
}
97-
};
98-
99-
// NotificationService notifications are minimal, they do no necessarily contain projectName info.
100-
// ATM tacking this on via watching the current project.
101-
// var watchNotifications = function(projectName, cb) {
102-
// deregisterNotificationListener();
103-
// if(!projectName) {
104-
// return;
105-
// }
106-
// notificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', cb);
107-
// };
108-
109-
var deregisterNotificationListener = function() {
110-
notificationListener && notificationListener();
111-
notificationListener = null;
78+
var makeProjectGroup = function(projectName, notifications) {
79+
return {
80+
heading: $filter('displayName')(projects[projectName]),
81+
project: projects[projectName],
82+
notifications: notifications
83+
};
11284
};
11385

11486
var unread = function(notifications) {
11587
return _.filter(notifications, 'unread');
11688
};
11789

118-
// returns a count for each type of notification, example:
119-
// {Normal: 1, Warning: 5}
120-
// TODO: eliminate this $rootScope.$applyAsync,
121-
// there is a quirk here where the values are not picked up the
122-
// first time the function runs, despite the same $applyAsync
123-
// in the render() function
124-
var countUnreadNotificationsForGroup = function(group) {
125-
$rootScope.$applyAsync(function() {
90+
91+
// currently we only show 1 at a time anyway
92+
var countUnreadNotifications = function() {
93+
_.each(drawer.notificationGroups, function(group) {
12694
group.totalUnread = unread(group.notifications).length;
12795
group.hasUnread = !!group.totalUnread;
12896
$rootScope.$emit('NotificationDrawerWrapper.count', group.totalUnread);
12997
});
13098
};
13199

132-
// currently we only show 1 at a time anyway
133-
var countUnreadNotificationsForAllGroups = function() {
134-
_.each(notificationGroups, countUnreadNotificationsForGroup);
100+
// returns a map of filtered events ONLY.
101+
// will worry about unread, actions, etc in render.
102+
var formatAndFilterAPIEvents = function(events) {
103+
return _.reduce(events, function(result, event) {
104+
if(EventsService.isImportantAPIEvent(event) && !EventsService.isCleared(event.id)) {
105+
result[event.metadata.uid] = {
106+
actions: null,
107+
uid: event.metadata.uid,
108+
unread: !EventsService.isRead(event.uid),
109+
type: event.type,
110+
timestamp: event.lastTimestamp,
111+
event: event
112+
};
113+
}
114+
return result;
115+
}, {});
135116
};
136117

137-
var sortNotifications = function(notifications) {
138-
return _.orderBy(notifications, ['event.lastTimestamp', 'event.firstTimestamp'], ['desc', 'desc']);
118+
// we have to keep notifications & events separate as
119+
// notifications are ephemerial, but events have a time to live
120+
// set by the server. we can merge them right before we update
121+
// the UI.
122+
var mergeMaps = function(firstMap, secondMap) {
123+
var proj = $routeParams.project;
124+
return _.assign({}, firstMap[proj], secondMap[proj]);
139125
};
140126

141-
var sortNotificationGroups = function(groupsMap) {
142-
// convert the map into a sorted array
143-
var sortedGroups = _.sortBy(groupsMap, function(group) {
144-
return group.heading;
145-
});
146-
// and sort the notifications under each one
147-
_.each(sortedGroups, function(group) {
148-
group.notifications = sortNotifications(group.notifications);
149-
group.counts = countUnreadNotificationsForGroup(group);
150-
});
151-
return sortedGroups;
127+
//
128+
var sortMap = function(map) {
129+
return _.orderBy(map, ['event.lastTimestamp', 'event.firstTimestamp'], ['desc', 'desc']);
152130
};
153131

154-
var formatAndFilterEvents = function(eventMap) {
155-
var filtered = {};
156-
ensureProjectGroupExists(filtered, $routeParams.project);
157-
_.each(eventMap, function(event) {
158-
if(EventsService.isImportantEvent(event) && !EventsService.isCleared(event)) {
159-
ensureProjectGroupExists(filtered, event.metadata.namespace);
160-
filtered[event.metadata.namespace].notifications.push({
161-
unread: !EventsService.isRead(event),
162-
event: event,
163-
actions: null
164-
});
165-
}
132+
var render = function() {
133+
$rootScope.$evalAsync(function() {
134+
drawer.notificationGroups = [
135+
makeProjectGroup($routeParams.project, sortMap( mergeMaps(eventsMap, notificationsMap )))
136+
];
137+
countUnreadNotifications();
166138
});
167-
return filtered;
168139
};
169140

170141
var deregisterRootScopeWatches = function() {
@@ -174,51 +145,61 @@
174145
rootScopeWatches = [];
175146
};
176147

177-
var hideIfNoProject = function(projectName) {
178-
if(!projectName) {
179-
drawer.drawerHidden = true;
148+
var deregisterEventsWatch = function() {
149+
if(eventsWatcher) {
150+
DataService.unwatch(eventsWatcher);
151+
eventsWatcher = null;
180152
}
181153
};
182154

183-
var render = function() {
184-
$rootScope.$evalAsync(function () {
185-
countUnreadNotificationsForAllGroups();
186-
// NOTE: we are currently only showing one project in the drawer at a
187-
// time. If we go back to multiple projects, we can eliminate the filter here
188-
// and just pass the whole array as notificationGroups.
189-
// if we do, we will have to handle group.open to keep track of what the
190-
// user is viewing at the time & indicate to the user that the non-active
191-
// project is "asleep"/not being watched.
192-
drawer.notificationGroups = _.filter(notificationGroups, function(group) {
193-
return group.project.metadata.name === $routeParams.project;
194-
});
195-
});
155+
var deregisterNotificationListener = function() {
156+
notificationListener && notificationListener();
157+
notificationListener = null;
196158
};
197159

198-
// TODO: follow-on PR to decide which of these events to toast,
199-
// via config in constants.js
200160
var eventWatchCallback = function(eventData) {
201-
eventsByNameData = eventData.by('metadata.name');
202-
eventsMap = formatAndFilterEvents(eventsByNameData);
203-
// TODO: Update to an intermediate map, so that we can then combine both
204-
// events + notifications into the final notificationGroups output
205-
notificationGroups = sortNotificationGroups(eventsMap);
161+
eventsMap[$routeParams.project] = formatAndFilterAPIEvents(eventData.by('metadata.name'));
162+
render();
163+
};
164+
165+
var notificationWatchCallback = function(event, notification) {
166+
var project = notification.namespace || $routeParams.project;
167+
notificationsMap[project] = notificationsMap[project] || {};
168+
169+
notificationsMap[project][notification.id] = {
170+
actions: null,
171+
unread: !EventsService.isRead(notification.id),
172+
// using uid to match API events and have one filed to pass
173+
// to EventsService for read/cleared, etc
174+
uid: notification.id,
175+
type: notification.type,
176+
timestamp: notification.timestamp,
177+
message: notification.message,
178+
namespace: project,
179+
links: notification.links
180+
};
206181
render();
207182
};
208183

209-
// TODO: Follow-on PR to update & add the internal notifications to the
210-
// var notificationWatchCallback = function(event, notification) {
211-
// // will need to add .event = {} and immitate structure
212-
// if(!notification.lastTimestamp) {
213-
// // creates a timestamp that matches event format: 2017-08-09T19:55:35Z
214-
// notification.lastTimestamp = moment.parseZone(new Date()).utc().format();
215-
// }
216-
// clientGeneratedNotifications.push(notification);
217-
// };
218-
219-
var iconClassByEventSeverity = {
220-
Normal: 'pficon pficon-info',
221-
Warning: 'pficon pficon-warning-triangle-o'
184+
var watchEvents = function(projectName, cb) {
185+
deregisterEventsWatch();
186+
if(projectName) {
187+
eventsWatcher = DataService.watch('events', {namespace: projectName}, _.debounce(cb, 400), { skipDigest: true });
188+
}
189+
};
190+
191+
var watchNotifications = _.once(function(projectName, cb) {
192+
deregisterNotificationListener();
193+
notificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', cb);
194+
});
195+
196+
var reset = function() {
197+
getProject($routeParams.project).then(function() {
198+
watchEvents($routeParams.project, eventWatchCallback);
199+
watchNotifications($routeParams.project, notificationWatchCallback);
200+
hideIfNoProject($routeParams.project);
201+
render();
202+
});
222203
};
223204

224205
angular.extend(drawer, {
@@ -235,58 +216,42 @@
235216
onMarkAllRead: function(group) {
236217
_.each(group.notifications, function(notification) {
237218
notification.unread = false;
238-
EventsService.markRead(notification.event);
219+
EventsService.markRead(notification.uid);
239220
});
240221
render();
241222
$rootScope.$emit('NotificationDrawerWrapper.onMarkAllRead');
242223
},
243224
onClearAll: function(group) {
244225
_.each(group.notifications, function(notification) {
245-
EventsService.markRead(notification.event);
246-
EventsService.markCleared(notification.event);
226+
notification.unread = false;
227+
EventsService.markRead(notification.uid);
228+
EventsService.markCleared(notification.uid);
247229
});
248-
group.notifications = [];
230+
eventsMap[$routeParams.project] = {};
231+
notificationsMap[$routeParams.project] = {};
249232
render();
250233
$rootScope.$emit('NotificationDrawerWrapper.onMarkAllRead');
251234
},
252-
notificationGroups: notificationGroups,
235+
notificationGroups: [],
253236
headingInclude: 'views/directives/notifications/header.html',
254237
notificationBodyInclude: 'views/directives/notifications/notification-body.html',
255238
customScope: {
256239
clear: function(notification, index, group) {
257-
EventsService.markCleared(notification.event);
240+
EventsService.markCleared(notification.uid);
258241
group.notifications.splice(index, 1);
259-
countUnreadNotificationsForAllGroups();
242+
countUnreadNotifications();
260243
},
261244
markRead: function(notification) {
262245
notification.unread = false;
263-
EventsService.markRead(notification.event);
264-
countUnreadNotificationsForAllGroups();
265-
},
266-
getNotficationStatusIconClass: function(event) {
267-
return iconClassByEventSeverity[event.type] || iconClassByEventSeverity.info;
268-
},
269-
getStatusForCount: function(countKey) {
270-
return iconClassByEventSeverity[countKey] || iconClassByEventSeverity.info;
246+
EventsService.markRead(notification.uid);
247+
countUnreadNotifications();
271248
},
272249
close: function() {
273250
drawer.drawerHidden = true;
274251
}
275252
}
276253
});
277254

278-
var projectChanged = function(next, current) {
279-
return _.get(next, 'params.project') !== _.get(current, 'params.project');
280-
};
281-
282-
var reset = function() {
283-
getProject($routeParams.project).then(function() {
284-
watchEvents($routeParams.project, eventWatchCallback);
285-
//watchNotifications($routeParams.project, notificationWatchCallback);
286-
hideIfNoProject($routeParams.project);
287-
render();
288-
});
289-
};
290255

291256
var initWatches = function() {
292257
if($routeParams.project) {
@@ -322,7 +287,6 @@
322287
deregisterEventsWatch();
323288
deregisterRootScopeWatches();
324289
};
325-
326290
}
327291

328292
})();

0 commit comments

Comments
 (0)