Skip to content

Commit

Permalink
Merge pull request openshift#1325 from jwforres/error_handling
Browse files Browse the repository at this point in the history
Merged by openshift-bot
  • Loading branch information
OpenShift Bot committed Mar 17, 2015
2 parents 1d21d59 + 375006a commit 4548ebb
Show file tree
Hide file tree
Showing 11 changed files with 884 additions and 118 deletions.
8 changes: 7 additions & 1 deletion assets/Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,13 @@ module.exports = function (grunt) {
'bower_components/uri.js/src/punycode.js',
'bower_components/uri.js/src/URI.min.js',
'bower_components/uri.js/src/jquery.URI.min.js',
'bower_components/uri.js/src/URI.fragmentQuery.js'
'bower_components/uri.js/src/URI.fragmentQuery.js',
'bower_components/messenger/build/css/messenger-theme-future.css',
'bower_components/messenger/build/css/messenger-theme-flat.css',
'bower_components/messenger/build/css/messenger-theme-air.css',
'bower_components/messenger/build/css/messenger-theme-ice.css',
'bower_components/messenger/build/js/messenger-theme-future.js',
'bower_components/messenger/build/js/messenger-theme-flat.js'
]
}
},
Expand Down
4 changes: 4 additions & 0 deletions assets/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<!-- build:css(.) styles/vendor.css -->
<!-- bower:css -->
<link rel="stylesheet" href="bower_components/messenger/build/css/messenger.css" />
<link rel="stylesheet" href="bower_components/messenger/build/css/messenger-theme-block.css" />
<!-- endbower -->
<!-- endbuild -->
<!-- build:css(.tmp) styles/main.css -->
Expand Down Expand Up @@ -102,6 +104,7 @@
<script src="bower_components/microplugin/src/microplugin.js"></script>
<script src="bower_components/selectize/dist/js/selectize.js"></script>
<script src="bower_components/zeroclipboard/dist/ZeroClipboard.js"></script>
<script src="bower_components/messenger/build/js/messenger.js"></script>
<!-- endbower -->
<!-- endbuild -->

Expand All @@ -116,6 +119,7 @@
<script src="scripts/services/logout.js"></script>
<script src="scripts/services/labelFilter.js"></script>
<script src="scripts/services/tasks.js"></script>
<script src="scripts/services/notification.js"></script>
<script src="scripts/controllers/projects.js"></script>
<script src="scripts/controllers/project.js"></script>
<script src="scripts/controllers/pods.js"></script>
Expand Down
36 changes: 31 additions & 5 deletions assets/app/scripts/controllers/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,47 @@
* Controller of the openshiftConsole
*/
angular.module('openshiftConsole')
.controller('ProjectController', function ($scope, $routeParams, DataService, AuthService, $filter, LabelFilter) {
.controller('ProjectController', function ($scope, $routeParams, DataService, AuthService, $filter, LabelFilter, $location) {

$scope.projectName = $routeParams.project;
$scope.project = {};
$scope.projectPromise = $.Deferred();
$scope.projects = {};
$scope.alerts = {};
$scope.renderOptions = {
hideFilterWidget: false
};

AuthService.withUser().then(function() {
DataService.get("projects", $scope.projectName, $scope).then(function(project) {
$scope.project = project;
$scope.projectPromise.resolve(project);
});
DataService.get("projects", $scope.projectName, $scope, {errorNotification: false}).then(
// success
function(project) {
$scope.project = project;
$scope.projectPromise.resolve(project);
},
// failure
function(e) {
$scope.projectPromise.reject(e);
if (e.status == 403 || e.status == 404) {
var message = e.status == 403 ?
("The project " + $scope.projectName + " does not exist or you are not authorized to view it.") :
("The project " + $scope.projectName + " does not exist.")
var redirect = URI('/error').query({
"error_description": message,
"error" : e.status == 403 ? 'access_denied' : 'not_found'
}).toString();
$location.url(redirect);
}
else {
// Something spurious has happened, stay on the page and show an alert
$scope.alerts["load"] = {
type: "error",
message: "The project could not be loaded.",
details: e.data
};
}
}
);

DataService.list("projects", $scope, function(projects) {
$scope.projects = projects.by("metadata.name");
Expand Down
30 changes: 17 additions & 13 deletions assets/app/scripts/controllers/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,23 @@
*/
angular.module('openshiftConsole')
.controller('ErrorController', function ($scope) {
var params = URI(window.location.href).query(true);
var error = params.error;
var error_description = params.error_description;
var error_uri = params.error_uri;
var params = URI(window.location.href).query(true);
var error = params.error;
var error_description = params.error_description;
var error_uri = params.error_uri;

switch(error) {
case 'access_denied':
$scope.errorMessage = "Access denied";
default:
$scope.errorMessage = "An error has occurred";
}
switch(error) {
case 'access_denied':
$scope.errorMessage = "Access denied";
break;
case 'not_found':
$scope.errorMessage = "Not found";
break;
default:
$scope.errorMessage = "An error has occurred";
}

if (params.error_description) {
$scope.errorDetails = params.error_description;
}
if (params.error_description) {
$scope.errorDetails = params.error_description;
}
});
2 changes: 1 addition & 1 deletion assets/app/scripts/controllers/util/oauth.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ angular.module('openshiftConsole')
}

// Try to fetch the user
var opts = {http: {auth: {token: token, triggerLogin: false}}};
var opts = {errorNotification: false, http: {auth: {token: token, triggerLogin: false}}};
if (debug) { console.log("OAuthController, got token, fetching user", opts); }

DataService.get("users", "~", {}, opts)
Expand Down
80 changes: 75 additions & 5 deletions assets/app/scripts/services/data.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

angular.module('openshiftConsole')
.factory('DataService', function($http, $ws, $rootScope, $q, API_CFG) {
.factory('DataService', function($http, $ws, $rootScope, $q, API_CFG, Notification) {
function Data(array) {
this._data = {};
this._objectsByAttribute(array, "metadata.name", this._data);
Expand Down Expand Up @@ -84,6 +84,12 @@ angular.module('openshiftConsole')
this._watchOptionsMap = {};
this._watchWebsocketsMap = {};
this._watchPollTimeoutsMap = {};
this._watchWebsocketRetriesMap = {};

var self = this;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
self._watchWebsocketRetriesMap = {};
});
}

// type: API type (e.g. "pods")
Expand Down Expand Up @@ -213,6 +219,7 @@ angular.module('openshiftConsole')
// context: API context (e.g. {project: "..."})
// opts: force - always request (default is false)
// http - options to pass to the inner $http call
// errorNotification - will popup an error notification if the API request fails (default true)
DataService.prototype.get = function(type, name, context, opts) {
opts = opts || {};

Expand Down Expand Up @@ -251,6 +258,13 @@ angular.module('openshiftConsole')
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
if (opts.errorNotification !== false) {
var msg = "Failed to get " + type + "/" + name;
if (status !== 0) {
msg += " (" + status + ")"
}
Notification.error(msg);
}
deferred.reject({
data: data,
status: status,
Expand Down Expand Up @@ -331,7 +345,8 @@ angular.module('openshiftConsole')
clearTimeout(this._watchPollTimeouts(type, context));
this._watchPollTimeouts(type, context, null);
}
else {
else if (this._watchWebsockets(type, context)){
// watchWebsockets may not have been set up yet if the projectPromise never resolves
this._watchWebsockets(type, context).close();
this._watchWebsockets(type, context, null);
}
Expand Down Expand Up @@ -435,6 +450,16 @@ angular.module('openshiftConsole')
}
};

DataService.prototype._watchWebsocketRetries = function(type, context, retry) {
var key = this._uniqueKeyForTypeContext(type, context);
if (retry === undefined) {
return this._watchWebsocketRetriesMap[key];
}
else {
this._watchWebsocketRetriesMap[key] = retry;
}
};

DataService.prototype._uniqueKeyForTypeContext = function(type, context) {
// Note: when we start handling selecting multiple projects this
// will change to include all relevant scope
Expand All @@ -453,6 +478,13 @@ angular.module('openshiftConsole')
url: self._urlForType(type, null, context, false, {namespace: project.metadata.name})
}).success(function(data, status, headerFunc, config, statusText) {
self._listOpComplete(type, context, data);
}).error(function(data, status, headers, config) {
var msg = "Failed to list " + type;
if (status !== 0) {
msg += " (" + status + ")"
}
// TODO would like to make this optional with an errorNotification option, see get for an example
Notification.error(msg);
});
});
}
Expand All @@ -462,6 +494,13 @@ angular.module('openshiftConsole')
url: this._urlForType(type, null, context),
}).success(function(data, status, headerFunc, config, statusText) {
self._listOpComplete(type, context, data);
}).error(function(data, status, headers, config) {
var msg = "Failed to list " + type;
if (status !== 0) {
msg += " (" + status + ")"
}
// TODO would like to make this optional with an errorNotification option, see get for an example
Notification.error(msg);
});
}
};
Expand Down Expand Up @@ -508,7 +547,8 @@ angular.module('openshiftConsole')
method: "WATCH",
url: self._urlForType(type, null, context, true, params),
onclose: $.proxy(self, "_watchOpOnClose", type, context),
onmessage: $.proxy(self, "_watchOpOnMessage", type, context)
onmessage: $.proxy(self, "_watchOpOnMessage", type, context),
onopen: $.proxy(self, "_watchOpOnOpen", type, context)
}).then(function(ws) {
console.log("Watching", ws);
self._watchWebsockets(type, context, ws);
Expand All @@ -520,7 +560,8 @@ angular.module('openshiftConsole')
method: "WATCH",
url: self._urlForType(type, null, context, true, params),
onclose: $.proxy(self, "_watchOpOnClose", type, context),
onmessage: $.proxy(self, "_watchOpOnMessage", type, context)
onmessage: $.proxy(self, "_watchOpOnMessage", type, context),
onopen: $.proxy(self, "_watchOpOnOpen", type, context)
}).then(function(ws){
console.log("Watching", ws);
self._watchWebsockets(type, context, ws);
Expand All @@ -529,6 +570,11 @@ angular.module('openshiftConsole')
}
};

DataService.prototype._watchOpOnOpen = function(type, context, event) {
// If we opened the websocket cleanly, set retries to 0
this._watchWebsocketRetries(type, context, 0);
};

DataService.prototype._watchOpOnMessage = function(type, context, event) {
try {
var eventData = $.parseJSON(event.data);
Expand All @@ -552,8 +598,32 @@ angular.module('openshiftConsole')
// Attempt to re-establish the connection in cases
// where the socket close was unexpected, i.e. the event's
// wasClean attribute is false
var retry = this._watchWebsocketRetries(type, context) || 0;
if (!event.wasClean && this._watchCallbacks(type, context).has()) {
this._startWatchOp(type, context, this._resourceVersion(type, context));
if (retry < 5) {
this._watchWebsocketRetries(type, context, retry + 1);
setTimeout(
$.proxy(this, "_startWatchOp", type, context, this._resourceVersion(type, context)),
1000
);
}
else {
Notification.error(
"Server connection interrupted.",
{
id: "websocket_retry_halted",
mustDismiss: true,
actions: {
"refresh" : {
label: "Refresh",
action: function() {
window.location.reload();
}
}
}
}
);
}
}
};

Expand Down
59 changes: 59 additions & 0 deletions assets/app/scripts/services/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

angular.module('openshiftConsole')
.factory('Notification', function($rootScope) {
function Notification() {
this.messenger = Messenger({
extraClasses: 'messenger-fixed messenger-on-top messenger-on-right',
theme: 'block',
messageDefaults: {
showCloseButton: true,
hideAfter: 10
}
});

var self = this;
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
self.clear();
});
}

// Opts:
// id - if an id is passed only one message with this id will ever be shown
// mustDismiss - the user must explicitly dismiss the message, it will not auto-hide
Notification.prototype.notify = function(type, message, opts) {
opts = opts || {};
var notifyOpts = {
type: type,
message: message,
id: opts.id,
actions: opts.actions
};
if (opts.mustDismiss) {
notifyOpts.hideAfter = false;
}
this.messenger.post(notifyOpts);
};

Notification.prototype.success = function(message, opts) {
this.notify("success", message, opts);
};

Notification.prototype.info = function(message, opts) {
this.notify("info", message, opts);
};

Notification.prototype.error = function(message, opts) {
this.notify("error", message, opts);
};

Notification.prototype.warning = function(message, opts) {
this.notify("warning", message, opts);
};

Notification.prototype.clear = function() {
this.messenger.hideAll();
};

return new Notification();;
});
11 changes: 6 additions & 5 deletions assets/app/scripts/services/ws.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ angular.module('openshiftConsole')

if (debug) { console.log("$ws (pre-intercept)", config.url.toString()); }
var serverRequest = function(config) {
if (debug) { console.log("$ws (post-intercept)", config.url.toString()); }
var ws = new WebSocket(config.url);
if (config.onclose) { ws.onclose = config.onclose; }
if (config.onmessage) { ws.onmessage = config.onmessage; }
return ws;
if (debug) { console.log("$ws (post-intercept)", config.url.toString()); }
var ws = new WebSocket(config.url);
if (config.onclose) { ws.onclose = config.onclose; }
if (config.onmessage) { ws.onmessage = config.onmessage; }
if (config.onopen) { ws.onopen = config.onopen; }
return ws;
};

// Apply interceptors to request config
Expand Down
Loading

0 comments on commit 4548ebb

Please sign in to comment.