Merge "[CORD-2188] Multi-stage build for xos-gui"
diff --git a/docs/developer/gui_extensions.md b/docs/developer/gui_extensions.md
index b299283..52bf7f7 100644
--- a/docs/developer/gui_extensions.md
+++ b/docs/developer/gui_extensions.md
@@ -1,23 +1,74 @@
-# Create a custom GUI Extension
+# Creating a custom GUI Extension
  
 The CORD GUI is designed to be extensible. There are two cases in which we envision an extension to be needed:
 - Provide a different view over data
 - Create custom interfaces for services
  
-The development process for the two cases it's absolutely the same.
+The development process for both is the same.
   
-We suggest to get started by duplicating [xos-sample-gui-extension](https://github.com/opencord/xos-sample-gui-extension)
-  
-## Development
+## Starting Development 
+
+### Option 1: Use the provided Yeoman xos-gui-extension generator
+
+The provided generator in `generator-xos-gui-extension` will generate a new GUI extension for you with the name of
+your choice and demo components based on a simplified `sample-gui-extension`. No refactoring necessary.
+
+#### Prerequisites
+You must have the Yeoman toolset installed via npm on your system. It can be installed by running `npm install --global yo`. 
+If you encounter any issues, full detailed instructions on installation can be found at the [Yeoman website](http://yeoman.io/codelab/setup.html).
+
+#### Installation
+Once you have successfully installed Yeoman, run the following to install the generator.
+```bash
+cd ~cord/orchestration/xos-gui/generator-xos-gui-extension
+npm link
+```
+To run the generator, simply run `yo xos-gui-extension` from whatever location in your file system you wish to place your
+new GUI extension. The extension will prompt for a name for your extension. 
+
+
+### Option 2: Copy over sample-gui-extension
+If you choose not to use the Yeoman generator, you can copy over the contents of `sample-gui-extension` to your desired
+destination in your file system. If you are creating a GUI extension to used with a service, we suggest creating the 
+extension in a folder named `gui` in the service's `xos` folder as follows: `orchestration/xos_services/service_name/xos/gui/`.
+When changing the name of `sample-gui-extension`, you must be wary to change all instances of `sample-gui-extension` in the 
+extension folder.
+
  
-The development environment is the same as `xos-gui` so you can use the `npm start` command and augment it with the same sets fo environment variables.
-The dev server is going to proxy your requests to the appropriate backend and load the base application from it.
+## Adding an extension to the build system
  
-## Add an extension to a profile
+To deploy your GUI extension with a cord profile you'll need to reference it in `platform-install` and `build`.
+The following steps must be followed to ensure that your GUI extension builds correctly with XOS.
+
+### Adding the extension to `docker_images.yml`
+
+Open `cord/build/docker_images.yml`. Locate the section of the file with other GUI extensions, and add the following:
+
+```yaml
+- name: xosproject/gui-extension-sample
+    repo: sampleRepo # should match Gerrit repo name
+    path: "xos/gui" # path to find extension in the repo (i.e. sampleRepo/xos/gui/)
+```
+Please maintain ascending alphabetical order among the GUI extensions when inserting your own extension.
+
+### Adding the extension to the podconfig scenario
+
+Open the `config.yml` file of the podconfig scenario relevant to your GUI extension (e.g. cord, mock, local). 
+Locate the section titled `docker_image_whitelist` and add your GUI extension.
+
+```yaml
+docker_image_whitelist:
+# ...other docker images...
+  - "xosproject/gui-extension-rcord"
+  - "xosproject/gui-extension-sample" # extension added in alphabetical order
+  - "xosproject/gui-extension-vtr"
+# ...more docker images...
+
+```
+
+### Adding the extension to the podconfig profile manifest
  
-To deploy your GUI extension with a cord profile you'll need to reference it in `platform-install`.
- 
-Open the `profile-manifest` you're working on (eg: profile_manifests/frontend.yml) and locate `enabled_gui_extensions`.
+Open the `profile-manifest` relevant to the podconfig you're working on (eg: profile_manifests/frontend.yml) and locate `enabled_gui_extensions`.
 It may appear in two forms, depending whether or not there are others loaded extensions:
 ```yaml
 enabled_gui_extensions:
@@ -28,7 +79,9 @@
 ```yaml
 enabled_gui_extensions: []
 ```
-_NOTE: if it is not there, just create it_
+_NOTE: if it is not there, just create it._
+
+### Change conf export settings to match extension name
 
 To add your extension, just add it to the list:
 ```yaml
@@ -40,7 +93,7 @@
 ```
 _NOTE: if it was defined as an empty array you'll need to remove the square brackets (`[]`)_
 
-The `name` field must match the directory in which the GUI Extension is built. You can update it in `conf/gulp.conf.js`, just locate:
+You must make sure that the `name` field matches the directory in which the GUI Extension is built. You can update it in `conf/gulp.conf.js`.
 ```js
 exports.paths = {
   src: 'src',
@@ -51,6 +104,97 @@
   tasks: 'gulp_tasks'
 };
 ```
-and replace `sample` with the appropriate name.
+and replace `sample` with your appropriate name. If you used the Yeoman generator, `sample` will already have been 
+replaced with the GUI extension name you chose.
 
-The `path` field identify the directory (starting from the `repo` root), in which your extension is stored. As now is not supported the loading from external sources. 
\ No newline at end of file
+The `path` field identifies the directory (starting from the CORD `repo` root), in which your extension is stored.
+Loading from external sources is not currently supported.
+
+## Additional Tips
+
+### Including Extra Files
+
+Additional necessary files (such as stylesheets or config files) can be added to the profile manifest as follows, 
+with the extension's `src` folder as the root. Here, we use `xos-sample-gui-extension` as an example.
+
+```yaml
+enabled_gui_extensions:
+  - name: sample
+    path: orchestration/xos-sample-gui-extension
+    extra_files:
+      -  app/style/style.css
+```
+
+### Generating config files
+
+During development, you may find it necessary to create separate config files in order to include other files used in
+your extension (such as images). The path to your extension may vary depending on whether you are running it locally 
+(`./xos/extensions/extension-name`) vs. on a container in production (`./extensions/extension-name`).
+
+You can create separate `customconfig.local.js` and `customconfig.production.js` files in the `conf/` folder, and then edit the 
+following portion of the appropriate `webpack.conf.js` file as follows:
+
+```js
+new CopyWebpackPlugin([
+      { from: `./conf/app/app.config.${env}.js`, to: `app.config.js` },
+      { from: `./conf/app/style.config.${brand}.js`, to: `style.config.js` },
+      // add your file here
+      { from: `./conf/app/customconfig.local.js`, to: `customconfig.js`}
+    ])
+```
+
+`webpack.conf.js` will be used in a local development environment, such as when running `npm start`
+
+`webpack-dist.conf.js` will be used in a production container after deploying a profile.
+
+### Handy XOS Components and Services
+
+The following XOS components and services may be helpful to you in your GUI extension development.
+
+#### XosComponentInjector
+Allows for the injection of components into the XOS GUI by specifying a target element ID. Useful IDs include:
+* `#dashboard-component-container`: the dashboard as seen on the XOS home
+* `#side-panel-container`: a side panel that can slide out from the right. However, there is also a `XosSidePanel` 
+service that can make development easier.
+
+#### XosConfirm
+Allows for the creation of confirmation modal dialogs to confirm whether or not to execute a selected action.
+
+#### XosKeyboardShortcut
+Allows for the creation of custom user keyboard shortcuts. See the provided `components/demo.ts` as an example.
+
+#### XosModelStore
+Provides easy access to model ngResources provided by an XOS service. Can be used as follows in your component's
+associated TypeScript file:
+
+```typescript
+import {Subscription} from 'rxjs/Subscription';
+export class ExampleComponent {
+    static $inject = ['XosModelStore'];
+    public resource;
+    private modelSubscription : Subscription;
+    constructor(
+      private XosModelStore: any,
+    ){}
+    
+    $onInit() {
+        this.modelSubscription = this.XosModelStore.query('SampleModel', '/sampleservice/SampleModels').subscribe(
+          res => {
+            this.resource = res;
+          }
+        );
+    }
+}
+export const exampleComponent : angular.IComponentOptions = {
+  template: require('./example.component.html'),
+  controllerAs: 'vm',
+  controller: ExampleComponent
+}
+```
+
+#### XosNavigationService
+Used to create custom navigation links in the left navigation panel.
+
+#### XosSidePanel
+Makes the injection of a custom side panel somewhat easier (no need to specify a target)
+
diff --git a/docs/developer/quickstart.md b/docs/developer/quickstart.md
index c0b05a0..a27c800 100644
--- a/docs/developer/quickstart.md
+++ b/docs/developer/quickstart.md
@@ -1,41 +1,42 @@
 # GUI Quickstart
 
-_We assume that you already have the code downloaded with `repo`_
+_We assume that you already have the CORD source downloaded with `repo`_
 
-## Setting up the `frontend` configuration
+## Setting up a podconfig
 
-The easiest to work on the XOS GUI is to create a local deployment of the `frontend` configuration of CORD.
+For front-end development, using a podconfig with your choice of CORD flavors and  the `mock` or `local` scenario is sufficient.
+`mock` will create a vagrant VM running all the necessary containers, while `local` directly creates the containers on your machine, 
+so we recommend using `mock` when developing on your own laptop/desktop.
 
-You can find detailed instructions in the `platform-install` documentation, but for all that we need you can: 
+Assuming you have already downloaded and ran the `cord-bootstrap.sh` script, setup is easy:
+
+```bash
+cd ~/cord/build/
+make PODCONFIG={CORD_FLAVOR}-mock.yml config
+make build |& tee ~/build.out
+```  
+
+Further details on the mock configuration and its setup can be found at [Mock Configuration Workflow](/xos/dev/workflow_mock_single.md)
+
+### Login credentials
+After the mock profile finishes building, you can find the credentials necessary to login into XOS by running the following:
 
 ```
-cd opencord/build/platform-install
-vagrant up head-node
-vagrant ssh
-// you're now inside the vagrant VM
-cd cord/buid/platform-install
-ansible-playbook -i inventory/frontend deploy-xos-playbook.yml
-```
-
-
-> This commands will spin up a local vm and deploy the minimal configuration of CORD in it. <br/>
-> _NOTE the first you'll execute this commands they may take a long time (~20 min)_
-
-#### Retrieve the password to access XOS
-```
-cat ~/cord/buid/platform-install/credentials/xosadmin@opencord.org
+cat ~/cord/build/platform-install/credentials/xosadmin@opencord.org
 // save the output somewhere
 ```
 
 ## Serving the GUI in development mode
 
-Once your basic CORD config is up and running you should be able to access the GUI at `http://192.168.46.100/xos/`
-but that's not your development copy, it is the one deployed inside a container in XOS.
+Once your basic CORD config is up and running you should be able to access the GUI at `http://192.168.46.100/xos/`.
+
+NOTE: This is not your development copy, it is the one deployed inside a container in XOS and will not change until
+the container is torn down and redeployed.
 
 To launch a development copy:
 ```
 // back to your local system
-cd opencord/orchestration/xos-gui
+cd cord/orchestration/xos-gui
 npm install
 npm start
 ```
@@ -45,9 +46,9 @@
 **Now you're ready to start working on it!**
 
 To get start, login using username `xosadmin@opencord.org` and the password you previously saved,
-then pick any file and make a change, you'll see the GUI reload.
+then pick any file and make a change, and you'll see the GUI reload.
 
-### Configuring the `dev` GUI
+## Configuring the `dev` GUI
 
 There are two configuration file available in the application, and they depend on the environment. You can find the various possibilities in `conf/app`, and they regard application constants, such as `apiEndpoint`, or branding elements, such as `projectName`.
 
@@ -55,6 +56,8 @@
 - `PROXY`: you can use this variable to send request to an arbitrary XOS installation (eg: `clnode022.clemson.cloudlab.us:8080`)
 - `BRAND`: to configure style constants (eg: `cord`, `opencloud`)
 
+## Working with an existing XOS installation
+
 You can also specify a different installation of XOS as backend by using the `PROXY` environment variable.
-For example you can connect to a remote CORD-in-a-box installation for debugging purposes with:
+For example, you can connect to a remote CORD-in-a-box installation for debugging purposes with:
 `PROXY=ms1106.utah.cloudlab.us:8080 npm start`
diff --git a/generator-xos-gui-extension/README.md b/generator-xos-gui-extension/README.md
new file mode 100644
index 0000000..fb671d7
--- /dev/null
+++ b/generator-xos-gui-extension/README.md
@@ -0,0 +1,17 @@
+#generator-xos-gui-extension
+
+## About
+This is a Yeoman generator intended to aid with the development of new XOS GUI Extensions. It generates a new GUI extension
+with only one component. 
+
+## Installation
+Run the following command in this directory:
+```bash
+npm link
+```
+
+## Running the generator
+Run the following command in any directory and follow the prompts.
+```bash
+yo xos-gui-extension
+```  
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/README.md b/generator-xos-gui-extension/generators/README.md
deleted file mode 100644
index c13ead2..0000000
--- a/generator-xos-gui-extension/generators/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-#generator-xos-gui-extension
-
-## About
-This is a Yeoman generator intended to aid with the development of new XOS GUI Extensions. It generates a new GUI extension
-with only one component. 
-
-## Installation
-Run the following command in this directory and follow the prompts.
-```
-yo xos-gui-extension
-```  
\ No newline at end of file
diff --git a/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js b/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js
index c870043..5f924fb 100755
--- a/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js
+++ b/generator-xos-gui-extension/generators/templates/conf/webpack-dist.conf.js
@@ -105,7 +105,10 @@
       '.web.js',
       '.js',
       '.ts'
-    ]
+    ],
+    alias: {
+      "ngprogress": path.resolve(__dirname, '../node_modules/ngprogress/build/ngProgress.js')
+    }
   },
   entry: {
     app: `./${conf.path.src('index')}`,
diff --git a/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js b/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js
index c0115ab..04f9e4c 100755
--- a/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js
+++ b/generator-xos-gui-extension/generators/templates/conf/webpack-test.conf.js
@@ -73,7 +73,10 @@
       '.web.js',
       '.js',
       '.ts'
-    ]
+    ],
+    alias: {
+      "ngprogress": path.resolve(__dirname, '../node_modules/ngprogress/build/ngProgress.js')
+    }
   },
   ts: {
     configFileName: 'tsconfig.json'
diff --git a/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js b/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js
index d70e771..f0dbc51 100755
--- a/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js
+++ b/generator-xos-gui-extension/generators/templates/conf/webpack.conf.js
@@ -99,7 +99,10 @@
       '.web.js',
       '.js',
       '.ts'
-    ]
+    ],
+    alias: {
+      "ngprogress": path.resolve(__dirname, '../node_modules/ngprogress/build/ngProgress.js')
+    }
   },
   entry: `./${conf.path.src('index')}`,
   ts: {
diff --git a/generator-xos-gui-extension/generators/templates/package.json b/generator-xos-gui-extension/generators/templates/package.json
index 32a0676..b56f74e 100644
--- a/generator-xos-gui-extension/generators/templates/package.json
+++ b/generator-xos-gui-extension/generators/templates/package.json
@@ -1,5 +1,5 @@
 {
-  "version": "3.0.0",
+  "version": "4.0.0",
   "dependencies": {
     "angular": "1.6.3",
     "angular-animate": "1.6.3",
diff --git a/package.json b/package.json
index cb93442..d9bd9b9 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,8 @@
   "dependencies": {
     "angular": "1.6.3",
     "angular-animate": "1.6.3",
+    "angular-base64": "^2.0.5",
+    "angular-chart.js": "^1.1.1",
     "angular-cookies": "1.6.3",
     "angular-resource": "1.6.3",
     "angular-toastr": "2.1.1",
@@ -15,6 +17,7 @@
     "lodash": "4.17.4",
     "ngprogress": "1.1.1",
     "oclazyload": "1.1.0",
+    "moment": "^2.19.0",
     "pluralize": "3.1.0",
     "rxjs": "5.2.0",
     "socket.io-client": "1.7.3"
diff --git a/src/app/core/debug/debug-model.spec.ts b/src/app/core/debug/debug-model.spec.ts
index 46b6344..b081de0 100644
--- a/src/app/core/debug/debug-model.spec.ts
+++ b/src/app/core/debug/debug-model.spec.ts
@@ -91,7 +91,7 @@
 
       dateFields.forEach(f => {
         const date = isolatedScope.parseField(f, model[f]);
-        expect(date).toEqual('Thu Aug 17 2017 15:45:20 GMT-0700 (PDT)');
+        expect(date).toEqual(new Date(model[f] * 1000).toString());
       });
     });
 
diff --git a/src/app/core/header/header.html b/src/app/core/header/header.html
index 5249a3f..56cf7f1 100644
--- a/src/app/core/header/header.html
+++ b/src/app/core/header/header.html
@@ -52,20 +52,7 @@
           typeahead-template-url="customTemplate.html"
           typeahead-on-select="vm.routeSelected($item, $model, $label)">
       </form>
-      <ul class="nav navbar-nav navbar-right">
-        <!--<li class="dropdown">-->
-          <!--<a ui-sref="main.versions">-->
-            <!--<i class="fa fa-bell"></i>-->
-            <!--<span ng-if="vm.newNotifications.length > 0" class="label label-warning pull-right">{{vm.newNotifications.length}}</span>-->
-          <!--</a>-->
-        <!--</li>-->
-        <li class=" profil-link">
-          <a ui-sref="commonviews.login">
-            <span class="profile-address">{{vm.userEmail}}</span>
-            <img src="../../images/profile.jpg" class="img-circle" alt="">
-          </a>
-        </li>
-      </ul>
     </div>
   </div>
-</nav>
\ No newline at end of file
+</nav>
+
diff --git a/src/app/core/header/header.spec.ts b/src/app/core/header/header.spec.ts
index a4a7d90..3437b99 100644
--- a/src/app/core/header/header.spec.ts
+++ b/src/app/core/header/header.spec.ts
@@ -143,10 +143,6 @@
     // }, 'global');
   });
 
-  it('should print user email', () => {
-    expect($('.profile-address', element).text()).toBe('test@xos.us');
-  });
-
   it('should configure toastr', () => {
     delete MockToastrConfig['onTap'];
 
diff --git a/src/app/core/nav/nav.scss b/src/app/core/nav/nav.scss
index 4897dec..55725b8 100644
--- a/src/app/core/nav/nav.scss
+++ b/src/app/core/nav/nav.scss
@@ -51,4 +51,4 @@
       margin-top: 20px;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/app/core/services/navigation.spec.ts b/src/app/core/services/navigation.spec.ts
index bc27772..b9cb84c 100644
--- a/src/app/core/services/navigation.spec.ts
+++ b/src/app/core/services/navigation.spec.ts
@@ -116,7 +116,7 @@
     service.add(testRoute);
     service.add(testRoute);
     expect($log.warn).toHaveBeenCalled();
-    expect($log.warn).toHaveBeenCalledWith(`[XosNavigation] Route with label: ${testRoute.label}, state: ${testRoute.state} and parent: ${testRoute.parent} already exist`);
+    expect($log.warn).toHaveBeenCalledWith(`[XosNavigation] Route with label: ${testRoute.label}, state: ${testRoute.state} and parent: ${testRoute.parent} already exists`);
     expect(service.query()).toEqual(defaultRoutes.concat([testRoute]));
   });
 });
diff --git a/src/app/core/services/navigation.ts b/src/app/core/services/navigation.ts
index e7fa4ed..660c144 100644
--- a/src/app/core/services/navigation.ts
+++ b/src/app/core/services/navigation.ts
@@ -71,24 +71,27 @@
     return this.routes;
   }
 
-  add(route: IXosNavigationRoute) {
+  add(route: IXosNavigationRoute, override: boolean = false) {
     if (angular.isDefined(route.state) && angular.isDefined(route.url)) {
       throw new Error('[XosNavigation] You can\'t provide both state and url');
     }
 
     // NOTE factor this out in a separate method an eventually use recursion since we can nest more routes
+    let preExisting = null;
     const routeExist = _.findIndex(this.routes, (r: IXosNavigationRoute) => {
-      if (r.label === route.label && r.state === route.state && r.parent === route.parent) {
+      if (r.label === route.label && (r.state === route.state || override) && r.parent === route.parent) {
+        preExisting = r;
         return true;
       }
       else if (_.findIndex(r.children, route) > -1) {
+        preExisting = r;
         return true;
       }
       return false;
     }) > -1;
 
-    if (routeExist) {
-      this.$log.warn(`[XosNavigation] Route with label: ${route.label}, state: ${route.state} and parent: ${route.parent} already exist`);
+    if (routeExist && !override) {
+      this.$log.warn(`[XosNavigation] Route with label: ${route.label}, state: ${route.state} and parent: ${route.parent} already exists`);
       return;
     }
 
@@ -97,6 +100,9 @@
       const parentRoute = _.find(this.routes, {state: route.parent});
       if (angular.isDefined(parentRoute)) {
         if (angular.isArray(parentRoute.children)) {
+          if (override) {
+            _.remove(parentRoute.children, r => r === preExisting);
+          }
           parentRoute.children.push(route);
         }
         else {
@@ -104,11 +110,14 @@
         }
       }
       else {
-        this.$log.warn(`[XosNavigation] Parent State (${route.parent}) for state: ${route.state} does not exists`);
+        this.$log.warn(`[XosNavigation] Parent State (${route.parent}) for state: ${route.state} does not exist`);
         return;
       }
     }
     else {
+      if (override) {
+        _.remove(this.routes, r => r === preExisting);
+      }
       this.routes.push(route);
     }
   }
diff --git a/src/app/datasources/helpers/store.helpers.spec.ts b/src/app/datasources/helpers/store.helpers.spec.ts
index f3b2321..2f617bc 100644
--- a/src/app/datasources/helpers/store.helpers.spec.ts
+++ b/src/app/datasources/helpers/store.helpers.spec.ts
@@ -26,6 +26,7 @@
 import {ConfigHelpers} from '../../core/services/helpers/config.helpers';
 import {AuthService} from '../rest/auth.rest';
 import {IXosModeldefsCache} from './modeldefs.service';
+import {XosFormHelpers} from '../../core/form/form-helpers';
 
 let service: IStoreHelpersService;
 let subject: BehaviorSubject<any>;
@@ -42,6 +43,7 @@
       .service('ModelRest', ModelRest) // NOTE evaluate mock
       .service('StoreHelpers', StoreHelpers)
       .service('AuthService', AuthService)
+      .service('XosFormHelpers', XosFormHelpers)
       .value('XosModeldefsCache', {
         get: jasmine.createSpy('XosModeldefsCache.get'),
         getApiUrlFromModel: jasmine.createSpy('XosModeldefsCache.getApiUrlFromModel')
diff --git a/src/app/datasources/rest/model.rest.spec.ts b/src/app/datasources/rest/model.rest.spec.ts
index d545ac2..da4f2f4 100644
--- a/src/app/datasources/rest/model.rest.spec.ts
+++ b/src/app/datasources/rest/model.rest.spec.ts
@@ -22,6 +22,7 @@
 import 'angular-cookies';
 import {IXosResourceService} from './model.rest';
 import {xosDataSources} from '../index';
+import {IXosFormHelpersService} from '../../core/form/form-helpers';
 
 let service: IXosResourceService;
 let resource: ng.resource.IResourceClass<any>;
@@ -34,6 +35,10 @@
   websocketClient: 'http://xos-test:3000'
 };
 
+const MockFormHelpers: IXosFormHelpersService = {
+  _getFieldFormat: () => 'date'
+};
+
 describe('The ModelRest service', () => {
 
   beforeEach(angular.mock.module(xosDataSources));
@@ -41,7 +46,8 @@
   beforeEach(() => {
 
     angular.module(xosDataSources)
-      .constant('AppConfig', MockAppCfg);
+      .constant('AppConfig', MockAppCfg)
+      .value('XosFormHelpers', MockFormHelpers);
 
     angular.mock.module(xosDataSources);
   });
@@ -99,4 +105,31 @@
     $scope.$apply();
     httpBackend.flush();
   });
+
+  describe('when saving a model', () => {
+
+    let item, date;
+    const timestamp = 1509552402000;
+
+    beforeEach(() => {
+      httpBackend.expectPOST(`${MockAppCfg.apiEndpoint}/core/test`)
+        .respond((method, url, req) => {
+          return [200, req];
+        });
+      resource = service.getResource('/core/test');
+      date = new Date(timestamp);
+      item = new resource({date: date.toString()});
+    });
+
+    xit('should convert dates to timestamps', (done) => {
+      item.$save()
+        .then(res => {
+          expect(res.date).toEqual(timestamp);
+          done();
+        });
+      $scope.$apply();
+      httpBackend.flush();
+      done();
+    });
+  });
 });
diff --git a/src/app/datasources/rest/model.rest.ts b/src/app/datasources/rest/model.rest.ts
index 6c72d99..f390616 100644
--- a/src/app/datasources/rest/model.rest.ts
+++ b/src/app/datasources/rest/model.rest.ts
@@ -15,37 +15,48 @@
  * limitations under the License.
  */
 
-
+import * as _ from 'lodash';
 import {IXosAppConfig} from '../../../index';
+import {IXosFormHelpersService} from '../../core/form/form-helpers';
+
 export interface IXosResourceService {
   getResource(url: string): ng.resource.IResourceClass<any>;
 }
 
 export class ModelRest implements IXosResourceService {
-  static $inject = ['$resource', 'AppConfig'];
+  static $inject = ['$resource', 'AppConfig', 'XosFormHelpers'];
 
   /** @ngInject */
   constructor(
     private $resource: ng.resource.IResourceService,
-    private AppConfig: IXosAppConfig
+    private AppConfig: IXosAppConfig,
+    private XosFormHelpers: IXosFormHelpersService
   ) {
 
   }
 
   public getResource(url: string): ng.resource.IResourceClass<ng.resource.IResource<any>> {
+    const self = this;
     const resource: angular.resource.IResourceClass<any> = this.$resource(`${this.AppConfig.apiEndpoint}${url}/:id/`, {id: '@id'}, {
       update: { method: 'PUT' },
       query: {
         method: 'GET',
         isArray: true,
         transformResponse: (res) => {
-          // FIXME chameleon return everything inside "items"
           return res.items ? res.items : res;
         }
       }
     });
 
     resource.prototype.$save = function() {
+
+      // NOTE converting dates back to timestamp
+      _.forEach(Object.keys(this), (k: string) => {
+        if (self.XosFormHelpers._getFieldFormat(this[k]) === 'date') {
+          this[k] = new Date(this[k]).getTime();
+        }
+      });
+
       if (this.id) {
         return this.$update();
       } else {
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index 09dc3f9..ce3e2cd 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -28,6 +28,7 @@
 import {AuthService} from '../rest/auth.rest';
 import {XosDebouncer} from '../../core/services/helpers/debounce.helper';
 import {IXosModeldefsCache} from '../helpers/modeldefs.service';
+import {XosFormHelpers} from '../../core/form/form-helpers';
 
 let service: IXosModelStoreService;
 let httpBackend: ng.IHttpBackendService;
@@ -70,6 +71,7 @@
       .service('XosModelStore', XosModelStore)
       .service('ConfigHelpers', ConfigHelpers) // TODO mock
       .service('AuthService', AuthService)
+      .service('XosFormHelpers', XosFormHelpers)
       .constant('AppConfig', MockAppCfg)
       .value('XosModeldefsCache', {
         get: jasmine.createSpy('XosModeldefsCache.get').and.returnValue({}),