[CORD-1653] Adding a debug tab in model details

Change-Id: I6c3be4227309cbeb2dd7ab6252c1312dfd00fb18
diff --git a/src/app/core/debug/debug-model.html b/src/app/core/debug/debug-model.html
new file mode 100644
index 0000000..cf74189
--- /dev/null
+++ b/src/app/core/debug/debug-model.html
@@ -0,0 +1,22 @@
+<!--
+Copyright 2017-present Open Networking Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<div class="row" ng-if="vm.ngModel[field]" ng-repeat="field in vm.debugFields">
+    <div class="col-xs-12">
+        <label>{{vm.toLabel(field)}}</label>
+        <pre>{{vm.parseField(field, vm.ngModel[field])}}</pre>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/core/debug/debug-model.spec.ts b/src/app/core/debug/debug-model.spec.ts
new file mode 100644
index 0000000..46b6344
--- /dev/null
+++ b/src/app/core/debug/debug-model.spec.ts
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import * as angular from 'angular';
+import 'angular-mocks';
+import 'angular-ui-router';
+import {xosDebugModel} from './debug-model';
+
+const MockConfigHelpers = {
+  toLabel: jasmine.createSpy('toLabel')
+};
+
+const model = {
+  policed: 1503009920,
+  backend_register: '{\"next_run\": 0, \"last_success\": 1502860176.52445, \"exponent\": 0}',
+  backend_status: '1 - OK',
+  id: 1,
+  backend_need_delete: true,
+  self_content_type_id: 'core.instance',
+  backend_need_reap: false,
+  no_sync: false,
+  updated: 1503009920,
+  deleted: false,
+  policy_status: '2 - AuthorizationFailure(Authorization Failed: SSL exception connecting to https://192.168.108.119:5000/v2.0/tokens,) // Exception(Ansible playbook failed. // Error in creating the server, please check logs,) // The VM is available but not Active. state:ERROR,)',
+  lazy_blocked: false,
+  enacted: 1503009920,
+  enabled: 1503009920,
+  leaf_model_name: 'Instance',
+  created: 1503009920,
+  write_protect: false,
+  no_policy: false,
+  class_names: 'Instance,XOSBase'
+};
+
+describe('The xosDebugModel component', () => {
+  let scope, rootScope, element, compile , isolatedScope;
+
+  const compileElement = () => {
+
+    if (!scope) {
+      scope = rootScope.$new();
+    }
+
+    element = angular.element(`<xos-debug-model ng-model="model"></xos-debug-model>`);
+    compile(element)(scope);
+    scope.$digest();
+    isolatedScope = element.isolateScope().vm;
+  };
+
+  beforeEach(() => {
+    angular.module('xosDebugModel', [])
+      .component('xosDebugModel', xosDebugModel)
+      .value('ConfigHelpers', MockConfigHelpers);
+    angular.mock.module('xosDebugModel');
+
+    inject(($compile: ng.ICompileService, $rootScope: ng.IScope) => {
+      rootScope = $rootScope;
+      compile = $compile;
+    });
+  });
+
+  it('should have a toLabel method', () => {
+    compileElement();
+    expect(isolatedScope.toLabel).toBeDefined();
+    isolatedScope.toLabel('a');
+    expect(MockConfigHelpers.toLabel).toHaveBeenCalledWith('a');
+  });
+
+  describe('the parseField method', () => {
+    beforeEach(() => {
+      scope = rootScope.$new();
+      scope.model = model;
+      compileElement();
+    });
+
+    it('should convert dates', () => {
+      const dateFields = ['created', 'updated', 'enacted', 'policed'];
+
+      dateFields.forEach(f => {
+        const date = isolatedScope.parseField(f, model[f]);
+        expect(date).toEqual('Thu Aug 17 2017 15:45:20 GMT-0700 (PDT)');
+      });
+    });
+
+    it('should convert strings to JSON', () => {
+      const res = isolatedScope.parseField('backend_register', model['backend_register']);
+      expect(res.next_run).toBe(0);
+      expect(res.exponent).toBe(0);
+    });
+
+    it('should parse backend_status and policy_status', () => {
+      const policy = isolatedScope.parseField('policy_status', model['policy_status']);
+      expect(policy.match(/\n/g).length).toBe(3);
+    });
+  });
+});
diff --git a/src/app/core/debug/debug-model.ts b/src/app/core/debug/debug-model.ts
new file mode 100644
index 0000000..9760520
--- /dev/null
+++ b/src/app/core/debug/debug-model.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+
+ * http://www.apache.org/licenses/LICENSE-2.0
+
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// NOTE this component will render the hidden model fields for debug purposes
+
+import {IXosConfigHelpersService} from '../services/helpers/config.helpers';
+export class XosDebugModelController {
+  static $inject = [
+    'ConfigHelpers'
+  ];
+
+  public debugFields: string[];
+
+  constructor(
+    private ConfigHelpers: IXosConfigHelpersService
+  ) {
+
+  }
+
+  $onInit() {
+    this.debugFields = this.ConfigHelpers.form_excluded_fields;
+  }
+
+  public toLabel(string: string): string {
+    return this.ConfigHelpers.toLabel(string);
+  }
+
+  // NOTE each field has his own format, so make it human readable
+  public parseField(fieldName: string, value: any): any {
+    switch (fieldName) {
+      case 'created':
+      case 'updated':
+      case 'enacted':
+      case 'policed':
+        return new Date(parseInt(value, 10) * 1000).toString();
+      case 'backend_register':
+        return JSON.parse(value);
+      case 'policy_status':
+      case 'backend_status':
+        return value
+          .split(' // ')
+          .join('\n');
+      default:
+        return value;
+    }
+  }
+}
+
+export const xosDebugModel: angular.IComponentOptions = {
+  template: require('./debug-model.html'),
+    controllerAs: 'vm',
+    controller: XosDebugModelController,
+    bindings: {
+      ngModel: '=',
+    }
+};
diff --git a/src/app/core/debug/debug.html b/src/app/core/debug/debug-summary.html
similarity index 81%
rename from src/app/core/debug/debug.html
rename to src/app/core/debug/debug-summary.html
index 2590708..9807ac9 100644
--- a/src/app/core/debug/debug.html
+++ b/src/app/core/debug/debug-summary.html
@@ -34,5 +34,12 @@
                 <i class="fa fa-remove text-danger" ng-hide="vm.debugStatus.events"></i>
             </td>
         </tr>
+        <tr>
+            <td>Models</td>
+            <td class="text-right">
+                <i class="fa fa-check text-success" ng-show="vm.debugStatus.modelsTab"></i>
+                <i class="fa fa-remove text-danger" ng-hide="vm.debugStatus.modelsTab"></i>
+            </td>
+        </tr>
     </tbody>
 </table>
\ No newline at end of file
diff --git a/src/app/core/debug/debug.ts b/src/app/core/debug/debug-summary.ts
similarity index 85%
rename from src/app/core/debug/debug.ts
rename to src/app/core/debug/debug-summary.ts
index fe5d178..f037080 100644
--- a/src/app/core/debug/debug.ts
+++ b/src/app/core/debug/debug-summary.ts
@@ -16,7 +16,7 @@
 
 import {IXosDebugStatus, IXosDebugService} from './debug.service';
 
-class XosDebugComponentController {
+class XosDebugSummaryController {
   static $inject = ['$scope', 'XosDebug'];
   public debugStatus: IXosDebugStatus;
 
@@ -33,8 +33,8 @@
   }
 }
 
-export const xosDebugComponent: angular.IComponentOptions = {
-  template: require('./debug.html'),
+export const xosDebugSummary: angular.IComponentOptions = {
+  template: require('./debug-summary.html'),
   controllerAs: 'vm',
-  controller: XosDebugComponentController
+  controller: XosDebugSummaryController
 };
diff --git a/src/app/core/debug/debug.service.spec.ts b/src/app/core/debug/debug.service.spec.ts
index 0d27fa6..a26d8cc 100644
--- a/src/app/core/debug/debug.service.spec.ts
+++ b/src/app/core/debug/debug.service.spec.ts
@@ -56,16 +56,16 @@
   it('should disable the global debug status', () => {
     spyOn(window.localStorage, 'getItem')
       .and.returnValue('true');
-    service.toggleGlobalDebug();
-    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug', 'false');
+    service.toggleDebug('global');
+    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug-global', 'false');
     expect(service.status.global).toBeFalsy();
     expect($scope.$broadcast).toHaveBeenCalledWith('xos.debug.status', service.status);
   });
   it('should enable the global debug status', () => {
     spyOn(window.localStorage, 'getItem')
       .and.returnValue('false');
-    service.toggleGlobalDebug();
-    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug', 'true');
+    service.toggleDebug('global');
+    expect(window.localStorage.setItem).toHaveBeenCalledWith('debug-global', 'true');
     expect(service.status.global).toBeTruthy();
     expect($scope.$broadcast).toHaveBeenCalledWith('xos.debug.status', service.status);
   });
diff --git a/src/app/core/debug/debug.service.ts b/src/app/core/debug/debug.service.ts
index 3c6c2c8..f23fa06 100644
--- a/src/app/core/debug/debug.service.ts
+++ b/src/app/core/debug/debug.service.ts
@@ -19,13 +19,13 @@
 export interface IXosDebugStatus {
   global: boolean;
   events: boolean;
+  modelsTab: boolean;
 }
 
 export interface IXosDebugService {
   status: IXosDebugStatus;
   setupShortcuts(): void;
-  toggleGlobalDebug(): void;
-  toggleEventDebug(): void;
+  toggleDebug(type: 'global' | 'events' | 'modelsTab'): void;
 }
 
 export class XosDebugService implements IXosDebugService {
@@ -34,7 +34,8 @@
 
   public status: IXosDebugStatus = {
     global: false,
-    events: false
+    events: false,
+    modelsTab: false
   };
 
   constructor (
@@ -42,51 +43,40 @@
     private $scope: ng.IScope,
     private XosKeyboardShortcut: IXosKeyboardShortcutService
   ) {
-    const debug = window.localStorage.getItem('debug');
+    const debug = window.localStorage.getItem('debug-global');
     this.status.global = (debug === 'true');
 
-    const debugEvent = window.localStorage.getItem('debug-event');
+    const debugEvent = window.localStorage.getItem('debug-events');
     this.status.events = (debugEvent === 'true');
+
+    const debugModelsTab = window.localStorage.getItem('debug-modelsTab');
+    this.status.modelsTab = (debugModelsTab === 'true');
   }
 
   public setupShortcuts(): void {
     this.XosKeyboardShortcut.registerKeyBinding({
       key: 'D',
-      cb: () => this.toggleGlobalDebug(),
+      cb: () => this.toggleDebug('global'),
       description: 'Toggle debug messages in browser console'
     }, 'global');
 
     this.XosKeyboardShortcut.registerKeyBinding({
       key: 'E',
-      cb: () => this.toggleEventDebug(),
+      cb: () => this.toggleDebug('events'),
       description: 'Toggle debug messages for WS events in browser console'
     }, 'global');
   }
 
-  public toggleGlobalDebug(): void {
-    if (window.localStorage.getItem('debug') === 'true') {
-      this.$log.info(`[XosDebug] Disabling debug`);
-      window.localStorage.setItem('debug', 'false');
-      this.status.global = false;
+  public toggleDebug(type: 'global' | 'events' | 'modelsTab'): void {
+    if (window.localStorage.getItem(`debug-${type}`) === 'true') {
+      this.$log.info(`[XosDebug] Disabling ${type} debug`);
+      window.localStorage.setItem(`debug-${type}`, 'false');
+      this.status[type] = false;
     }
     else {
-      window.localStorage.setItem('debug', 'true');
-      this.$log.info(`[XosDebug] Enabling debug`);
-      this.status.global = true;
-    }
-    this.$scope.$broadcast('xos.debug.status', this.status);
-  }
-
-  public toggleEventDebug(): void {
-    if (window.localStorage.getItem('debug-event') === 'true') {
-      this.$log.info(`[XosDebug] Disabling debug for WS events`);
-      window.localStorage.setItem('debug-event', 'false');
-      this.status.events = false;
-    }
-    else {
-      window.localStorage.setItem('debug-event', 'true');
-      this.$log.info(`[XosDebug] Enabling debug for WS events`);
-      this.status.events = true;
+      this.$log.info(`[XosDebug] Enabling ${type} debug`);
+      window.localStorage.setItem(`debug-${type}`, 'true');
+      this.status[type] = true;
     }
     this.$scope.$broadcast('xos.debug.status', this.status);
   }