CORD-772 Loading external app when a new XosComponent of that kind is created and injecting loaded components at boot

Change-Id: I4f70d3b1e48b63eab3b8f8d2ca3f5b049b468f4c
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index dce81b6..b8ff208 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -4,7 +4,7 @@
 import {IModeldef} from '../../../datasources/rest/modeldefs.rest';
 import {IXosFormConfig, IXosFormInput} from '../../form/form';
 import {IXosAuthService} from '../../../datasources/rest/auth.rest';
-import {IModelStoreService} from '../../../datasources/stores/model.store';
+import {IXosModelStoreService} from '../../../datasources/stores/model.store';
 import {IXosState} from '../../../../index';
 
 export interface IXosModelDefsField {
@@ -59,7 +59,7 @@
     private $state: ng.ui.IStateService,
     private toastr: ng.toastr.IToastrService,
     private AuthService: IXosAuthService,
-    private ModelStore: IModelStoreService
+    private ModelStore: IXosModelStoreService
   ) {
     pluralize.addIrregularRule('xos', 'xoses');
     pluralize.addPluralRule(/slice$/i, 'slices');
diff --git a/src/app/datasources/helpers/search.service.ts b/src/app/datasources/helpers/search.service.ts
index 8cd98a7..5565b2c 100644
--- a/src/app/datasources/helpers/search.service.ts
+++ b/src/app/datasources/helpers/search.service.ts
@@ -1,7 +1,7 @@
 import * as _ from 'lodash';
 import {IXosNavigationService} from '../../core/services/navigation';
 import {IXosState} from '../../../index';
-import {IModelStoreService} from '../stores/model.store';
+import {IXosModelStoreService} from '../stores/model.store';
 import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
 
 export interface IXosSearchResult {
@@ -21,7 +21,7 @@
   constructor (
     private $rootScope: ng.IScope,
     private NavigationService: IXosNavigationService,
-    private ModelStore: IModelStoreService,
+    private ModelStore: IXosModelStoreService,
     private ConfigHelpers: IXosConfigHelpersService
   ) {
     this.$rootScope.$on('xos.core.modelSetup', () => {
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index a73e5ff..7173658 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -1,7 +1,7 @@
 import * as angular from 'angular';
 import 'angular-mocks';
 import 'angular-resource';
-import {IModelStoreService, ModelStore} from './model.store';
+import {IXosModelStoreService, ModelStore} from './model.store';
 import {Subject} from 'rxjs';
 import {IWSEvent} from '../websocket/global';
 import {StoreHelpers} from '../helpers/store.helpers';
@@ -9,7 +9,7 @@
 import {ConfigHelpers} from '../../core/services/helpers/config.helpers';
 import {AuthService} from '../rest/auth.rest';
 
-let service: IModelStoreService;
+let service: IXosModelStoreService;
 let httpBackend: ng.IHttpBackendService;
 let $scope;
 let WebSocket;
@@ -55,7 +55,7 @@
   });
 
   beforeEach(angular.mock.inject((
-    ModelStore: IModelStoreService,
+    ModelStore: IXosModelStoreService,
     $httpBackend: ng.IHttpBackendService,
     _$rootScope_: ng.IRootScopeService,
     _WebSocket_: any
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index a070436..291e7c0 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -5,12 +5,12 @@
 import {IXosResourceService} from '../rest/model.rest';
 import {IStoreHelpersService} from '../helpers/store.helpers';
 
-export interface  IModelStoreService {
+export interface  IXosModelStoreService {
   query(model: string): Observable<any>;
   search(modelName: string): any[];
 }
 
-export class ModelStore {
+export class ModelStore implements IXosModelStoreService {
   static $inject = ['$log', 'WebSocket', 'StoreHelpers', 'ModelRest'];
   private _collections: any; // NOTE contains a map of {model: BehaviourSubject}
   constructor(
@@ -22,7 +22,7 @@
     this._collections = {};
   }
 
-  public query(model: string) {
+  public query(model: string): Observable<any> {
     // if there isn't already an observable for that item
     if (!this._collections[model]) {
       this._collections[model] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
diff --git a/src/app/extender/services/onboard.service.spec.ts b/src/app/extender/services/onboard.service.spec.ts
index f9373c9..375aadb 100644
--- a/src/app/extender/services/onboard.service.spec.ts
+++ b/src/app/extender/services/onboard.service.spec.ts
@@ -5,7 +5,7 @@
 import {XosOnboarder, IXosOnboarder} from './onboard.service';
 import {IWSEventService} from '../../datasources/websocket/global';
 
-let service, $ocLazyLoad;
+let service, $ocLazyLoad, $timeout;
 
 const subject = new Subject();
 
@@ -32,6 +32,16 @@
   }
 };
 
+const MockModelStore = {
+  query: () => {
+    return {
+      subscribe: () => {
+        return;
+      }
+    };
+  }
+};
+
 describe('The XosOnboarder service', () => {
 
   beforeEach(() => {
@@ -40,6 +50,7 @@
       .module('XosOnboarder', [])
       .value('WebSocket', MockWs)
       .value('$ocLazyLoad', MockLoad)
+      .value('ModelStore', MockModelStore)
       .service('XosOnboarder', XosOnboarder);
 
     angular.mock.module('XosOnboarder');
@@ -47,21 +58,28 @@
 
   beforeEach(angular.mock.inject((
     XosOnboarder: IXosOnboarder,
-    _$ocLazyLoad_: any
+    _$ocLazyLoad_: any,
+    _$timeout_: ng.ITimeoutService
   ) => {
     $ocLazyLoad = _$ocLazyLoad_;
     spyOn($ocLazyLoad, 'load').and.callThrough();
     service = XosOnboarder;
+    $timeout = _$timeout_;
   }));
 
   describe('when receive an event', () => {
     it('should use $ocLazyLoad to add modules to the app', () => {
       subject.next({
+        model: 'XOSComponent',
         msg: {
           app: 'sample',
-          files: ['vendor.js', 'app.js']
+          object: {
+            extra: '["vendor.js", "app.js"]',
+            name: 'sample app'
+          }
         }
       });
+      $timeout.flush();
       expect($ocLazyLoad.load).toHaveBeenCalledWith('vendor.js');
       expect($ocLazyLoad.load).toHaveBeenCalledWith('app.js');
     });
diff --git a/src/app/extender/services/onboard.service.ts b/src/app/extender/services/onboard.service.ts
index 70d830c..402557c 100644
--- a/src/app/extender/services/onboard.service.ts
+++ b/src/app/extender/services/onboard.service.ts
@@ -1,32 +1,75 @@
-import {IWSEventService} from '../../datasources/websocket/global';
+import {IWSEventService, IWSEvent} from '../../datasources/websocket/global';
+import {IXosModelStoreService} from '../../datasources/stores/model.store';
+import * as _ from 'lodash';
+import {Observable} from 'rxjs';
 
 export interface IXosOnboarder {
 
 }
 
 export class XosOnboarder implements IXosOnboarder {
-  static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad'];
+  static $inject = ['$timeout', '$log', '$q', 'WebSocket', '$ocLazyLoad', 'ModelStore'];
 
   constructor(
     private $timeout: ng.ITimeoutService,
     private $log: ng.ILogService,
     private $q: ng.IQService,
     private webSocket: IWSEventService,
-    private $ocLazyLoad: any // TODO add definition
+    private $ocLazyLoad: any, // TODO add definition
+    private ModelStore: IXosModelStoreService
   ) {
     this.$log.info('[XosOnboarder] Setup');
+
+    // Listen for new app (we need a pause to allow the container to boot)
     this.webSocket.list()
-      .filter((e) => {
-        this.$log.log(e);
-        // TODO define event format
-        return e.msg['files'].length > 0;
+      .filter((e: IWSEvent) => {
+        if (e.model === 'XOSComponent' && e.msg.object.extra) {
+          e.msg.object.extra = JSON.parse(e.msg.object.extra);
+          return true;
+        }
+        return false;
       })
       .subscribe(
         (event) => {
-          this.loadFile(event.msg['files'])
-            .then((res) => {
-              this.$log.info(`[XosOnboarder] All files loaded for app: ${event.msg['app']}`);
-            });
+          this.$timeout(() => {
+            this.$log.info(`[XosOnboarder] Loading files for app: ${event.msg.object.name}`);
+            // NOTE we need the timeout because the event is triggered when the model is created,
+            // XOS take around 15s to boot it
+            this.loadFile(event.msg.object.extra)
+              .then((res) => {
+                this.$log.info(`[XosOnboarder] All files loaded for app: ${event.msg.object.name}`);
+              });
+          }, 20 * 1000);
+        }
+      );
+
+    // Load previously onboarded app (containers are already running, so we don't need to wait)
+    let componentsLoaded = false;
+    const ComponentObservable: Observable<any> = this.ModelStore.query('XOSComponent');
+    ComponentObservable.subscribe(
+        (component) => {
+          if (componentsLoaded) {
+            // if we have already loaded the component present when we loaded the page
+            // do nothing, we are intercepting WS to give the container time to boot
+            return;
+          }
+
+          _.forEach(component, (c) => {
+            if (c.extra) {
+              this.$log.info(`[XosOnboarder] Loading files for app: ${c.name}`);
+              let extra;
+              try {
+                extra = JSON.parse(c.extra);
+              } catch (e) {
+                extra = c.extra;
+              }
+              this.loadFile(extra)
+                .then((res) => {
+                  this.$log.info(`[XosOnboarder] All files loaded for app: ${c.name}`);
+                });
+            }
+          });
+          componentsLoaded = true;
         }
       );
   }
diff --git a/src/app/views/crud/crud.ts b/src/app/views/crud/crud.ts
index 4c41104..846a840 100644
--- a/src/app/views/crud/crud.ts
+++ b/src/app/views/crud/crud.ts
@@ -1,5 +1,5 @@
 import {IXosTableCfg} from '../../core/table/table';
-import {IModelStoreService} from '../../datasources/stores/model.store';
+import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {IXosConfigHelpersService} from '../../core/services/helpers/config.helpers';
 import * as _ from 'lodash';
 import {IXosFormConfig} from '../../core/form/form';
@@ -30,7 +30,7 @@
     private $scope: angular.IScope,
     private $state: angular.ui.IStateService,
     private $stateParams: ng.ui.IStateParamsService,
-    private store: IModelStoreService,
+    private store: IXosModelStoreService,
     private ConfigHelpers: IXosConfigHelpersService,
     private ModelRest: IXosResourceService,
     private StoreHelpers: IStoreHelpersService
diff --git a/src/app/views/dashboard/dashboard.ts b/src/app/views/dashboard/dashboard.ts
index 72d7f28..00fc1b4 100644
--- a/src/app/views/dashboard/dashboard.ts
+++ b/src/app/views/dashboard/dashboard.ts
@@ -1,4 +1,4 @@
-import {IModelStoreService} from '../../datasources/stores/model.store';
+import {IXosModelStoreService} from '../../datasources/stores/model.store';
 import {IXosAuthService} from '../../datasources/rest/auth.rest';
 class DashboardController {
   static $inject = ['$scope', '$state', 'ModelStore', 'AuthService'];
@@ -10,7 +10,7 @@
   constructor(
     private $scope: ng.IScope,
     private $state: ng.ui.IStateService,
-    private store: IModelStoreService,
+    private store: IXosModelStoreService,
     private auth: IXosAuthService
   ) {
 
diff --git a/src/decorators.ts b/src/decorators.ts
index 1ff981b..6f2628d 100644
--- a/src/decorators.ts
+++ b/src/decorators.ts
@@ -21,7 +21,7 @@
         let now     = new Date();
 
         // Prepend timestamp
-        args[0] = `[${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}] ${args[0]}`;
+        args.unshift(`[${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}]`);
 
         // Call the original with the output prepended with formatted timestamp
         fn.apply(null, args);