changeset 127:ef302144b90a

Client-Gateway integration Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-July/024095.html
author Andrew Azores <aazores@redhat.com>
date Thu, 13 Jul 2017 10:44:20 -0400
parents 1c4e3d60b0ed
children 2d6a8d1d5f41
files mock-api/endpoints/jvms.endpoint.js mock-api/endpoints/system-cpu.endpoint.js mock-api/endpoints/system-info.endpoint.js mock-api/endpoints/system-memory.endpoint.js src/app/components/jvm-info/jvm-info.html src/app/components/jvm-list/jvm-list.controller.js src/app/components/system-info/system-cpu.controller.js src/app/components/system-info/system-cpu.controller.spec.js src/app/components/system-info/system-info.controller.js src/app/components/system-info/system-info.controller.spec.js src/app/components/system-info/system-info.html src/app/components/system-info/system-info.service.js src/app/components/system-info/system-info.service.spec.js src/app/components/system-info/system-memory.controller.js src/app/components/system-info/system-memory.controller.spec.js src/app/shared/filters/format-bytes.filter.js src/app/shared/filters/format-bytes.filter.spec.js src/app/shared/filters/metric-to-big-int.filter.js src/app/shared/filters/metric-to-big-int.filter.spec.js src/app/shared/services/metric-to-big-int.service.js src/app/shared/services/metric-to-big-int.service.spec.js src/app/shared/services/scale-bytes.service.js src/app/shared/services/scale-bytes.service.spec.js
diffstat 23 files changed, 263 insertions(+), 53 deletions(-) [+]
line wrap: on
line diff
--- a/mock-api/endpoints/jvms.endpoint.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/mock-api/endpoints/jvms.endpoint.js	Thu Jul 13 10:44:20 2017 -0400
@@ -20,22 +20,26 @@
         'jvms': [
           {
             'mainClass': 'c.r.t.A',
-            'startTime': { $numberLong: '45000' },
+            'startTime': { $numberLong: (Date.now() - 10000000).toString() },
+            'stopTime': { $numberLong: '-1' },
             'jvmId': 'vm-0'
           },
           {
             'mainClass': 'c.r.t.B',
-            'startTime': { $numberLong: '45000' },
+            'startTime': { $numberLong: (Date.now() - 1500000).toString() },
+            'stopTime': { $numberLong: '-1' },
             'jvmId': 'vm-1'
           },
           {
             'mainClass': 'c.r.t.C',
-            'startTime': { $numberLong: '45000' },
+            'startTime': { $numberLong: (Date.now() - 25000000).toString() },
+            'stopTime': { $numberLong: '-1' },
             'jvmId': 'vm-2'
           },
           {
             'mainClass': 'c.r.t.D',
-            'startTime': { $numberLong: '45000' },
+            'startTime': { $numberLong: (Date.now() - 350000000).toString() },
+            'stopTime': { $numberLong: Date.now().toString() },
             'jvmId': 'vm-3'
           }
         ]
@@ -55,8 +59,8 @@
           systemId: req.params.systemId,
           jvmId: req.params.jvmId,
           mainClass: 'c.r.t.A',
-          startTime: { $numberLong: (Date.now() - 5000000 + _.round(Math.random() * 1000000)).toString() },
-          stopTime: { $numberLong: '-1' },
+          startTime: Date.now() - 5000000 + _.round(Math.random() * 1000000),
+          stopTime: -1,
           isAlive: true,
           jvmPid: _.round(Math.random() * 2048) + 512,
           javaVersion: '1.9',
@@ -77,9 +81,9 @@
               value: 'bam'
             }
           ],
-          uid: { $numberLong: _.round(Math.random() * 800) },
+          uid: _.floor(Math.random() * 800),
           username: 'thermostat-user',
-          lastUpdated: { $numberLong: Date.now().toString() }
+          lastUpdated: Date.now().toString()
         }]
       }
     ));
--- a/mock-api/endpoints/system-cpu.endpoint.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/mock-api/endpoints/system-cpu.endpoint.js	Thu Jul 13 10:44:20 2017 -0400
@@ -1,14 +1,17 @@
 function systemCpu (server) {
   var _ = require('lodash');
   server.init('systemCpu');
-  server.app.get('/system-info/cpu/:systemId', function (req, res, next) {
+  server.app.get('/system-cpu/0.0.1/systems/:systemId', function (req, res, next) {
     server.logRequest('system-info', req);
     res.setHeader('Content-Type', 'application/json');
+    var randomUsage = function () {
+      return _.round(Math.random() * 100);
+    };
     res.send(JSON.stringify(
       {
-        response: {
-          percent: _.round(Math.random() * 100)
-        }
+        response: [{
+          perProcessorUsage: [randomUsage(), randomUsage(), randomUsage(), randomUsage()]
+        }]
       }
     ));
     next();
--- a/mock-api/endpoints/system-info.endpoint.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/mock-api/endpoints/system-info.endpoint.js	Thu Jul 13 10:44:20 2017 -0400
@@ -1,19 +1,19 @@
 function systemInfo (server) {
   server.init('systemInfo');
-  server.app.get('/system-info/:systemId', function (req, res, next) {
+  server.app.get('/systems/0.0.1/systems/:systemId', function (req, res, next) {
     server.logRequest('system-info', req);
     res.setHeader('Content-Type', 'application/json');
     res.send(JSON.stringify(
       {
-        response: {
+        response: [{
           systemId: req.params.systemId,
-          hostName: req.params.systemId + '-host',
+          hostname: req.params.systemId + '-host',
           osName: 'Linux',
           osKernel: '4.10.11-200.fc25.x86_64',
           cpuCount: 4,
           cpuModel: 'GenuineIntel',
-          totalMemory: 16384
-        }
+          totalMemory: 16 * 1024 * 1024 * 1024
+        }]
       }
     ));
     next();
--- a/mock-api/endpoints/system-memory.endpoint.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/mock-api/endpoints/system-memory.endpoint.js	Thu Jul 13 10:44:20 2017 -0400
@@ -20,7 +20,7 @@
       var data = {
         systemId: systemId,
         agentId: 'mock-agentId',
-        timestamp: new Date().getTime(),
+        timeStamp: new Date().getTime(),
         total: 16384,
         free: _.round(Math.random() * (16384 / 4)),
         buffers: 16384 / 32,
--- a/src/app/components/jvm-info/jvm-info.html	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/jvm-info/jvm-info.html	Thu Jul 13 10:44:20 2017 -0400
@@ -22,7 +22,7 @@
           </tr>
           <tr>
             <td>UID</td>
-            <td>{{ctrl.jvmInfo.uid | metricToBigInt | bigIntToString }}</td>
+            <td>{{ctrl.jvmInfo.uid}}</td>
           </tr>
           <tr>
             <td>JVM ID</td>
@@ -34,15 +34,15 @@
           </tr>
           <tr>
             <td>Start Time</td>
-            <td>{{ctrl.jvmInfo.startTime | timestampToDate}}</td>
+            <td>{{ctrl.jvmInfo.startTime | date:"medium"}}</td>
           </tr>
           <tr ng-show="parseInt(ctrl.jvmInfo.endTime.$numberLong) > 0">
             <td>Stop Time</td>
-            <td>{{ctrl.jvmInfo.stopTime | timestampToDate}}</td>
+            <td>{{ctrl.jvmInfo.stopTime | date:"medium"}}</td>
           </tr>
           <tr ng-show="ctrl.jvmInfo.lastUpdated">
             <td>Last Updated</td>
-            <td>{{ctrl.jvmInfo.lastUpdated | timestampToDate}}</td>
+            <td>{{ctrl.jvmInfo.lastUpdated | date:"medium"}}</td>
           </tr>
           <tr>
             <td>JVM PID</td>
--- a/src/app/components/jvm-list/jvm-list.controller.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/jvm-list/jvm-list.controller.js	Thu Jul 13 10:44:20 2017 -0400
@@ -49,7 +49,7 @@
       this.loadData();
     });
 
-    this.scope.isAlive = (jvm) => {
+    this.scope.isAlive = jvm => {
       if (!jvm.hasOwnProperty('stopTime')) {
         return false;
       }
--- a/src/app/components/system-info/system-cpu.controller.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-cpu.controller.js	Thu Jul 13 10:44:20 2017 -0400
@@ -26,6 +26,7 @@
  */
 
 import 'c3';
+import _ from 'lodash';
 import filters from 'shared/filters/filters.module.js';
 import service from './system-info.service.js';
 
@@ -57,9 +58,9 @@
 
   update () {
     this.svc.getCpuInfo(this.scope.systemId).then(resp => {
-      let cpuInfo = resp.data.response;
+      let cpuInfo = resp.data.response[0];
       this.data = {
-        used: cpuInfo.percent,
+        used: _.floor(_.mean(cpuInfo.perProcessorUsage)),
         total: 100
       };
     });
--- a/src/app/components/system-info/system-cpu.controller.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-cpu.controller.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -107,14 +107,14 @@
         func.should.be.a.Function();
         let mockData = {
           data: {
-            response: {
-              percent: 80
-            }
+            response: [{
+              perProcessorUsage: [80]
+            }]
           }
         };
         func(mockData);
         controller.data.should.deepEqual({
-          used: mockData.data.response.percent,
+          used: mockData.data.response[0].perProcessorUsage[0],
           total: 100
         });
       });
--- a/src/app/components/system-info/system-info.controller.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-info.controller.js	Thu Jul 13 10:44:20 2017 -0400
@@ -40,7 +40,7 @@
 
     systemInfoService.getSystemInfo(systemId).then(
       resp => {
-        this.systemInfo = resp.data.response;
+        this.systemInfo = resp.data.response[0];
         this.showErr = false;
       },
       () => {
--- a/src/app/components/system-info/system-info.controller.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-info.controller.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -58,7 +58,7 @@
     };
     promise.resolve({
       data: {
-        response: response
+        response: [response]
       }
     });
     scope.$apply();
--- a/src/app/components/system-info/system-info.html	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-info.html	Thu Jul 13 10:44:20 2017 -0400
@@ -18,9 +18,25 @@
             </tr>
           </thead>
           <tbody>
-            <tr ng-repeat="(key, value) in ctrl.systemInfo">
-              <td>{{key}}</td>
-              <td>{{value}}</td>
+            <tr>
+              <td>Hostname</td>
+              <td>{{ctrl.systemInfo.hostname}}</td>
+            </tr>
+            <tr>
+              <td>Operating System</td>
+              <td>{{ctrl.systemInfo.osName}}</td>
+            </tr>
+            <tr>
+              <td>Kernel</td>
+              <td>{{ctrl.systemInfo.osKernel}}</td>
+            </tr>
+            <tr>
+              <td>CPU</td>
+              <td>{{ctrl.systemInfo.cpuModel}}<br/>({{ctrl.systemInfo.cpuCount}} cores)</td>
+            </tr>
+            <tr>
+              <td>Memory</td>
+              <td>{{ctrl.systemInfo.totalMemory | formatBytes}}</td>
             </tr>
           </tbody>
         </table>
--- a/src/app/components/system-info/system-info.service.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-info.service.js	Thu Jul 13 10:44:20 2017 -0400
@@ -37,15 +37,29 @@
   }
 
   getSystemInfo (systemId) {
-    return this.http.get(urlJoin(this.gatewayUrl, 'system-info', systemId));
+    return this.http.get(urlJoin(this.gatewayUrl, 'systems', '0.0.1', 'systems', systemId), {
+      params: {
+        sort: '-timeStamp',
+        limit: 1
+      }
+    });
   }
 
   getCpuInfo (systemId) {
-    return this.http.get(urlJoin(this.gatewayUrl, 'system-info', 'cpu', systemId));
+    return this.http.get(urlJoin(this.gatewayUrl, 'system-cpu', '0.0.1', 'systems', systemId), {
+      params: {
+        sort: '-timeStamp',
+        limit: 1
+      }
+    });
   }
 
   getMemoryInfo (systemId) {
-    return this.http.get(urlJoin(this.gatewayUrl, 'system-memory', '0.0.1', 'systems', systemId));
+    return this.http.get(urlJoin(this.gatewayUrl, 'system-memory', '0.0.1', 'systems', systemId), {
+      params: {
+        sort: '-timeStamp'
+      }
+    });
   }
 }
 
--- a/src/app/components/system-info/system-info.service.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-info.service.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -60,13 +60,13 @@
         osName: 'Linux',
         osKernel: '4.10.11-200.fc25.x86_64'
       };
-      httpBackend.when('GET', 'http://example.com:1234/system-info/foo-systemId')
+      httpBackend.when('GET', 'http://example.com:1234/systems/0.0.1/systems/foo-systemId?limit=1&sort=-timeStamp')
         .respond(expected);
       svc.getSystemInfo('foo-systemId').then(res => {
         res.data.should.deepEqual(expected);
         done();
       });
-      httpBackend.expectGET('http://example.com:1234/system-info/foo-systemId');
+      httpBackend.expectGET('http://example.com:1234/systems/0.0.1/systems/foo-systemId?limit=1&sort=-timeStamp');
       httpBackend.flush();
       scope.$apply();
     });
@@ -77,13 +77,13 @@
       let expected = {
         percent: 80
       };
-      httpBackend.when('GET', 'http://example.com:1234/system-info/cpu/foo-systemId')
+      httpBackend.when('GET', 'http://example.com:1234/system-cpu/0.0.1/systems/foo-systemId?limit=1&sort=-timeStamp')
         .respond(expected);
       svc.getCpuInfo('foo-systemId').then(res => {
         res.data.should.deepEqual(expected);
         done();
       });
-      httpBackend.expectGET('http://example.com:1234/system-info/cpu/foo-systemId');
+      httpBackend.expectGET('http://example.com:1234/system-cpu/0.0.1/systems/foo-systemId?limit=1&sort=-timeStamp');
       httpBackend.flush();
       scope.$apply();
     });
@@ -95,13 +95,13 @@
         total: 16384,
         used: 9001
       };
-      httpBackend.when('GET', 'http://example.com:1234/system-memory/0.0.1/systems/foo-systemId')
+      httpBackend.when('GET', 'http://example.com:1234/system-memory/0.0.1/systems/foo-systemId?sort=-timeStamp')
         .respond(expected);
       svc.getMemoryInfo('foo-systemId').then(res => {
         res.data.should.deepEqual(expected);
         done();
       });
-      httpBackend.expectGET('http://example.com:1234/system-memory/0.0.1/systems/foo-systemId');
+      httpBackend.expectGET('http://example.com:1234/system-memory/0.0.1/systems/foo-systemId?sort=-timeStamp');
       httpBackend.flush();
       scope.$apply();
     });
--- a/src/app/components/system-info/system-memory.controller.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-memory.controller.js	Thu Jul 13 10:44:20 2017 -0400
@@ -132,7 +132,7 @@
 
       // update the memory time series chart
       this.lineConfig.axis.y.max = total;
-      this.lineData.xData.push(data.timestamp);
+      this.lineData.xData.push(data.timeStamp);
       this.lineData.yData0.push(total);
       this.lineData.yData1.push(free);
       this.lineData.yData2.push(used);
--- a/src/app/components/system-info/system-memory.controller.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/components/system-info/system-memory.controller.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -94,7 +94,7 @@
         response: {
           systemId: 'foo-systemId',
           agentId: 'mock-agentId',
-          timestamp: Date.now(),
+          timeStamp: Date.now(),
           total: 16384,
           free: 0,
           buffers: 1,
@@ -219,7 +219,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestamp,
+              timeStamp: timestamp,
               total: 16384,
               free: 0,
               buffers: 1,
@@ -268,7 +268,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestampA,
+              timeStamp: timestampA,
               total: 16384,
               free: 0,
               buffers: 0,
@@ -280,7 +280,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestampB,
+              timeStamp: timestampB,
               total: 16384,
               free: 0,
               buffers: 0,
@@ -306,7 +306,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestampA,
+              timeStamp: timestampA,
               total: 16384,
               free: 0,
               buffers: 0,
@@ -324,7 +324,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestampB,
+              timeStamp: timestampB,
               total: 16384,
               free: 0,
               buffers: 0,
@@ -351,7 +351,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestampA,
+              timeStamp: timestampA,
               total: 16384,
               free: 0,
               buffers: 0,
@@ -369,7 +369,7 @@
             {
               systemId: 'foo-systemId',
               agentId: 'mock-agentId',
-              timestamp: timestampB,
+              timeStamp: timestampB,
               total: 16384,
               free: 0,
               buffers: 0,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/shared/filters/format-bytes.filter.js	Thu Jul 13 10:44:20 2017 -0400
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+import filterModule from './filters.module.js';
+
+function filterProvider (scaleBytesService) {
+  'ngInject';
+  return val => {
+    // FIXME: https://trello.com/c/3jDpmy8M/170-clean-up-numberlong-ambiguities
+    if (typeof val === 'number') {
+      val = { $numberLong: val.toString() };
+    }
+    let scale = scaleBytesService.format(val);
+    return scale.result + ' ' + scale.unit;
+  };
+}
+
+export default angular
+  .module(filterModule)
+  .filter('formatBytes', filterProvider)
+  .name;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/app/shared/filters/format-bytes.filter.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * Thermostat is distributed under the GNU General Public License,
+ * version 2 or any later version (with a special exception described
+ * below, commonly known as the "Classpath Exception").
+ *
+ * A copy of GNU General Public License (GPL) is included in this
+ * distribution, in the file COPYING.
+ *
+ * Linking Thermostat code with other modules is making a combined work
+ * based on Thermostat.  Thus, the terms and conditions of the GPL
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Thermostat give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent
+ * modules, and to copy and distribute the resulting executable under
+ * terms of your choice, provided that you also meet, for each linked
+ * independent module, the terms and conditions of the license of that
+ * module.  An independent module is a module which is not derived from
+ * or based on Thermostat code.  If you modify Thermostat, you may
+ * extend this exception to your version of the software, but you are
+ * not obligated to do so.  If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+import filtersModule from 'shared/filters/filters.module.js';
+import servicesModule from 'shared/services/services.module.js';
+
+describe('formatBytesFilter', () => {
+
+  let fn, mockSvc;
+  beforeEach(() => {
+    mockSvc = {
+      format: sinon.stub().returns({
+        result: 100,
+        scale: 2,
+        unit: 'MiB'
+      })
+    };
+    angular.mock.module(filtersModule);
+    angular.mock.module(servicesModule, $provide => {
+      'ngInject';
+      $provide.value('scaleBytesService', mockSvc);
+    });
+    angular.mock.inject(formatBytesFilter => {
+      'ngInject';
+      fn = formatBytesFilter;
+    });
+  });
+
+  it('should exist', () => {
+    should.exist(fn);
+  });
+
+  it('should convert raw numbers', () => {
+    mockSvc.format.should.not.be.called();
+    fn(100).should.equal('100 MiB');
+    mockSvc.format.should.be.calledOnce();
+    mockSvc.format.should.be.calledWithMatch({ $numberLong: '100' });
+  });
+
+  it('should delegate when called with $numberLong', () => {
+    mockSvc.format.should.not.be.called();
+    fn({ $numberLong: '100' }).should.equal('100 MiB');
+    mockSvc.format.should.be.calledOnce();
+    mockSvc.format.should.be.calledWithMatch({ $numberLong: '100' });
+  });
+
+});
--- a/src/app/shared/filters/metric-to-big-int.filter.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/shared/filters/metric-to-big-int.filter.js	Thu Jul 13 10:44:20 2017 -0400
@@ -40,6 +40,10 @@
     // in case the filter is invoked on asynchronously loaded data and
     // 'val' is undefined, we want to avoid throwing an error below
     val = val || { $numberLong: '0' };
+    // FIXME: https://trello.com/c/3jDpmy8M/170-clean-up-numberlong-ambiguities
+    if (typeof val === 'number') {
+      val = { $numberLong: val.toString() };
+    }
 
     let res = metricToBigIntService.convert(val);
     // 'big(undefined)' does not have any functions defined on it,
--- a/src/app/shared/filters/metric-to-big-int.filter.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/shared/filters/metric-to-big-int.filter.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -47,7 +47,11 @@
   });
 
   it('should fail on non-objects', () => {
-    fn(100).should.deepEqual(big(undefined));
+    fn('foo').should.deepEqual(big(undefined));
+  });
+
+  it('should convert plain numbers', () => {
+    fn(100).should.deepEqual(big(100));
   });
 
   it('should fail on objects without $numberLong property', () => {
--- a/src/app/shared/services/metric-to-big-int.service.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/shared/services/metric-to-big-int.service.js	Thu Jul 13 10:44:20 2017 -0400
@@ -40,6 +40,13 @@
   }
 
   convert (metric) {
+    if (!angular.isDefined(metric)) {
+      return this.big(undefined);
+    }
+    // FIXME: https://trello.com/c/3jDpmy8M/170-clean-up-numberlong-ambiguities
+    if (typeof metric === 'number') {
+      metric = { $numberLong: metric.toString() };
+    }
     return this.big(metric.$numberLong);
   }
 }
--- a/src/app/shared/services/metric-to-big-int.service.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/shared/services/metric-to-big-int.service.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -50,4 +50,18 @@
     svc.big.should.be.calledWith('0');
   });
 
+  it('should convert raw numbers', () => {
+    svc.big.should.not.be.called();
+    svc.convert(0).should.deepEqual(big(0));
+    svc.big.should.be.calledOnce();
+    svc.big.should.be.calledWith('0');
+  });
+
+  it('should handle undefined', () => {
+    svc.big.should.not.be.called();
+    svc.convert(undefined).should.deepEqual(big(undefined));
+    svc.big.should.be.calledOnce();
+    svc.big.should.be.calledWith(undefined);
+  });
+
 });
--- a/src/app/shared/services/scale-bytes.service.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/shared/services/scale-bytes.service.js	Thu Jul 13 10:44:20 2017 -0400
@@ -36,6 +36,17 @@
   }
 
   format (bytesMetric, dp = 2) {
+    if (!angular.isDefined(bytesMetric)) {
+      return {
+        result: 0,
+        scale: 0,
+        unit: ''
+      };
+    }
+    // FIXME: https://trello.com/c/3jDpmy8M/170-clean-up-numberlong-ambiguities
+    if (typeof bytesMetric === 'number') {
+      bytesMetric = { $numberLong: bytesMetric.toString() };
+    }
     const base = 1024;
     let big = this.metricToBigInt.convert(bytesMetric);
 
--- a/src/app/shared/services/scale-bytes.service.spec.js	Wed Jul 12 13:56:56 2017 -0400
+++ b/src/app/shared/services/scale-bytes.service.spec.js	Thu Jul 13 10:44:20 2017 -0400
@@ -39,6 +39,22 @@
     should.exist(svc);
   });
 
+  it('should handle undefined', () => {
+    svc.format(undefined).should.deepEqual({
+      result: 0,
+      scale: 0,
+      unit: ''
+    });
+  });
+
+  it('should convert plain numbers', () => {
+    svc.format(100).should.deepEqual({
+      result: 100,
+      scale: 1,
+      unit: 'B'
+    });
+  });
+
   it('should not scale 0 bytes', () => {
     svc.format({ $numberLong: '0' }).should.deepEqual({
       result: 0,