Move to '404' page when resource type or resource not found

When refresh or link directly to ngdetails without existing resource type
or ID for the resource, ngdetails view shows blank view.

This patch jump to 404 page in this situation.

Change-Id: Ie95132d0fdb1e7aae5e32faad752f92ff76b238a
Closes-Bug: #1746709
This commit is contained in:
Shu Muto 2018-07-04 15:53:03 +09:00
parent 841bce0623
commit 58af806796
4 changed files with 89 additions and 8 deletions

View File

@ -62,6 +62,7 @@ module.exports = function (config) {
// from jasmine.html
xstaticPath + 'jquery/data/jquery.js',
xstaticPath + 'angular/data/angular.js',
xstaticPath + 'angular/data/angular-route.js',
xstaticPath + 'angular/data/angular-mocks.js',
xstaticPath + 'angular/data/angular-cookies.js',
xstaticPath + 'angular_bootstrap/data/angular-bootstrap.js',

View File

@ -17,13 +17,15 @@
angular
.module('horizon.framework', [
'ngRoute',
'horizon.framework.conf',
'horizon.framework.util',
'horizon.framework.widgets'
])
.config(config)
.run(run)
.factory('horizon.framework.redirect', httpRedirectLogin)
.factory('horizon.framework.redirect', redirect)
.config(registerNotFound)
.constant('horizon.framework.events', {
FORCE_LOGOUT: 'FORCE_LOGOUT'
});
@ -74,7 +76,7 @@
// Global http error handler
// if user is not authorized, log user out
// this can happen when session expires
$httpProvider.interceptors.push(httpRedirectLogin);
$httpProvider.interceptors.push(redirect);
$httpProvider.interceptors.push(stripAjaxHeaderForCORS);
stripAjaxHeaderForCORS.$inject = [];
@ -115,7 +117,7 @@
}
}
httpRedirectLogin.$inject = [
redirect.$inject = [
'$q',
'$rootScope',
'$window',
@ -123,7 +125,7 @@
'horizon.framework.widgets.toast.service'
];
function httpRedirectLogin($q, $rootScope, $window, frameworkEvents, toastService) {
function redirect($q, $rootScope, $window, frameworkEvents, toastService) {
return {
responseError: function (error) {
if (error.status === 401) {
@ -135,6 +137,9 @@
handleRedirectMessage(msg2, $rootScope, $window, frameworkEvents, toastService);
}
return $q.reject(error);
},
notFound: function() {
$window.location.href = $window.WEBROOT + 'not_found';
}
};
}
@ -149,4 +154,21 @@
$window.location.replace($window.WEBROOT + 'auth/logout');
}
registerNotFound.$inject = [
'$routeProvider'
];
/**
* @name registerNotFound
* @param {Object} $routeProvider
* @description Routes to "not_found".
* @returns {undefined} Returns nothing
*/
function registerNotFound($routeProvider) {
// if identifier not specified for "ngdetails"
$routeProvider.when('/ngdetails/:resourceType', {
redirectTo: "/not_found"
});
}
})();

View File

@ -22,6 +22,7 @@
controller.$inject = [
'horizon.framework.conf.resource-type-registry.service',
'horizon.framework.redirect',
'horizon.framework.util.actions.action-result.service',
'horizon.framework.util.navigations.service',
'horizon.framework.widgets.modal-wait-spinner.service',
@ -32,6 +33,7 @@
function controller(
registry,
redirect,
resultService,
navigationsService,
spinnerService,
@ -41,13 +43,17 @@
) {
var ctrl = this;
if (!registry.resourceTypes[$routeParams.type]) {
redirect.notFound();
}
ctrl.resourceType = registry.getResourceType($routeParams.type);
ctrl.context = {};
ctrl.context.identifier = ctrl.resourceType.parsePath($routeParams.path);
ctrl.context.loadPromise = ctrl.resourceType.load(ctrl.context.identifier);
ctrl.context.loadPromise.then(loadData);
ctrl.context.loadPromise.then(loadData, loadDataError);
ctrl.defaultTemplateUrl = registry.getDefaultDetailsTemplateUrl();
ctrl.resultHandler = actionResultHandler;
ctrl.pageNotFound = redirect.notFound;
checkRoutedByDjango(ctrl.resourceType);
@ -89,6 +95,12 @@
ctrl.itemName = ctrl.resourceType.itemName(response.data);
}
function loadDataError(error) {
if (error.status === 404) {
redirect.notFound();
}
}
function loadIndexView() {
spinnerService.hideModalSpinner();
ctrl.showDetails = false;

View File

@ -18,7 +18,7 @@
'use strict';
describe('RoutedDetailsViewController', function() {
var ctrl, deferred, $timeout, $q, actionResultService, navigationsService;
var ctrl, deferred, $timeout, $q, service, redirect, actionResultService, navigationsService;
beforeEach(module('horizon.framework.widgets.details'));
beforeEach(inject(function($injector, $controller, _$q_, _$timeout_) {
@ -26,7 +26,8 @@
deferred = $q.defer();
$timeout = _$timeout_;
var service = {
service = {
resourceTypes: {'OS::Glance::Image': {}},
getResourceType: function() {
return {
load: function() { return deferred.promise; },
@ -39,6 +40,11 @@
getDefaultDetailsTemplateUrl: angular.noop
};
redirect = {
responseError: angular.noop,
notFound: angular.noop
};
actionResultService = {
getIdsOfType: function() { return []; }
};
@ -46,11 +52,14 @@
navigationsService = {
expandNavigationByUrl: function() { return ['Project', 'Compute', 'Images']; },
setBreadcrumb: angular.noop,
getActivePanelUrl: function() { return 'project/fancypanel'; }
getActivePanelUrl: function() { return 'project/fancypanel'; },
nav: true,
isNavigationExists: function() { return navigationsService.nav; }
};
ctrl = $controller("RoutedDetailsViewController", {
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.redirect': redirect,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.util.navigations.service': navigationsService,
'horizon.framework.widgets.modal-wait-spinner.service': {
@ -62,8 +71,33 @@
path: '1234'
}
});
spyOn(redirect, 'notFound');
}));
describe('RoutedDetailsViewController', function() {
beforeEach(inject(function($controller) {
service.resourceTypes = {};
ctrl = $controller("RoutedDetailsViewController", {
'horizon.framework.conf.resource-type-registry.service': service,
'horizon.framework.redirect': redirect,
'horizon.framework.util.actions.action-result.service': actionResultService,
'horizon.framework.util.navigations.service': navigationsService,
'horizon.framework.widgets.modal-wait-spinner.service': {
showModalSpinner: angular.noop,
hideModalSpinner: angular.noop
},
'$routeParams': {
type: 'not exist',
path: 'xxxx'
}
});
}));
it('call redirect.notFound when resource type is not registered', function() {
expect(redirect.notFound).toHaveBeenCalled();
});
});
it('sets resourceType', function() {
expect(ctrl.resourceType).toBeDefined();
});
@ -79,6 +113,18 @@
expect(ctrl.itemData).toEqual({some: 'data'});
});
it('call redirect.notFound when item not found', function() {
deferred.reject({status: 404});
$timeout.flush();
expect(redirect.notFound).toHaveBeenCalled();
});
it('does not call redirect.notFound when server error occurred', function() {
deferred.reject({status: 500});
$timeout.flush();
expect(redirect.notFound).not.toHaveBeenCalled();
});
it('sets itemName when item loads', function() {
deferred.resolve({data: {some: 'data'}});
expect(ctrl.itemData).toBeUndefined();