Commit 48da780b by Samuel Padgett

Combine connection error messages

When multiple connection errors happen close together, show them in one
toast notification to avoid spamming the user.
parent 176e824b
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
color: @gray-light; color: @gray-light;
} }
.toast-notification-details .truncated-content {
white-space: pre-line;
}
.toast-notification-message { .toast-notification-message {
font-weight: 700; font-weight: 700;
margin-right: 5px; margin-right: 5px;
......
...@@ -1211,6 +1211,46 @@ angular.module('openshiftCommonServices') ...@@ -1211,6 +1211,46 @@ angular.module('openshiftCommonServices')
} }
} }
// If several connection errors happen close together, display them as one
// notification. This prevents us spamming the user with many failed requests
// at once.
var queuedErrors = [];
var addQueuedNotifications = _.debounce(function() {
if (!queuedErrors.length) {
return;
}
// Show all queued messages together. If the details is extremely long, it
// will be truncated with a see more link.
var notification = {
type: 'error',
message: 'An error occurred connecting to the server.',
details: queuedErrors.join('\n'),
links: [{
label: 'Refresh',
onClick: function() {
window.location.reload();
}
}]
};
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', notification);
// Clear the queue.
queuedErrors = [];
}, 300, { maxWait: 1000 });
var showRequestError = function(message, status) {
if (status) {
message += " (status " + status + ")";
}
// Queue the message and call debounced `addQueuedNotifications`.
queuedErrors.push(message);
addQueuedNotifications();
};
function DataService() { function DataService() {
this._listDeferredMap = {}; this._listDeferredMap = {};
this._watchCallbacksMap = {}; this._watchCallbacksMap = {};
...@@ -1547,16 +1587,7 @@ angular.module('openshiftCommonServices') ...@@ -1547,16 +1587,7 @@ angular.module('openshiftCommonServices')
}) })
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
if (opts.errorNotification !== false) { if (opts.errorNotification !== false) {
var msg = "Failed to get " + resource + "/" + name; showRequestError("Failed to get " + resource + "/" + name, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
} }
deferred.reject({ deferred.reject({
data: data, data: data,
...@@ -2092,16 +2123,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2092,16 +2123,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
return; return;
} }
var msg = "Failed to list " + resource; showRequestError("Failed to list " + resource, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
}); });
}); });
} }
...@@ -2123,16 +2145,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -2123,16 +2145,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
return; return;
} }
var msg = "Failed to list " + resource; showRequestError("Failed to list " + resource, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
}); });
} }
}; };
......
...@@ -405,7 +405,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -405,7 +405,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
" <span class=\"{{notification.type | alertIcon}}\" aria-hidden=\"true\"></span>\n" + " <span class=\"{{notification.type | alertIcon}}\" aria-hidden=\"true\"></span>\n" +
" <span class=\"sr-only\">{{notification.type}}</span>\n" + " <span class=\"sr-only\">{{notification.type}}</span>\n" +
" <span class=\"toast-notification-message\" ng-if=\"notification.message\">{{notification.message}}</span>\n" + " <span class=\"toast-notification-message\" ng-if=\"notification.message\">{{notification.message}}</span>\n" +
" <span ng-if=\"notification.details\">\n" + " <div ng-if=\"notification.details\" class=\"toast-notification-details\">\n" +
" <truncate-long-text\n" + " <truncate-long-text\n" +
" limit=\"200\"\n" + " limit=\"200\"\n" +
" content=\"notification.details\"\n" + " content=\"notification.details\"\n" +
...@@ -413,7 +413,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -413,7 +413,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
" expandable=\"true\"\n" + " expandable=\"true\"\n" +
" hide-collapse=\"true\">\n" + " hide-collapse=\"true\">\n" +
" </truncate-long-text>\n" + " </truncate-long-text>\n" +
" </span>\n" + " </div>\n" +
" <span ng-repeat=\"link in notification.links\">\n" + " <span ng-repeat=\"link in notification.links\">\n" +
" <a ng-if=\"!link.href\" href=\"\" ng-click=\"onClick(notification, link)\" role=\"button\">{{link.label}}</a>\n" + " <a ng-if=\"!link.href\" href=\"\" ng-click=\"onClick(notification, link)\" role=\"button\">{{link.label}}</a>\n" +
" <a ng-if=\"link.href\" ng-href=\"{{link.href}}\" ng-attr-target=\"{{link.target}}\">{{link.label}}</a>\n" + " <a ng-if=\"link.href\" ng-href=\"{{link.href}}\" ng-attr-target=\"{{link.target}}\">{{link.label}}</a>\n" +
...@@ -1081,6 +1081,7 @@ angular.module('openshiftCommonUI') ...@@ -1081,6 +1081,7 @@ angular.module('openshiftCommonUI')
// Listen for updates from NotificationsService to show a notification. // Listen for updates from NotificationsService to show a notification.
var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) { var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
$scope.$evalAsync(function() {
$scope.notifications.push(notification); $scope.notifications.push(notification);
if (NotificationsService.isAutoDismiss(notification)) { if (NotificationsService.isAutoDismiss(notification)) {
$timeout(function () { $timeout(function () {
...@@ -1092,6 +1093,7 @@ angular.module('openshiftCommonUI') ...@@ -1092,6 +1093,7 @@ angular.module('openshiftCommonUI')
// so that the array doesn't grow indefinitely. // so that the array doesn't grow indefinitely.
pruneRemovedNotifications(); pruneRemovedNotifications();
}); });
});
$scope.$on('$destroy', function() { $scope.$on('$destroy', function() {
if (deregisterNotificationListener) { if (deregisterNotificationListener) {
......
...@@ -267,6 +267,9 @@ div.hopscotch-bubble .hopscotch-nav-button.prev { ...@@ -267,6 +267,9 @@ div.hopscotch-bubble .hopscotch-nav-button.prev {
.toast-action-divider { .toast-action-divider {
color: #9c9c9c; color: #9c9c9c;
} }
.toast-notification-details .truncated-content {
white-space: pre-line;
}
.toast-notification-message { .toast-notification-message {
font-weight: 700; font-weight: 700;
margin-right: 5px; margin-right: 5px;
......
...@@ -576,7 +576,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -576,7 +576,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
" <span class=\"{{notification.type | alertIcon}}\" aria-hidden=\"true\"></span>\n" + " <span class=\"{{notification.type | alertIcon}}\" aria-hidden=\"true\"></span>\n" +
" <span class=\"sr-only\">{{notification.type}}</span>\n" + " <span class=\"sr-only\">{{notification.type}}</span>\n" +
" <span class=\"toast-notification-message\" ng-if=\"notification.message\">{{notification.message}}</span>\n" + " <span class=\"toast-notification-message\" ng-if=\"notification.message\">{{notification.message}}</span>\n" +
" <span ng-if=\"notification.details\">\n" + " <div ng-if=\"notification.details\" class=\"toast-notification-details\">\n" +
" <truncate-long-text\n" + " <truncate-long-text\n" +
" limit=\"200\"\n" + " limit=\"200\"\n" +
" content=\"notification.details\"\n" + " content=\"notification.details\"\n" +
...@@ -584,7 +584,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI'); ...@@ -584,7 +584,7 @@ hawtioPluginLoader.addModule('openshiftCommonUI');
" expandable=\"true\"\n" + " expandable=\"true\"\n" +
" hide-collapse=\"true\">\n" + " hide-collapse=\"true\">\n" +
" </truncate-long-text>\n" + " </truncate-long-text>\n" +
" </span>\n" + " </div>\n" +
" <span ng-repeat=\"link in notification.links\">\n" + " <span ng-repeat=\"link in notification.links\">\n" +
" <a ng-if=\"!link.href\" href=\"\" ng-click=\"onClick(notification, link)\" role=\"button\">{{link.label}}</a>\n" + " <a ng-if=\"!link.href\" href=\"\" ng-click=\"onClick(notification, link)\" role=\"button\">{{link.label}}</a>\n" +
" <a ng-if=\"link.href\" ng-href=\"{{link.href}}\" ng-attr-target=\"{{link.target}}\">{{link.label}}</a>\n" + " <a ng-if=\"link.href\" ng-href=\"{{link.href}}\" ng-attr-target=\"{{link.target}}\">{{link.label}}</a>\n" +
...@@ -1252,6 +1252,7 @@ angular.module('openshiftCommonUI') ...@@ -1252,6 +1252,7 @@ angular.module('openshiftCommonUI')
// Listen for updates from NotificationsService to show a notification. // Listen for updates from NotificationsService to show a notification.
var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) { var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
$scope.$evalAsync(function() {
$scope.notifications.push(notification); $scope.notifications.push(notification);
if (NotificationsService.isAutoDismiss(notification)) { if (NotificationsService.isAutoDismiss(notification)) {
$timeout(function () { $timeout(function () {
...@@ -1263,6 +1264,7 @@ angular.module('openshiftCommonUI') ...@@ -1263,6 +1264,7 @@ angular.module('openshiftCommonUI')
// so that the array doesn't grow indefinitely. // so that the array doesn't grow indefinitely.
pruneRemovedNotifications(); pruneRemovedNotifications();
}); });
});
$scope.$on('$destroy', function() { $scope.$on('$destroy', function() {
if (deregisterNotificationListener) { if (deregisterNotificationListener) {
...@@ -2975,6 +2977,46 @@ angular.module('openshiftCommonServices') ...@@ -2975,6 +2977,46 @@ angular.module('openshiftCommonServices')
} }
} }
// If several connection errors happen close together, display them as one
// notification. This prevents us spamming the user with many failed requests
// at once.
var queuedErrors = [];
var addQueuedNotifications = _.debounce(function() {
if (!queuedErrors.length) {
return;
}
// Show all queued messages together. If the details is extremely long, it
// will be truncated with a see more link.
var notification = {
type: 'error',
message: 'An error occurred connecting to the server.',
details: queuedErrors.join('\n'),
links: [{
label: 'Refresh',
onClick: function() {
window.location.reload();
}
}]
};
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', notification);
// Clear the queue.
queuedErrors = [];
}, 300, { maxWait: 1000 });
var showRequestError = function(message, status) {
if (status) {
message += " (status " + status + ")";
}
// Queue the message and call debounced `addQueuedNotifications`.
queuedErrors.push(message);
addQueuedNotifications();
};
function DataService() { function DataService() {
this._listDeferredMap = {}; this._listDeferredMap = {};
this._watchCallbacksMap = {}; this._watchCallbacksMap = {};
...@@ -3311,16 +3353,7 @@ angular.module('openshiftCommonServices') ...@@ -3311,16 +3353,7 @@ angular.module('openshiftCommonServices')
}) })
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
if (opts.errorNotification !== false) { if (opts.errorNotification !== false) {
var msg = "Failed to get " + resource + "/" + name; showRequestError("Failed to get " + resource + "/" + name, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
} }
deferred.reject({ deferred.reject({
data: data, data: data,
...@@ -3856,16 +3889,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3856,16 +3889,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
return; return;
} }
var msg = "Failed to list " + resource; showRequestError("Failed to list " + resource, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
}); });
}); });
} }
...@@ -3887,16 +3911,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -3887,16 +3911,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
return; return;
} }
var msg = "Failed to list " + resource; showRequestError("Failed to list " + resource, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
}); });
} }
}; };
......
...@@ -376,7 +376,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac ...@@ -376,7 +376,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac
" <span class=\"{{notification.type | alertIcon}}\" aria-hidden=\"true\"></span>\n" + " <span class=\"{{notification.type | alertIcon}}\" aria-hidden=\"true\"></span>\n" +
" <span class=\"sr-only\">{{notification.type}}</span>\n" + " <span class=\"sr-only\">{{notification.type}}</span>\n" +
" <span class=\"toast-notification-message\" ng-if=\"notification.message\">{{notification.message}}</span>\n" + " <span class=\"toast-notification-message\" ng-if=\"notification.message\">{{notification.message}}</span>\n" +
" <span ng-if=\"notification.details\">\n" + " <div ng-if=\"notification.details\" class=\"toast-notification-details\">\n" +
" <truncate-long-text\n" + " <truncate-long-text\n" +
" limit=\"200\"\n" + " limit=\"200\"\n" +
" content=\"notification.details\"\n" + " content=\"notification.details\"\n" +
...@@ -384,7 +384,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac ...@@ -384,7 +384,7 @@ angular.module('openshiftCommonUI').run(['$templateCache', function($templateCac
" expandable=\"true\"\n" + " expandable=\"true\"\n" +
" hide-collapse=\"true\">\n" + " hide-collapse=\"true\">\n" +
" </truncate-long-text>\n" + " </truncate-long-text>\n" +
" </span>\n" + " </div>\n" +
" <span ng-repeat=\"link in notification.links\">\n" + " <span ng-repeat=\"link in notification.links\">\n" +
" <a ng-if=\"!link.href\" href=\"\" ng-click=\"onClick(notification, link)\" role=\"button\">{{link.label}}</a>\n" + " <a ng-if=\"!link.href\" href=\"\" ng-click=\"onClick(notification, link)\" role=\"button\">{{link.label}}</a>\n" +
" <a ng-if=\"link.href\" ng-href=\"{{link.href}}\" ng-attr-target=\"{{link.target}}\">{{link.label}}</a>\n" + " <a ng-if=\"link.href\" ng-href=\"{{link.href}}\" ng-attr-target=\"{{link.target}}\">{{link.label}}</a>\n" +
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<span class="{{notification.type | alertIcon}}" aria-hidden="true"></span> <span class="{{notification.type | alertIcon}}" aria-hidden="true"></span>
<span class="sr-only">{{notification.type}}</span> <span class="sr-only">{{notification.type}}</span>
<span class="toast-notification-message" ng-if="notification.message">{{notification.message}}</span> <span class="toast-notification-message" ng-if="notification.message">{{notification.message}}</span>
<span ng-if="notification.details"> <div ng-if="notification.details" class="toast-notification-details">
<truncate-long-text <truncate-long-text
limit="200" limit="200"
content="notification.details" content="notification.details"
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
expandable="true" expandable="true"
hide-collapse="true"> hide-collapse="true">
</truncate-long-text> </truncate-long-text>
</span> </div>
<span ng-repeat="link in notification.links"> <span ng-repeat="link in notification.links">
<a ng-if="!link.href" href="" ng-click="onClick(notification, link)" role="button">{{link.label}}</a> <a ng-if="!link.href" href="" ng-click="onClick(notification, link)" role="button">{{link.label}}</a>
<a ng-if="link.href" ng-href="{{link.href}}" ng-attr-target="{{link.target}}">{{link.label}}</a> <a ng-if="link.href" ng-href="{{link.href}}" ng-attr-target="{{link.target}}">{{link.label}}</a>
......
...@@ -56,6 +56,7 @@ angular.module('openshiftCommonUI') ...@@ -56,6 +56,7 @@ angular.module('openshiftCommonUI')
// Listen for updates from NotificationsService to show a notification. // Listen for updates from NotificationsService to show a notification.
var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) { var deregisterNotificationListener = $rootScope.$on('NotificationsService.onNotificationAdded', function(event, notification) {
$scope.$evalAsync(function() {
$scope.notifications.push(notification); $scope.notifications.push(notification);
if (NotificationsService.isAutoDismiss(notification)) { if (NotificationsService.isAutoDismiss(notification)) {
$timeout(function () { $timeout(function () {
...@@ -67,6 +68,7 @@ angular.module('openshiftCommonUI') ...@@ -67,6 +68,7 @@ angular.module('openshiftCommonUI')
// so that the array doesn't grow indefinitely. // so that the array doesn't grow indefinitely.
pruneRemovedNotifications(); pruneRemovedNotifications();
}); });
});
$scope.$on('$destroy', function() { $scope.$on('$destroy', function() {
if (deregisterNotificationListener) { if (deregisterNotificationListener) {
......
...@@ -76,6 +76,46 @@ angular.module('openshiftCommonServices') ...@@ -76,6 +76,46 @@ angular.module('openshiftCommonServices')
} }
} }
// If several connection errors happen close together, display them as one
// notification. This prevents us spamming the user with many failed requests
// at once.
var queuedErrors = [];
var addQueuedNotifications = _.debounce(function() {
if (!queuedErrors.length) {
return;
}
// Show all queued messages together. If the details is extremely long, it
// will be truncated with a see more link.
var notification = {
type: 'error',
message: 'An error occurred connecting to the server.',
details: queuedErrors.join('\n'),
links: [{
label: 'Refresh',
onClick: function() {
window.location.reload();
}
}]
};
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', notification);
// Clear the queue.
queuedErrors = [];
}, 300, { maxWait: 1000 });
var showRequestError = function(message, status) {
if (status) {
message += " (status " + status + ")";
}
// Queue the message and call debounced `addQueuedNotifications`.
queuedErrors.push(message);
addQueuedNotifications();
};
function DataService() { function DataService() {
this._listDeferredMap = {}; this._listDeferredMap = {};
this._watchCallbacksMap = {}; this._watchCallbacksMap = {};
...@@ -412,16 +452,7 @@ angular.module('openshiftCommonServices') ...@@ -412,16 +452,7 @@ angular.module('openshiftCommonServices')
}) })
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
if (opts.errorNotification !== false) { if (opts.errorNotification !== false) {
var msg = "Failed to get " + resource + "/" + name; showRequestError("Failed to get " + resource + "/" + name, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
} }
deferred.reject({ deferred.reject({
data: data, data: data,
...@@ -957,16 +988,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -957,16 +988,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
return; return;
} }
var msg = "Failed to list " + resource; showRequestError("Failed to list " + resource, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
}); });
}); });
} }
...@@ -988,16 +1010,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR ...@@ -988,16 +1010,7 @@ DataService.prototype.createStream = function(resource, name, context, opts, isR
return; return;
} }
var msg = "Failed to list " + resource; showRequestError("Failed to list " + resource, status);
if (status !== 0) {
msg += " (" + status + ")";
}
// Use `$rootScope.$emit` instead of NotificationsService directly
// so that DataService doesn't add a dependency on `openshiftCommonUI`
$rootScope.$emit('NotificationsService.addNotification', {
type: 'error',
message: msg
});
}); });
} }
}; };
......
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
color: @gray-light; color: @gray-light;
} }
.toast-notification-details .truncated-content {
white-space: pre-line;
}
.toast-notification-message { .toast-notification-message {
font-weight: 700; font-weight: 700;
margin-right: 5px; margin-right: 5px;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment