[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}`);