Merge "[CORD-1001] Solved race condition in autogeneration of models and tweaks for slow connections"
diff --git a/conf/proxy.js b/conf/proxy.js
index daec1a8..6a7e8b0 100644
--- a/conf/proxy.js
+++ b/conf/proxy.js
@@ -1,7 +1,7 @@
 const httpProxy = require('http-proxy');
 
 const proxy = httpProxy.createProxyServer({
-  target: 'http://xos.dev:9101'
+  target: 'http://192.168.46.100:9101'
 });
 
 proxy.on('error', function(error, req, res) {
diff --git a/conf/webpack-dist.conf.js b/conf/webpack-dist.conf.js
index d0300a6..924f2de 100644
--- a/conf/webpack-dist.conf.js
+++ b/conf/webpack-dist.conf.js
@@ -91,7 +91,8 @@
   },
   entry: {
     app: `./${conf.path.src('index')}`,
-    vendor: Object.keys(pkg.dependencies)
+    vendor: Object.keys(pkg.dependencies),
+    loader: `./${conf.path.src('/app/style/imports/loader.scss')}`
   },
   ts: {
     configFileName: 'tsconfig.json'
diff --git a/conf/webpack.conf.js b/conf/webpack.conf.js
index 8bef911..5506296 100644
--- a/conf/webpack.conf.js
+++ b/conf/webpack.conf.js
@@ -5,6 +5,7 @@
 const HtmlWebpackPlugin = require('html-webpack-plugin');
 const autoprefixer = require('autoprefixer');
 const CopyWebpackPlugin = require('copy-webpack-plugin');
+const ExtractTextPlugin = require('extract-text-webpack-plugin');
 const env = process.env.NODE_ENV || 'production';
 const brand = process.env.BRAND || 'cord';
 
@@ -27,13 +28,15 @@
       },
       {
         test: /\.(css|scss)$/,
-        loaders: [
-          'style',
-          'css',
-          'resolve-url-loader',
-          'sass?sourceMap',
-          'postcss'
-        ]
+        loaders: ExtractTextPlugin.extract({
+          fallbackLoader: 'style',
+          loader: [
+            'css',
+            'resolve-url-loader',
+            'sass?sourceMap',
+            'postcss'
+          ]
+        })
       },
       {
         test: /\.ts$/,
@@ -66,14 +69,15 @@
     new webpack.NoErrorsPlugin(),
     new HtmlWebpackPlugin({
       template: conf.path.src('index.html')
-    })
+    }),
+    new ExtractTextPlugin('index-[contenthash].css'),
   ],
   postcss: () => [autoprefixer],
   debug: true,
   devtool: 'source-map',
   output: {
     path: path.join(process.cwd(), conf.paths.tmp),
-    filename: 'index.js'
+    filename: '[name].js'
   },
   resolve: {
     extensions: [
@@ -84,7 +88,10 @@
       '.ts'
     ]
   },
-  entry: `./${conf.path.src('index')}`,
+  entry: {
+    indes: `./${conf.path.src('index')}`,
+    loader: `./${conf.path.src('/app/style/imports/loader.scss')}`
+  },
   ts: {
     configFileName: 'tsconfig.json'
   },
diff --git a/package.json b/package.json
index e97f9a0..fa8fb46 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "bootstrap-sass": "^3.3.7",
     "jquery": "^3.1.1",
     "lodash": "^4.17.2",
+    "ngprogress": "^1.1.1",
     "oclazyload": "^1.0.9",
     "pluralize": "^3.1.0",
     "rxjs": "^5.0.1",
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
index da6f265..434f74e 100644
--- a/src/app/datasources/helpers/model-discoverer.service.ts
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -34,10 +34,12 @@
     'ConfigHelpers',
     'XosRuntimeStates',
     'XosNavigationService',
-    'XosModelStore'
+    'XosModelStore',
+    'ngProgressFactory'
   ];
   private xosModels: IXosModel[] = []; // list of augmented model definitions;
   private xosServices: string[] = []; // list of loaded services
+  private progressBar;
 
   constructor (
     private $log: ng.ILogService,
@@ -46,8 +48,11 @@
     private ConfigHelpers: IXosConfigHelpersService,
     private XosRuntimeStates: IXosRuntimeStatesService,
     private XosNavigationService: IXosNavigationService,
-    private XosModelStore: IXosModelStoreService
+    private XosModelStore: IXosModelStoreService,
+    private ngProgressFactory: any // check for type defs
   ) {
+    this.progressBar = this.ngProgressFactory.createInstance();
+    this.progressBar.setColor('#f6a821');
   }
 
   public get(modelName: string): IXosModel|null {
@@ -56,7 +61,7 @@
 
   public discover() {
     const d = this.$q.defer();
-
+    this.progressBar.start();
     this.XosModelDefs.get()
       .then((modelsDef: IXosModeldef[]) => {
 
@@ -81,23 +86,28 @@
             })
             .then(model => {
               this.$log.debug(`[XosModelDiscovererService] Model ${model.name} stored`);
-              return this.$q.resolve();
+              return this.$q.resolve('true');
             })
             .catch(err => {
               this.$log.error(`[XosModelDiscovererService] Model ${model.name} NOT stored`);
-              // NOTE why this does not resolve?????
-              // return this.$q.resolve();
-              return this.$q.reject();
+              return this.$q.resolve('false');
             });
             pArray.push(p);
         });
         this.$q.all(pArray)
-          .then(() => {
+          .then((res) => {
+            // the Model Loader promise won't ever be reject, in case it will be resolve with value false,
+            // that's because we want to wait anyway for all the models to be loaded
+            if (res.indexOf('false') > -1) {
+              d.resolve(false);
+            }
             d.resolve(true);
-            this.$log.info('[XosModelDiscovererService] All models loaded!');
           })
           .catch(() => {
             d.resolve(false);
+          })
+          .finally(() => {
+            this.progressBar.complete();
           });
       });
     return d.promise;
@@ -210,19 +220,19 @@
   private cacheModelEntries(model: IXosModel): ng.IPromise<IXosModel> {
     const d = this.$q.defer();
 
-    let called = false;
+    let populated = false;
     const apiUrl = this.getApiUrlFromModel(model);
     this.XosModelStore.query(model.name, apiUrl)
       .subscribe(
         () => {
           // skipping the first response as the observable gets created as an empty array
-          if (called) {
+          if (populated) {
             return d.resolve(model);
           }
-          called = true;
+          populated = true;
         },
         err => {
-          d.reject(err);
+          return d.reject(err);
         }
       );
 
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index 4958015..61eb114 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -24,10 +24,15 @@
 
   public query(modelName: string, apiUrl: string): Observable<any> {
     // if there isn't already an observable for that item
+    // create a new one and .next() is called by this.loadInitialData once data are received
     if (!this._collections[modelName]) {
       this._collections[modelName] = new BehaviorSubject([]); // NOTE maybe this can be created when we get response from the resource
       this.loadInitialData(modelName, apiUrl);
     }
+    // else manually trigger the next with the last know value to trigger the subscribe method of who's requestiong this data
+    else {
+      this._collections[modelName].next(this._collections[modelName].value);
+    }
 
     this.webSocket.list()
       .filter((e: IWSEvent) => e.model === modelName)
@@ -76,7 +81,7 @@
   }
 
   private loadInitialData(model: string, apiUrl?: string) {
-    // TODO provide alway the apiUrl togheter with the query() params
+    // TODO provide always the apiUrl togheter with the query() params
     if (!angular.isDefined(apiUrl)) {
       // NOTE check what is the correct pattern to pluralize this
       apiUrl = this.storeHelpers.urlFromCoreModel(model);
@@ -87,10 +92,8 @@
           this._collections[model].next(res);
         })
       .catch(
-        // TODO understand how to send an error to an observable
         err => {
           this._collections[model].error(err);
-          // this.$log.log(`Error retrieving ${model}`, err);
         }
       );
   }
diff --git a/src/app/style/imports/loader.scss b/src/app/style/imports/loader.scss
new file mode 100644
index 0000000..4c1a751
--- /dev/null
+++ b/src/app/style/imports/loader.scss
@@ -0,0 +1,45 @@
+@import '../vars';
+
+.loader,
+.loader:before,
+.loader:after {
+  background: $color-accent;
+  animation: loaderAnimation 1s infinite ease-in-out;
+  width: 1em;
+  height: 4em;
+}
+.loader {
+  color: $color-accent;
+  text-indent: -9999em;
+  margin: 88px auto;
+  position: relative;
+  font-size: 11px;
+  transform: translateZ(0);
+  animation-delay: -0.16s;
+}
+.loader:before,
+.loader:after {
+  position: absolute;
+  top: 0;
+  content: '';
+}
+.loader:before {
+  left: -1.5em;
+  animation-delay: -0.32s;
+}
+.loader:after {
+  left: 1.5em;
+}
+
+@keyframes loaderAnimation {
+  0%,
+  80%,
+  100% {
+    box-shadow: 0 0;
+    height: 4em;
+  }
+  40% {
+    box-shadow: 0 -2em;
+    height: 5em;
+  }
+}
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 2f5ee65..9ffc4c8 100644
--- a/src/index.html
+++ b/src/index.html
@@ -14,6 +14,11 @@
 
   <body class="{{class}}">
     <ui-view></ui-view>
+    <div ng-show="::false">
+      <div class="loader">
+        Loading
+      </div>
+    </div>
   </body>
   <script type="text/javascript" src="./app.config.js"></script>
   <script type="text/javascript" src="./style.config.js"></script>
diff --git a/src/index.scss b/src/index.scss
index 2d28146..08318a1 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -3,6 +3,7 @@
 @import './app/style/style.scss';
 @import './app/style/stroke-icons/style.css';
 @import './app/style/pe-icons/pe-icon-7-stroke.css';
+@import '../node_modules/ngprogress/ngProgress.css';
 
 html, body.blank {
   height: 100%;
@@ -16,6 +17,14 @@
 // BOOTSTRAP OVERRIDES
 // TODO Clean app/style/style.scss
 
+// ngProgress
+#ngProgress-container {
+  position: absolute;
+  width: 100%;
+  top: 0;
+  z-index: 10000;
+}
+
 // Alternate styles
 //
 // Generate contextual modifier classes for colorizing the alert.
diff --git a/src/index.ts b/src/index.ts
index de9d9bf..6c34b57 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,6 +6,7 @@
 import 'angular-ui-router';
 import 'angular-resource';
 import 'angular-cookies';
+import '../node_modules/ngprogress/build/ngProgress';
 import routesConfig from './routes';
 
 import {main} from './app/main';
@@ -48,9 +49,10 @@
     xosDataSources,
     xosViews,
     xosExtender,
+    xosTemplate, // template module
     'ui.router',
     'ngResource',
-    xosTemplate // template module
+    'ngProgress'
   ])
   .config(XosLogDecorator)
   .config(routesConfig)
@@ -121,6 +123,8 @@
 
           // after setting up dynamic routes, redirect to previous state
           $location.path(lastRoute).search(lastQueryString);
+        })
+        .finally(() => {
           $rootScope.$emit('xos.core.modelSetup');
         });
     }
diff --git a/src/interceptors.ts b/src/interceptors.ts
index 0a2bf74..a79765e 100644
--- a/src/interceptors.ts
+++ b/src/interceptors.ts
@@ -8,12 +8,16 @@
 
 export function userStatusInterceptor($state: angular.ui.IStateService, $cookies: ng.cookies.ICookiesService, $q: ng.IQService) {
   const checkLogin = (res) => {
-    if (res.status === 401 || res.status === -1) {
-      $cookies.remove('sessionid', {path: '/'});
-      $state.go('login');
-      return $q.reject(res);
+    switch (res.status) {
+      case -1:
+      case 401:
+      case 500:
+        $cookies.remove('sessionid', {path: '/'});
+        $state.go('login');
+        return $q.reject(res);
+      default:
+        return res;
     }
-    return res;
   };
 
   return {