Improve error handling mechanism (related to getredash/redash#2162)

This commit is contained in:
Levko Kravets 2017-12-27 16:01:29 +02:00
parent 6441c4b52d
commit 1bfea8b493
8 changed files with 83 additions and 49 deletions

View File

@ -0,0 +1,64 @@
import template from './template.html';
export default function init(ngModule) {
ngModule.component('appView', {
template,
controller($rootScope, $route, Auth) {
this.showHeaderAndFooter = false;
$rootScope.$on('$routeChangeStart', (event, route) => {
if (route.$$route.authenticated) {
// For routes that need authentication, check if session is already
// loaded, and load it if not.
Auth.logger('Requested authenticated route: ', route);
if (Auth.isAuthenticated()) {
this.showHeaderAndFooter = true;
} else {
event.preventDefault();
Auth.loadSession().then(() => {
if (Auth.isAuthenticated()) {
this.showHeaderAndFooter = true;
Auth.logger('Loaded session');
$route.reload();
} else {
throw new Error('Need to login');
}
}).catch(() => {
Auth.logger('Need to login, redirecting');
Auth.login();
});
}
} else {
this.showHeaderAndFooter = false;
}
});
this.error = null;
$rootScope.$on('appViewError', (event, error) => {
if ((error !== null) && (error !== undefined) && (error !== '')) {
this.error = error instanceof Error ? error : new Error('' + error);
} else {
this.error = null;
}
});
$rootScope.$on('$routeChangeSuccess', () => {
$rootScope.$broadcast('appViewError', null);
});
$rootScope.$on('appViewRejection', (event, rejection) => {
let error = null;
switch (rejection.status) {
case 403: error = new Error(''); break;
default: error = new Error(rejection.data.message); break;
}
$rootScope.$broadcast('appViewError', error);
});
$rootScope.$on('$routeChangeError', (event, current, previous, rejection) => {
$rootScope.$broadcast('appViewRejection', rejection);
});
},
});
}

View File

@ -0,0 +1,6 @@
<app-header ng-if="$ctrl.showHeaderAndFooter"></app-header>
<div ng-if="$ctrl.error" class="container-fluid">
<div class="alert alert-danger">{{ $ctrl.error.message }}</div>
</div>
<div ng-hide="$ctrl.error" ng-view></div>
<footer ng-if="$ctrl.showHeaderAndFooter"></footer>

View File

@ -1,19 +0,0 @@
export default function init(ngModule) {
ngModule.component('routeStatus', {
template: '<overlay ng-if="$ctrl.permissionDenied">You do not have permission to load this page.',
controller($rootScope) {
this.permissionDenied = false;
$rootScope.$on('$routeChangeSuccess', () => {
this.permissionDenied = false;
});
$rootScope.$on('$routeChangeError', (event, current, previous, rejection) => {
if (rejection.status === 403) {
this.permissionDenied = true;
}
});
},
});
}

View File

@ -84,11 +84,6 @@ function registerPages() {
ngModule.config(($routeProvider) => {
each(routes, (route, path) => {
logger('Registering route: %s', path);
// This is a workaround, to make sure app-header and footer are loaded only
// for the authenticated routes.
// We should look into switching to ui-router, that has built in support for
// such things.
route.template = `<app-header></app-header><route-status></route-status>${route.template}<footer></footer>`;
route.authenticated = true;
$routeProvider.when(path, route);
});

View File

@ -13,7 +13,7 @@
<body>
<section>
<div ng-view></div>
<app-view></app-view>
</section>
</body>
</html>

View File

@ -13,7 +13,7 @@
<body>
<section>
<div ng-view></div>
<app-view></app-view>
</section>
</body>
</html>

View File

@ -164,12 +164,16 @@ function DashboardCtrl(
this.dashboard = Dashboard.get({ slug: $routeParams.dashboardSlug }, (dashboard) => {
Events.record('view', 'dashboard', dashboard.id);
renderDashboard(dashboard, force);
}, () => {
// error...
// try again. we wrap loadDashboard with throttle so it doesn't happen too often.
// we might want to consider exponential backoff and also move this as a general
// solution in $http/$resource for all AJAX calls.
this.loadDashboard();
}, (error) => {
const statusGroup = Math.floor(error.status / 100);
if (statusGroup === 5) {
// recoverable errors - all 5** (server is temporarily unavailable
// for some reason, but it should get up soon).
this.loadDashboard();
} else {
// all kind of 4** errors are not recoverable, so just display them
$rootScope.$broadcast('appViewRejection', error);
}
});
}, 1000);

View File

@ -24,6 +24,7 @@ function getLocalSessionData() {
function AuthService($window, $location, $q, $http) {
const Auth = {
logger,
isAuthenticated() {
const sessionData = getLocalSessionData();
return sessionData.loaded && sessionData.user.id;
@ -112,21 +113,4 @@ export default function init(ngModule) {
ngModule.config(($httpProvider) => {
$httpProvider.interceptors.push('apiKeyHttpInterceptor');
});
ngModule.run(($location, $window, $rootScope, $route, Auth) => {
$rootScope.$on('$routeChangeStart', (event, to) => {
if (to.authenticated && !Auth.isAuthenticated()) {
logger('Requested authenticated route: ', to);
event.preventDefault();
// maybe we only miss the session? try to load session
Auth.loadSession().then(() => {
logger('Loaded session');
$route.reload();
}).catch(() => {
logger('Need to login, redirecting');
Auth.login();
});
}
});
});
}