[CORD-1117] XOS GUI Various fix
Change-Id: I4237a5e23509e9173c958d76aa929a70583ba1e6
diff --git a/src/app/core/index.ts b/src/app/core/index.ts
index 0cc1e12..64d295c 100644
--- a/src/app/core/index.ts
+++ b/src/app/core/index.ts
@@ -24,6 +24,7 @@
import {PaginationFilter} from './pagination/pagination.filter';
import {XosDebouncer} from './services/helpers/debounce.helper';
import {ArrayToListFilter} from './table/array-to-list.filter';
+import {xosLoader} from './loader/loader';
export const xosCore = 'xosCore';
@@ -48,6 +49,7 @@
.component('xosFooter', xosFooter)
.component('xosNav', xosNav)
.component('xosLogin', xosLogin)
+ .component('xosLoader', xosLoader)
.component('xosPagination', xosPagination)
.component('xosTable', xosTable)
.component('xosForm', xosForm)
diff --git a/src/app/core/loader/loader.spec.ts b/src/app/core/loader/loader.spec.ts
new file mode 100644
index 0000000..1bcff9c
--- /dev/null
+++ b/src/app/core/loader/loader.spec.ts
@@ -0,0 +1,115 @@
+import * as angular from 'angular';
+import 'angular-mocks';
+import {xosLoader} from './loader';
+
+let loaded = true;
+
+const MockConfig = {
+ lastVisitedUrl: '/test'
+};
+
+const MockDiscover = {
+ areModelsLoaded: () => loaded,
+ discover: null
+};
+
+const MockOnboarder = {
+ onboard: null
+};
+
+describe('The XosLoader component', () => {
+ beforeEach(() => {
+ angular
+ .module('loader', [])
+ .value('XosConfig', MockConfig)
+ .value('XosModelDiscoverer', MockDiscover)
+ .value('XosOnboarder', MockOnboarder)
+ .component('xosLoader', xosLoader);
+ angular.mock.module('loader');
+ });
+
+ let scope, element, isolatedScope, rootScope, compile, timeout, location;
+ const compileElement = () => {
+
+ if (!scope) {
+ scope = rootScope.$new();
+ }
+
+ element = angular.element('<xos-loader></xos-loader>');
+ compile(element)(scope);
+ scope.$digest();
+ isolatedScope = element.isolateScope().vm;
+ };
+
+ beforeEach(inject(function ($q: ng.IQService, $compile: ng.ICompileService, $rootScope: ng.IScope, $timeout: ng.ITimeoutService, $location: ng.ILocationService) {
+ compile = $compile;
+ rootScope = $rootScope;
+ timeout = $timeout;
+ location = $location;
+ spyOn(location, 'path');
+
+ MockDiscover.discover = jasmine.createSpy('discover')
+ .and.callFake(() => {
+ const d = $q.defer();
+ d.resolve(true);
+ return d.promise;
+ });
+
+ MockOnboarder.onboard = jasmine.createSpy('onboard')
+ .and.callFake(() => {
+ const d = $q.defer();
+ d.resolve();
+ return d.promise;
+ });
+ }));
+
+ describe('when models are already loaded', () => {
+
+ beforeEach(() => {
+ compileElement();
+ spyOn(isolatedScope, 'moveOnTo');
+ isolatedScope.run();
+ timeout.flush();
+ });
+
+ it('should redirect to the last visited page', (done) => {
+ window.setTimeout(() => {
+ expect(isolatedScope.moveOnTo).toHaveBeenCalledWith('/test');
+ expect(location.path).toHaveBeenCalledWith('/test');
+ done();
+ }, 600);
+ });
+ });
+
+ describe('when the last visited page is "loader"', () => {
+
+ beforeEach(() => {
+ MockConfig.lastVisitedUrl = '/loader';
+ compileElement();
+ spyOn(isolatedScope, 'moveOnTo');
+ isolatedScope.run();
+ });
+
+ it('should redirect to the "dashboard" page', (done) => {
+ window.setTimeout(() => {
+ expect(isolatedScope.moveOnTo).toHaveBeenCalledWith('/loader');
+ expect(location.path).toHaveBeenCalledWith('/dashboard');
+ done();
+ }, 600);
+ });
+ });
+
+ describe('when models are not loaded', () => {
+
+ beforeEach(() => {
+ loaded = false;
+ compileElement();
+ spyOn(isolatedScope, 'moveOnTo');
+ });
+
+ it('should call XosModelDiscoverer.discover', () => {
+ expect(MockDiscover.discover).toHaveBeenCalled();
+ });
+ });
+
+});
diff --git a/src/app/core/loader/loader.ts b/src/app/core/loader/loader.ts
new file mode 100644
index 0000000..07a9875
--- /dev/null
+++ b/src/app/core/loader/loader.ts
@@ -0,0 +1,83 @@
+import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
+import {IXosOnboarder} from '../../extender/services/onboard.service';
+class LoaderCtrl {
+ static $inject = [
+ '$log',
+ '$rootScope',
+ '$location',
+ '$timeout',
+ 'XosConfig',
+ 'XosModelDiscoverer',
+ `XosOnboarder`
+ ];
+
+ public aaaaa = 'ciao';
+
+ constructor (
+ private $log: ng.ILogService,
+ private $rootScope: ng.IScope,
+ private $location: ng.ILocationService,
+ private $timeout: ng.ITimeoutService,
+ private XosConfig: any,
+ private XosModelDiscoverer: IXosModelDiscovererService,
+ private XosOnboarder: IXosOnboarder
+ ) {
+
+ this.run();
+ }
+
+ public run() {
+ if (this.XosModelDiscoverer.areModelsLoaded()) {
+ this.moveOnTo(this.XosConfig.lastVisitedUrl);
+ }
+ else {
+ this.XosModelDiscoverer.discover()
+ // NOTE loading XOS Models
+ .then((res) => {
+ if (res) {
+ this.$log.info('[XosLoader] All models loaded');
+ }
+ else {
+ this.$log.info('[XosLoader] Failed to load some models, moving on.');
+ }
+ return this.XosOnboarder.onboard();
+ })
+ // NOTE loading GUI Extensions
+ .then(() => {
+ this.moveOnTo(this.XosConfig.lastVisitedUrl);
+ })
+ .finally(() => {
+ // NOTE it is in a timeout as the searchService is loaded after that
+ // we navigate to another page
+ this.$timeout(() => {
+ this.$rootScope.$emit('xos.core.modelSetup');
+ }, 500);
+ });
+ }
+ }
+
+ public moveOnTo(url: string) {
+ this.$log.info(`[XosLoader] Redirecting to: ${url}`);
+ switch (url) {
+ case '':
+ case '/':
+ case '/loader':
+ case '/login':
+ this.$location.path('/dashboard');
+ break;
+ default:
+ this.$timeout(() => {
+ this.$location.path(url);
+ }, 500);
+ break;
+ }
+ }
+}
+
+export const xosLoader: angular.IComponentOptions = {
+ template: `
+ <div class="loader"></div>
+ `,
+ controllerAs: 'vm',
+ controller: LoaderCtrl
+};
diff --git a/src/app/core/login/login.ts b/src/app/core/login/login.ts
index 558fe37..8aa40d5 100644
--- a/src/app/core/login/login.ts
+++ b/src/app/core/login/login.ts
@@ -2,10 +2,15 @@
import './login.scss';
import {IXosStyleConfig} from '../../../index';
-import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
class LoginCtrl {
- static $inject = ['$log', 'AuthService', '$state', 'XosModelDiscoverer', 'StyleConfig'];
+ static $inject = [
+ '$log',
+ 'AuthService',
+ '$state',
+ 'StyleConfig'
+ ];
+
public loginStyle: any;
public img: string;
public showErrorMsg: boolean = false;
@@ -14,7 +19,6 @@
private $log: ng.ILogService,
private authService: AuthService,
private $state: angular.ui.IStateService,
- private XosModelDiscoverer: IXosModelDiscovererService,
private StyleConfig: IXosStyleConfig
) {
@@ -35,15 +39,10 @@
password: password
})
.then(res => {
- this.showErrorMsg = false;
- // after login set up models
- return this.XosModelDiscoverer.discover();
- })
- .then(() => {
- this.$state.go('xos.dashboard');
+ this.$state.go('loader');
})
.catch(e => {
- this.$log.error(`[XosLogin] Error during login.`);
+ this.$log.error(`[XosLogin] Error during login.`, e);
this.errorMsg = `Something went wrong, please try again.`;
this.showErrorMsg = true;
});
diff --git a/src/app/core/services/keyboard-shortcut.spec.ts b/src/app/core/services/keyboard-shortcut.spec.ts
index b573656..deb04b7 100644
--- a/src/app/core/services/keyboard-shortcut.spec.ts
+++ b/src/app/core/services/keyboard-shortcut.spec.ts
@@ -7,6 +7,7 @@
let $log: ng.ILogService;
let $transitions: any;
let XosSidePanel: IXosSidePanelService;
+let logSpy: any;
const baseGlobalModifiers: IXosKeyboardShortcutBinding[] = [
{
@@ -55,6 +56,7 @@
$log = _$log_;
$transitions = _$transitions_;
XosSidePanel = _XosSidePanel_;
+ logSpy = spyOn($log, 'warn');
});
service = new XosKeyboardShortcut($log, $transitions, XosSidePanel);
@@ -167,13 +169,11 @@
});
it('should not add binding that has an already registered key', () => {
- function errorFunctionWrapper() {
- service['registerKeyBinding']({
- key: 'A',
- cb: 'cb'
- }, 'global');
- }
- expect(errorFunctionWrapper).toThrow(new Error('[XosKeyboardShortcut] A shortcut for key "a" has already been registered'));
+ service['registerKeyBinding']({
+ key: 'A',
+ cb: 'cb'
+ }, 'global');
+ expect(logSpy).toHaveBeenCalledWith('[XosKeyboardShortcut] A shortcut for key "a" has already been registered');
});
});
});
diff --git a/src/app/core/services/keyboard-shortcut.ts b/src/app/core/services/keyboard-shortcut.ts
index e814943..35af07e 100644
--- a/src/app/core/services/keyboard-shortcut.ts
+++ b/src/app/core/services/keyboard-shortcut.ts
@@ -120,7 +120,8 @@
binding.key = binding.key.toLowerCase();
if (_.find(this.keyMapping.global, {key: binding.key}) || _.find(this.keyMapping.view, {key: binding.key})) {
- throw new Error(`[XosKeyboardShortcut] A shortcut for key "${binding.key}" has already been registered`);
+ this.$log.warn(`[XosKeyboardShortcut] A shortcut for key "${binding.key}" has already been registered`);
+ return;
}
this.$log.debug(`[XosKeyboardShortcut] Registering binding for key: ${binding.key}`);
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
index 1078853..ba9cb13 100644
--- a/src/app/datasources/helpers/model-discoverer.service.ts
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -25,6 +25,7 @@
discover(): ng.IPromise<boolean>;
get(modelName: string): IXosModel;
getApiUrlFromModel(model: IXosModel): string;
+ areModelsLoaded(): boolean;
}
export class XosModelDiscovererService implements IXosModelDiscovererService {
@@ -41,6 +42,7 @@
private xosModels: IXosModel[] = []; // list of augmented model definitions;
private xosServices: string[] = []; // list of loaded services
private progressBar;
+ private modelsLoaded: boolean = false;
constructor (
private $log: ng.ILogService,
@@ -56,6 +58,10 @@
this.progressBar.setColor('#f6a821');
}
+ public areModelsLoaded(): boolean {
+ return this.modelsLoaded;
+ }
+
public get(modelName: string): IXosModel|null {
return _.find(this.xosModels, m => m.name === modelName);
}
@@ -119,6 +125,7 @@
})
.finally(() => {
this.progressBar.complete();
+ this.modelsLoaded = true;
});
});
return d.promise;
diff --git a/src/app/datasources/helpers/search.service.ts b/src/app/datasources/helpers/search.service.ts
index fd038a2..2a77ab6 100644
--- a/src/app/datasources/helpers/search.service.ts
+++ b/src/app/datasources/helpers/search.service.ts
@@ -41,6 +41,7 @@
return list;
}, []);
this.states = _.uniqBy(this.states, 'state');
+ this.$log.debug(`[XosSearchService] States: `, this.states);
});
}
diff --git a/src/app/extender/services/onboard.service.spec.ts b/src/app/extender/services/onboard.service.spec.ts
index d53f3af..02237cc 100644
--- a/src/app/extender/services/onboard.service.spec.ts
+++ b/src/app/extender/services/onboard.service.spec.ts
@@ -61,6 +61,9 @@
spyOn($ocLazyLoad, 'load').and.callThrough();
service = XosOnboarder;
$timeout = _$timeout_;
+
+ // start the service
+ service.onboard();
}));
describe('when receive an event', () => {
diff --git a/src/app/extender/services/onboard.service.ts b/src/app/extender/services/onboard.service.ts
index b6a9abc..206a968 100644
--- a/src/app/extender/services/onboard.service.ts
+++ b/src/app/extender/services/onboard.service.ts
@@ -4,11 +4,18 @@
import {Observable} from 'rxjs';
export interface IXosOnboarder {
-
+ onboard(): ng.IPromise<boolean>;
}
export class XosOnboarder implements IXosOnboarder {
- static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad', 'XosModelStore'];
+ static $inject = [
+ '$timeout',
+ '$log',
+ '$q',
+ 'WebSocket',
+ '$ocLazyLoad',
+ 'XosModelStore'
+ ];
constructor(
private $timeout: ng.ITimeoutService,
@@ -18,23 +25,37 @@
private $ocLazyLoad: any, // TODO add definition
private XosModelStore: IXosModelStoreService
) {
+
+ }
+
+ public onboard(): ng.IPromise<boolean> {
+ const d = this.$q.defer();
+
this.$log.info('[XosOnboarder] Setup');
// Load onboarded app
const ComponentObservable: Observable<any> = this.XosModelStore.query('XOSGuiExtension');
ComponentObservable.subscribe(
- (component) => {
- _.forEach(component, (c) => {
- this.$log.info(`[XosOnboarder] Loading files for app: ${c.name}`);
- const files = c.files.split(',').map(s => s.trim());
- this.loadFile(files)
- .then((res) => {
- this.$log.info(`[XosOnboarder] All files loaded for app: ${c.name}`);
- });
- });
+ (component) => {
+ if (component.length === 0) {
+ return d.resolve();
}
- );
+ _.forEach(component, (c) => {
+ this.$log.info(`[XosOnboarder] Loading files for app: ${c.name}`);
+ const files = c.files.split(',').map(s => s.trim());
+ this.loadFile(files)
+ .then((res) => {
+ this.$log.info(`[XosOnboarder] All files loaded for app: ${c.name}`);
+ d.resolve();
+ })
+ .catch(e => {
+ this.$log.info(`[XosOnboarder] Error while onboarding apps: `, e);
+ });
+ });
+ }
+ );
+ return d.promise;
}
// NOTE files needs to be loaded in order, so async loop!
@@ -43,6 +64,7 @@
d = this.$q.defer();
}
const file = files.shift();
+
this.$log.info(`[XosOnboarder] Loading file: ${file}`);
this.$ocLazyLoad.load(file)
.then((res) => {
diff --git a/src/app/service-graph/components/coarse/coarse.component.ts b/src/app/service-graph/components/coarse/coarse.component.ts
index d02bb23..c7a7ac5 100644
--- a/src/app/service-graph/components/coarse/coarse.component.ts
+++ b/src/app/service-graph/components/coarse/coarse.component.ts
@@ -80,6 +80,9 @@
}
private _renderGraph() {
+ if (!angular.isDefined(this.graph) || !angular.isDefined(this.graph.nodes) || !angular.isDefined(this.graph.links)) {
+ return;
+ }
this.addNodeLinksToForceLayout(this.graph);
this.renderNodes(this.graph.nodes);
this.renderLinks(this.graph.links);
@@ -87,8 +90,8 @@
private getSvgDimensions(): {width: number, heigth: number} {
return {
- width: $('xos-coarse-tenancy-graph svg').width(),
- heigth: $('xos-coarse-tenancy-graph svg').height()
+ width: $('xos-coarse-tenancy-graph svg').width() || 0,
+ heigth: $('xos-coarse-tenancy-graph svg').height() || 0
};
}
diff --git a/src/app/service-graph/components/fine-grained/fine-grained.component.ts b/src/app/service-graph/components/fine-grained/fine-grained.component.ts
index 64955ae..d0aea83 100644
--- a/src/app/service-graph/components/fine-grained/fine-grained.component.ts
+++ b/src/app/service-graph/components/fine-grained/fine-grained.component.ts
@@ -83,6 +83,9 @@
}
private _renderGraph() {
+ if (!angular.isDefined(this.graph) || !angular.isDefined(this.graph.nodes) || !angular.isDefined(this.graph.links)) {
+ return;
+ }
this.addNodeLinksToForceLayout(this.graph);
this.renderNodes(this.graph.nodes);
this.renderLinks(this.graph.links);
diff --git a/src/index.ts b/src/index.ts
index d2f6c72..042f1bd 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -62,16 +62,15 @@
.factory('CredentialsInterceptor', CredentialsInterceptor)
.factory('NoHyperlinksInterceptor', NoHyperlinksInterceptor)
.component('xos', main)
- .run(function($log: ng.ILogService, $rootScope: ng.IRootScopeService, $transitions: any, StyleConfig: IXosStyleConfig) {
- $rootScope['favicon'] = `./app/images/brand/${StyleConfig.favicon}`;
- $transitions.onSuccess({ to: '**' }, (transtion) => {
- if (transtion.$to().name === 'login') {
- $rootScope['class'] = 'blank';
- }
- else {
- $rootScope['class'] = '';
- }
- });
+ .provider('XosConfig', function(){
+ // save the last visited state before reload
+ const lastVisitedUrl = window.location.hash.replace('#', '');
+ this.$get = [() => {
+ return {
+ lastVisitedUrl
+ };
+ }] ;
+ return this;
})
.run((
$rootScope: ng.IRootScopeService,
@@ -79,14 +78,27 @@
$log: ng.ILogService,
$location: ng.ILocationService,
$state: ng.ui.IStateService,
+ StyleConfig: IXosStyleConfig,
XosModelDiscoverer: IXosModelDiscovererService,
AuthService: IXosAuthService,
XosKeyboardShortcut: IXosKeyboardShortcutService,
- toastr: ng.toastr.IToastrService,
- PageTitle: IXosPageTitleService
+ PageTitle: IXosPageTitleService // NOTE this service is not used, but needs to be loaded somewhere
) => {
+ // handle style configs
+ $rootScope['favicon'] = `./app/images/brand/${StyleConfig.favicon}`;
+ if ($state.current.data && $state.current.data.specialClass) {
+ $rootScope['class'] = $state.current.data.specialClass;
+ }
+ $transitions.onSuccess({ to: '**' }, (transtion) => {
+ if ($state.current.data && $state.current.data.specialClass) {
+ $rootScope['class'] = transtion.$to().data.specialClass;
+ }
+ else {
+ $rootScope['class'] = '';
+ }
+ });
- // check the user login
+ // check the user login (on route change)
$transitions.onSuccess({ to: '**' }, (transtion) => {
if (!AuthService.isAuthenticated()) {
AuthService.clearUser();
@@ -94,40 +106,10 @@
}
});
- // preserve debug=true query string parameter
- $transitions.onStart({ to: '**' }, (transtion) => {
- // save location.search so we can add it back after transition is done
- this.locationSearch = $location.search();
- });
-
- $transitions.onSuccess({ to: '**' }, (transtion) => {
- // restore all query string parameters back to $location.search
- if (angular.isDefined(this.locationSearch.debug) && this.locationSearch.debug) {
- $location.search({debug: 'true'});
- }
- });
-
- // save the last visited state before reload
- const lastRoute = $location.path();
- const lastQueryString = $location.search();
-
// if the user is authenticated
$log.info(`[XOS] Is user authenticated? ${AuthService.isAuthenticated()}`);
if (AuthService.isAuthenticated()) {
- XosModelDiscoverer.discover()
- .then((res) => {
- if (res) {
- $log.info('[XOS] All models loaded');
- }
- else {
- $log.info('[XOS] Failed to load some models, moving on.');
- }
- // after setting up dynamic routes, redirect to previous state
- $location.path(lastRoute).search(lastQueryString);
- })
- .finally(() => {
- $rootScope.$emit('xos.core.modelSetup');
- });
+ $state.go('loader');
}
else {
AuthService.clearUser();
@@ -139,7 +121,6 @@
XosKeyboardShortcut.registerKeyBinding({
key: 'D',
- // modifiers: ['Command'],
cb: () => {
if (window.localStorage.getItem('debug') === 'true') {
$log.info(`[XosKeyboardShortcut] Disabling debug`);
diff --git a/src/routes.ts b/src/routes.ts
index 8685ce7..28a5906 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -5,18 +5,25 @@
/** @ngInject */
function routesConfig($stateProvider: angular.ui.IStateProvider, $urlRouterProvider: angular.ui.IUrlRouterProvider, $locationProvider: angular.ILocationProvider) {
$locationProvider.html5Mode(false).hashPrefix('');
- $urlRouterProvider.otherwise('/');
+ $urlRouterProvider.otherwise('/loader');
// declare here static endpoints,
// core related endpoints are dynamically generated
$stateProvider
+ .state('loader', {
+ url: '/loader',
+ component: 'xosLoader',
+ data: {
+ specialClass: 'blank'
+ }
+ })
.state('xos', {
abstract: true,
url: '/',
component: 'xos'
})
.state('xos.dashboard', {
- url: '',
+ url: 'dashboard',
parent: 'xos',
template: '<xos-dashboard></xos-dashboard>'
})