From 58af8067966539dd3417f113e7356298b48386e5 Mon Sep 17 00:00:00 2001 From: Shu Muto <shu.mutow@gmail.com> Date: Wed, 4 Jul 2018 15:53:03 +0900 Subject: [PATCH] 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 --- horizon/karma.conf.js | 1 + horizon/static/framework/framework.module.js | 30 +++++++++-- .../details/routed-details-view.controller.js | 14 ++++- .../routed-details-view.controller.spec.js | 52 +++++++++++++++++-- 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/horizon/karma.conf.js b/horizon/karma.conf.js index 495495dc23..f05dc11f97 100644 --- a/horizon/karma.conf.js +++ b/horizon/karma.conf.js @@ -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', diff --git a/horizon/static/framework/framework.module.js b/horizon/static/framework/framework.module.js index fb3fb86ba9..755dde3b84 100644 --- a/horizon/static/framework/framework.module.js +++ b/horizon/static/framework/framework.module.js @@ -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" + }); + } + })(); diff --git a/horizon/static/framework/widgets/details/routed-details-view.controller.js b/horizon/static/framework/widgets/details/routed-details-view.controller.js index edd2fea4b9..7cedd80e20 100644 --- a/horizon/static/framework/widgets/details/routed-details-view.controller.js +++ b/horizon/static/framework/widgets/details/routed-details-view.controller.js @@ -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; diff --git a/horizon/static/framework/widgets/details/routed-details-view.controller.spec.js b/horizon/static/framework/widgets/details/routed-details-view.controller.spec.js index 6e9e99dcce..a765af3311 100644 --- a/horizon/static/framework/widgets/details/routed-details-view.controller.spec.js +++ b/horizon/static/framework/widgets/details/routed-details-view.controller.spec.js @@ -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();