[CORD-1338] Inline navigation for related models

Change-Id: I58ff4a4675d1ce1140fe162f1f8360f2dc9a6527
diff --git a/src/app/datasources/helpers/model-discoverer.service.ts b/src/app/datasources/helpers/model-discoverer.service.ts
index 1ef2904..37ecc93 100644
--- a/src/app/datasources/helpers/model-discoverer.service.ts
+++ b/src/app/datasources/helpers/model-discoverer.service.ts
@@ -186,11 +186,15 @@
         id: null
       },
       data: {
-        model: model.name
+        model: model.name,
       },
       component: 'xosCrud',
     };
 
+    if (angular.isDefined(model.relations)) {
+      state.data.relations = model.relations;
+    }
+
     try {
       this.XosRuntimeStates.addState(
         this.stateNameFromModel(model),
diff --git a/src/app/datasources/helpers/model.discoverer.service.spec.ts b/src/app/datasources/helpers/model.discoverer.service.spec.ts
index d5a3232..b72b311 100644
--- a/src/app/datasources/helpers/model.discoverer.service.spec.ts
+++ b/src/app/datasources/helpers/model.discoverer.service.spec.ts
@@ -190,6 +190,30 @@
     scope.$apply();
   });
 
+  it('should add a state with relations in the system', (done) => {
+    MockXosRuntimeStates.addState.calls.reset();
+    service['addState']({name: 'Tenant', app: 'services.vsg', relations: [{model: 'Something', type: 'manytoone'}]})
+      .then((model) => {
+        expect(MockXosRuntimeStates.addState).toHaveBeenCalledWith('xos.vsg.tenant', {
+          parent: 'xos.vsg',
+          url: '/tenants/:id?',
+          params: {
+            id: null
+          },
+          data: {
+            model: 'Tenant',
+            relations: [
+              {model: 'Something', type: 'manytoone'}
+            ]
+          },
+          component: 'xosCrud',
+        });
+        expect(model.clientUrl).toBe('vsg/tenants/:id?');
+        done();
+      });
+    scope.$apply();
+  });
+
   it('should add an item to navigation', () => {
     service['addNavItem']({name: 'Tenant', app: 'services.vsg'});
     expect(MockXosNavigationService.add).toHaveBeenCalledWith({
diff --git a/src/app/datasources/rest/modeldefs.rest.ts b/src/app/datasources/rest/modeldefs.rest.ts
index fdb4b99..0af00ff 100644
--- a/src/app/datasources/rest/modeldefs.rest.ts
+++ b/src/app/datasources/rest/modeldefs.rest.ts
@@ -16,6 +16,7 @@
 export interface IXosModelDefsRelation {
   model: string; // model name
   type: string; // relation type
+  on_field: string; // the field that is containing the relation
 }
 
 export interface IXosModeldef {
diff --git a/src/app/datasources/stores/model.store.spec.ts b/src/app/datasources/stores/model.store.spec.ts
index b114de4..846ef84 100644
--- a/src/app/datasources/stores/model.store.spec.ts
+++ b/src/app/datasources/stores/model.store.spec.ts
@@ -31,7 +31,7 @@
 
 const queryData = [
   {id: 1, name: 'foo'},
-  {id: 1, name: 'bar'}
+  {id: 2, name: 'bar'}
 ];
 
 const MockAppCfg = {
@@ -72,23 +72,42 @@
       .respond(queryData);
   }));
 
-  it('should return an Observable', () => {
-    expect(typeof service.query('test').subscribe).toBe('function');
+  describe('the QUERY method', () => {
+    it('should return an Observable', () => {
+      expect(typeof service.query('test').subscribe).toBe('function');
+    });
+
+    it('the first event should be the resource response', (done) => {
+      let event = 0;
+      service.query('sample')
+        .subscribe(collection => {
+          event++;
+          if (event === 2) {
+            expect(collection[0].id).toEqual(queryData[0].id);
+            expect(collection[1].id).toEqual(queryData[1].id);
+            done();
+          }
+        });
+      $scope.$apply();
+      httpBackend.flush();
+    });
   });
 
-  it('the first event should be the resource response', (done) => {
-    let event = 0;
-    service.query('sample')
-      .subscribe(collection => {
-        event++;
-        if (event === 2) {
-          expect(collection[0].id).toEqual(queryData[0].id);
-          expect(collection[1].id).toEqual(queryData[1].id);
-          done();
-        }
-      });
-    $scope.$apply();
-    httpBackend.flush();
+  describe('the GET method', () => {
+    it('should return an observable containing a single model', (done) => {
+      let event = 0;
+      service.get('sample', queryData[1].id)
+        .subscribe((model) => {
+          event++;
+          if (event === 2) {
+            expect(model.id).toEqual(queryData[1].id);
+            expect(model.name).toEqual(queryData[1].name);
+            done();
+          }
+        });
+      httpBackend.flush();
+      $scope.$apply();
+    });
   });
 
   describe('when a web-socket event is received for that model', () => {
diff --git a/src/app/datasources/stores/model.store.ts b/src/app/datasources/stores/model.store.ts
index 950d441..10da16e 100644
--- a/src/app/datasources/stores/model.store.ts
+++ b/src/app/datasources/stores/model.store.ts
@@ -8,6 +8,7 @@
 
 export interface  IXosModelStoreService {
   query(model: string, apiUrl?: string): Observable<any>;
+  get(model: string, id: string | number): Observable<any>;
   search(modelName: string): any[];
 }
 
@@ -26,7 +27,7 @@
     this.efficientNext = this.XosDebouncer.debounce(this.next, 500, this, false);
   }
 
-  public query(modelName: string, apiUrl: string): Observable<any> {
+  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]) {
@@ -80,8 +81,31 @@
     }
   }
 
-  public get(model: string, id: number) {
-    // TODO implement a get method
+  public get(modelName: string, modelId: string | number): Observable<any> {
+    const subject = new BehaviorSubject([]);
+
+    const _findModel = (subject) => {
+      this._collections[modelName]
+        .subscribe((res) => {
+          const model = _.find(res, {id: modelId});
+          if (model) {
+            subject.next(model);
+          }
+        });
+    };
+
+    if (!this._collections[modelName]) {
+      // cache the models in that collection
+      this.query(modelName)
+        .subscribe((res) => {
+          _findModel(subject);
+        });
+    }
+    else {
+      _findModel(subject);
+    }
+
+    return subject.asObservable();
   }
 
   private next(subject: BehaviorSubject<any>): void {
@@ -89,7 +113,7 @@
   }
 
   private loadInitialData(model: string, apiUrl?: string) {
-    // TODO provide always the apiUrl togheter with the query() params
+    // TODO provide always the apiUrl together with the query() params
     if (!angular.isDefined(apiUrl)) {
       // NOTE check what is the correct pattern to pluralize this
       apiUrl = this.storeHelpers.urlFromCoreModel(model);