Skip to content

Commit 72741ab

Browse files
committed
Quota Notifications
1 parent e41e0d5 commit 72741ab

File tree

8 files changed

+299
-215
lines changed

8 files changed

+299
-215
lines changed

app/scripts/constants.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,5 +540,10 @@ angular.extend(window.OPENSHIFT_CONSTANTS, {
540540
// href: 'http://example.com/',
541541
// tooltip: 'Open Dashboard'
542542
// }
543-
]
543+
],
544+
QUOTA_NOTIFICATION_MESSAGE: {
545+
// Example quota messages to show in notification drawer
546+
// "pods": "Upgrade to <a href='http://www.google.com'>OpenShift Pro</a> if you need additional resources.",
547+
// "limits.memory": "Upgrade to <a href='http://www.google.com'>OpenShift Online Pro</a> if you need additional resources."
548+
}
544549
});

app/scripts/controllers/overview.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,11 +1041,10 @@ function OverviewController($scope,
10411041
groupRecentBuildsByDeploymentConfig();
10421042
};
10431043

1044-
var updateQuotaWarnings = function() {
1045-
ResourceAlertsService.setGenericQuotaWarning(state.quotas,
1046-
state.clusterQuotas,
1047-
$routeParams.project,
1048-
state.alerts);
1044+
var setQuotaNotifications = function() {
1045+
ResourceAlertsService.setQuotaNotifications(state.quotas,
1046+
state.clusterQuotas,
1047+
$routeParams.project);
10491048
};
10501049

10511050
overview.clearFilter = function() {
@@ -1305,12 +1304,12 @@ function OverviewController($scope,
13051304
// Always poll quotas instead of watching, its not worth the overhead of maintaining websocket connections
13061305
watches.push(DataService.watch('resourcequotas', context, function(quotaData) {
13071306
state.quotas = quotaData.by("metadata.name");
1308-
updateQuotaWarnings();
1307+
setQuotaNotifications();
13091308
}, {poll: true, pollInterval: DEFAULT_POLL_INTERVAL}));
13101309

13111310
watches.push(DataService.watch('appliedclusterresourcequotas', context, function(clusterQuotaData) {
13121311
state.clusterQuotas = clusterQuotaData.by("metadata.name");
1313-
updateQuotaWarnings();
1312+
setQuotaNotifications();
13141313
}, {poll: true, pollInterval: DEFAULT_POLL_INTERVAL}));
13151314

13161315
var canI = $filter('canI');

app/scripts/directives/notifications/notificationDrawerWrapper.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
'Constants',
1818
'DataService',
1919
'EventsService',
20+
'NotificationsService',
2021
NotificationDrawerWrapper
2122
]
2223
});
@@ -171,7 +172,7 @@
171172
var id = notification.id || _.uniqueId('notification_') + Date.now();
172173
notificationsMap[project] = notificationsMap[project] || {};
173174
notificationsMap[project][id] = {
174-
actions: null,
175+
actions: notification.actions,
175176
unread: !EventsService.isRead(id),
176177
// using uid to match API events and have one filed to pass
177178
// to EventsService for read/cleared, etc
@@ -259,6 +260,26 @@
259260
onLinkClick: function(link) {
260261
link.onClick();
261262
drawer.drawerHidden = true;
263+
},
264+
handleAction: function(notification, action, index, group) {
265+
switch (action.name) {
266+
case 'View Quotas':
267+
$location.url("/project/" + $routeParams.project + "/quota");
268+
drawer.drawerHidden = true;
269+
break;
270+
case "Don't Show Me Again":
271+
NotificationsService.permanentlyHideNotification(notification.uid, notification.namespace);
272+
EventsService.markCleared(notification.uid);
273+
group.notifications.splice(index, 1);
274+
countUnreadNotifications();
275+
break;
276+
case 'Clear':
277+
EventsService.markCleared(notification.uid);
278+
group.notifications.splice(index, 1);
279+
countUnreadNotifications();
280+
break;
281+
282+
}
262283
}
263284
}
264285
});

app/scripts/services/quota.js

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ angular.module("openshiftConsole")
44
.factory("QuotaService", function(APIService,
55
$filter,
66
$q,
7+
Constants,
78
DataService,
89
Logger) {
910

1011
var isNil = $filter('isNil');
1112
var usageValue = $filter('usageValue');
13+
var usageWithUnits = $filter('usageWithUnits');
14+
1215
var isBestEffortPod = function(pod) {
1316
// To be best effort a pod must not have any containers that have non-zero requests or limits
1417
// Break out as soon as we find any pod with a non-zero request or limit
@@ -254,6 +257,84 @@ angular.module("openshiftConsole")
254257
});
255258
};
256259

260+
var OBJECT_COUNT_QUOTAS = [
261+
"pods",
262+
"replicationcontrollers",
263+
"resourcequotas",
264+
"services",
265+
"secrets",
266+
"configmaps",
267+
"persistentvolumeclaims",
268+
"openshift.io/imagestreams"
269+
];
270+
271+
var getNotificaitonMessage = function(used, usedValue, hard, hardValue, quotaKey) {
272+
var msgPrefix = "Your project is " + (hardValue < usedValue ? 'over' : 'at') + " quota. ";
273+
var msg;
274+
if (_.includes(OBJECT_COUNT_QUOTAS, quotaKey)) {
275+
msg = msgPrefix + "It is using " + usedValue + " of " + hardValue + " " + humanizeQuotaResource(quotaKey, true) + ".";
276+
} else {
277+
msg = msgPrefix + "It is using " + (usedValue / hardValue * 100) + "% of " + usageWithUnits(hard, quotaKey) + " " + humanizeQuotaResource(quotaKey, true) + ".";
278+
}
279+
280+
if (Constants.QUOTA_NOTIFICATION_MESSAGE && Constants.QUOTA_NOTIFICATION_MESSAGE[quotaKey]) {
281+
msg += " " + Constants.QUOTA_NOTIFICATION_MESSAGE[quotaKey];
282+
}
283+
284+
return msg;
285+
};
286+
287+
// Return notifications if you are at quota or over any quota for any resource. Do *not*
288+
// warn about quota for 'resourcequotas' or resources whose hard limit is
289+
// 0, however.
290+
var getQuotaNotifications = function(quotas, clusterQuotas, projectName) {
291+
var notifications = [];
292+
293+
var notificationsForQuota = function(quota) {
294+
var q = quota.status.total || quota.status;
295+
_.each(q.hard, function(hard, quotaKey) {
296+
var hardValue = usageValue(hard);
297+
var used = _.get(q, ['used', quotaKey]);
298+
var usedValue = usageValue(used);
299+
300+
// We always ignore quota warnings about being out of
301+
// resourcequotas since end users cant do anything about it
302+
if (quotaKey === 'resourcequotas' || !hardValue || !usedValue) {
303+
return;
304+
}
305+
306+
if(hardValue <= usedValue) {
307+
notifications.push({
308+
id: "quota-limit-reached-" + quotaKey,
309+
namespace: projectName,
310+
type: (hardValue < usedValue ? 'warning' : 'info'),
311+
message: getNotificaitonMessage(used, usedValue, hard, hardValue, quotaKey),
312+
skipToast: true,
313+
showInDrawer: true,
314+
actions: [
315+
{
316+
name: 'View Quotas',
317+
title: 'View project quotas'
318+
},
319+
{
320+
name: "Don't Show Me Again",
321+
title: 'Permenantly hide this notificaiton until quota limit changes'
322+
},
323+
{
324+
name: "Clear",
325+
title: 'Clear this notificaiton'
326+
}
327+
]
328+
});
329+
}
330+
});
331+
};
332+
_.each(quotas, notificationsForQuota);
333+
_.each(clusterQuotas, notificationsForQuota);
334+
335+
return notifications;
336+
};
337+
257338
// Warn if you are at quota or over any quota for any resource. Do *not*
258339
// warn about quota for 'resourcequotas' or resources whose hard limit is
259340
// 0, however.
@@ -324,6 +405,7 @@ angular.module("openshiftConsole")
324405
getLatestQuotaAlerts: getLatestQuotaAlerts,
325406
isAnyQuotaExceeded: isAnyQuotaExceeded,
326407
isAnyStorageQuotaExceeded: isAnyStorageQuotaExceeded,
327-
willRequestExceedQuota: willRequestExceedQuota
408+
willRequestExceedQuota: willRequestExceedQuota,
409+
getQuotaNotifications: getQuotaNotifications
328410
};
329411
});

app/scripts/services/resourceAlerts.js

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ angular.module("openshiftConsole")
66
AlertMessageService,
77
DeploymentsService,
88
Navigate,
9+
NotificationsService,
910
QuotaService) {
1011
var annotation = $filter('annotation');
1112
var humanizeKind = $filter('humanizeKind');
@@ -70,36 +71,14 @@ angular.module("openshiftConsole")
7071
return alerts;
7172
};
7273

73-
var setGenericQuotaWarning = function(quotas, clusterQuotas, projectName, alerts) {
74-
var isHidden = AlertMessageService.isAlertPermanentlyHidden("overview-quota-limit-reached", projectName);
75-
if (!isHidden && QuotaService.isAnyQuotaExceeded(quotas, clusterQuotas)) {
76-
if (alerts['quotaExceeded']) {
77-
// Don't recreate the alert or it will reset the temporary hidden state
78-
return;
74+
var setQuotaNotifications = function(quotas, clusterQuotas, projectName) {
75+
var notifications = QuotaService.getQuotaNotifications(quotas, clusterQuotas, projectName);
76+
_.each(notifications, function(notification) {
77+
// NotificationsService.restorePermanentlyHiddenNotification(notification.id, projectName);
78+
if(!NotificationsService.isNotificationPermanentlyHidden(notification)) {
79+
NotificationsService.addNotification(notification);
7980
}
80-
81-
alerts['quotaExceeded'] = {
82-
type: 'warning',
83-
message: 'Quota limit has been reached.',
84-
links: [{
85-
href: Navigate.quotaURL(projectName),
86-
label: "View Quota"
87-
},{
88-
href: "",
89-
label: "Don't Show Me Again",
90-
onClick: function() {
91-
// Hide the alert on future page loads.
92-
AlertMessageService.permanentlyHideAlert("overview-quota-limit-reached", projectName);
93-
94-
// Return true close the existing alert.
95-
return true;
96-
}
97-
}]
98-
};
99-
}
100-
else {
101-
delete alerts['quotaExceeded'];
102-
}
81+
});
10382
};
10483

10584
// deploymentConfig, k8s deployment
@@ -207,9 +186,9 @@ angular.module("openshiftConsole")
207186

208187
return {
209188
getPodAlerts: getPodAlerts,
210-
setGenericQuotaWarning: setGenericQuotaWarning,
211189
getDeploymentStatusAlerts: getDeploymentStatusAlerts,
212190
getPausedDeploymentAlerts: getPausedDeploymentAlerts,
213-
getServiceInstanceAlerts: getServiceInstanceAlerts
191+
getServiceInstanceAlerts: getServiceInstanceAlerts,
192+
setQuotaNotifications: setQuotaNotifications
214193
};
215194
});

app/views/directives/notifications/notification-body.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
ng-click="$ctrl.customScope.markRead(notification)">
55
<a
66
class="pull-right"
7+
ng-if="!notification.actions.length"
78
tabindex="0"
89
ng-click="$ctrl.customScope.clear(notification, $index, notificationGroup)">
910
<span class="sr-only">Clear notification</span>
@@ -35,7 +36,7 @@
3536
href=""
3637
class="secondary-action"
3738
title="{{action.title}}"
38-
ng-click="$ctrl.customScope.handleAction(notification, action)">
39+
ng-click="$ctrl.customScope.handleAction(notification, action, $parent.$parent.$index, notificationGroup)">
3940
{{action.name}}
4041
</a>
4142
</li>
@@ -71,7 +72,7 @@
7172
</span>
7273

7374
<span ng-if="!(notification.event.involvedObject)">
74-
{{notification.message}}
75+
<span ng-bind-html="notification.message"></span>
7576
<span ng-repeat="link in notification.links">
7677
<a
7778
ng-if="!link.href"

0 commit comments

Comments
 (0)