GUI -- Completed implementation of Instance events (add, update, remove)
- fixed instance color selection (using cat7() function)
- miscellaneous additions to utility functions.
- etc. and so on...
Change-Id: I61895489ccc60fa17beda9e920e65742e0f2c526
diff --git a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
index 735733e..77c70a6 100644
--- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
+++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -132,8 +132,97 @@
$log.warn('SvgUtilService: loadGlow -- To Be Implemented');
}
+ // --- Ordinal scales for 7 values.
+ // TODO: tune colors for light and dark themes
+ // Note: These colors look good on the white background. Still, need to tune for dark.
+
+ // blue brown brick red sea green purple dark teal lime
+ var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
+ lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
+
+ darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
+ darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
+
+ var colors= {
+ light: {
+ norm: d3.scale.ordinal().range(lightNorm),
+ mute: d3.scale.ordinal().range(lightMute)
+ },
+ dark: {
+ norm: d3.scale.ordinal().range(darkNorm),
+ mute: d3.scale.ordinal().range(darkMute)
+ }
+ };
+
function cat7() {
- $log.warn('SvgUtilService: cat7 -- To Be Implemented');
+ var tcid = 'd3utilTestCard';
+
+ function getColor(id, muted, theme) {
+ // NOTE: since we are lazily assigning domain ids, we need to
+ // get the color from all 4 scales, to keep the domains
+ // in sync.
+ var ln = colors.light.norm(id),
+ lm = colors.light.mute(id),
+ dn = colors.dark.norm(id),
+ dm = colors.dark.mute(id);
+ if (theme === 'dark') {
+ return muted ? dm : dn;
+ } else {
+ return muted ? lm : ln;
+ }
+ }
+
+ function testCard(svg) {
+ var g = svg.select('g#' + tcid),
+ dom = d3.range(7),
+ k, muted, theme, what;
+
+ if (!g.empty()) {
+ g.remove();
+
+ } else {
+ g = svg.append('g')
+ .attr('id', tcid)
+ .attr('transform', 'scale(4)translate(20,20)');
+
+ for (k=0; k<4; k++) {
+ muted = k%2;
+ what = muted ? ' muted' : ' normal';
+ theme = k < 2 ? 'light' : 'dark';
+ dom.forEach(function (id, i) {
+ var x = i * 20,
+ y = k * 20,
+ f = get(id, muted, theme);
+ g.append('circle').attr({
+ cx: x,
+ cy: y,
+ r: 5,
+ fill: f
+ });
+ });
+ g.append('rect').attr({
+ x: 140,
+ y: k * 20 - 5,
+ width: 32,
+ height: 10,
+ rx: 2,
+ fill: '#888'
+ });
+ g.append('text').text(theme + what)
+ .attr({
+ x: 142,
+ y: k * 20 + 2,
+ fill: 'white'
+ })
+ .style('font-size', '4pt');
+ }
+ }
+ }
+
+ return {
+ testCard: testCard,
+ getColor: getColor
+ };
}
function translate(x, y) {
diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js
index 932a7c6..690c8b2 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -45,7 +45,38 @@
// Returns true if all names in the array are defined as functions
// on the given api object; false otherwise.
+ // Also returns false if there are properties on the api that are NOT
+ // listed in the array of names.
function areFunctions(api, fnNames) {
+ var fnLookup = {},
+ extraFound = false;
+
+ if (!isA(fnNames)) {
+ return false;
+ }
+ var n = fnNames.length,
+ i, name;
+ for (i=0; i<n; i++) {
+ name = fnNames[i];
+ if (!isF(api[name])) {
+ return false;
+ }
+ fnLookup[name] = true;
+ }
+
+ // check for properties on the API that are not listed in the array,
+ angular.forEach(api, function (value, key) {
+ if (!fnLookup[key]) {
+ extraFound = true;
+ }
+ });
+ return !extraFound;
+ }
+
+ // Returns true if all names in the array are defined as functions
+ // on the given api object; false otherwise. This is a non-strict version
+ // that does not care about other properties on the api.
+ function areFunctionsNonStrict(api, fnNames) {
if (!isA(fnNames)) {
return false;
}
@@ -71,6 +102,21 @@
};
}
+ // search through an array of objects, looking for the one with the
+ // tagged property matching the given key. tag defaults to 'id'.
+ // returns the index of the matching object, or -1 for no match.
+ function find(key, array, tag) {
+ var _tag = tag || 'id',
+ idx, n, d;
+ for (idx = 0, n = array.length; idx < n; idx++) {
+ d = array[idx];
+ if (d[_tag] === key) {
+ return idx;
+ }
+ }
+ return -1;
+ }
+
angular.module('onosUtil')
.factory('FnService', ['$window', function (_$window_) {
$window = _$window_;
@@ -82,7 +128,9 @@
isO: isO,
contains: contains,
areFunctions: areFunctions,
- windowSize: windowSize
+ areFunctionsNonStrict: areFunctionsNonStrict,
+ windowSize: windowSize,
+ find: find
};
}]);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
index fcff4e4..b4b5c74 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -30,7 +30,9 @@
var evHandler = {
showSummary: showSummary,
- addInstance: addInstance
+ addInstance: addInstance,
+ updateInstance: updateInstance,
+ removeInstance: removeInstance
// TODO: implement remaining handlers..
};
@@ -51,6 +53,16 @@
tis.addInstance(ev.payload);
}
+ function updateInstance(ev) {
+ $log.debug(' **** Update Instance **** ', ev.payload);
+ tis.updateInstance(ev.payload);
+ }
+
+ function removeInstance(ev) {
+ $log.debug(' **** Remove Instance **** ', ev.payload);
+ tis.removeInstance(ev.payload);
+ }
+
// ==========================
var dispatcher = {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoInst.js b/web/gui/src/main/webapp/app/view/topo/topoInst.js
index 12e47c6..7bd74f9 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoInst.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoInst.js
@@ -23,7 +23,7 @@
'use strict';
// injected refs
- var $log, ps, sus, gs;
+ var $log, ps, sus, gs, ts, fs;
// configuration
var instCfg = {
@@ -79,6 +79,23 @@
}
}
+ function removeInstance(data) {
+ var id = data.id,
+ d = onosInstances[id];
+ if (d) {
+ var idx = fs.find(id, onosOrder);
+ if (idx >= 0) {
+ onosOrder.splice(idx, 1);
+ }
+ delete onosInstances[id];
+ updateInstances();
+ } else {
+ logicError('removeInstance lookup fail. ID = "' + id + '"');
+ }
+ }
+
+ // ==========================
+
function computeDim(self) {
var css = window.getComputedStyle(self);
return {
@@ -143,9 +160,7 @@
}
function instColor(id, online) {
- // TODO: fix this..
- //return cat7.get(id, !online, network.view.getTheme());
- return '#3E5780';
+ return sus.cat7().getColor(id, !online, ts.theme());
}
// ==============================
@@ -288,17 +303,22 @@
angular.module('ovTopo')
.factory('TopoInstService',
['$log', 'PanelService', 'SvgUtilService', 'GlyphService',
+ 'ThemeService', 'FnService',
- function (_$log_, _ps_, _sus_, _gs_) {
+ function (_$log_, _ps_, _sus_, _gs_, _ts_, _fs_) {
$log = _$log_;
ps = _ps_;
sus = _sus_;
gs = _gs_;
+ ts = _ts_;
+ fs = _fs_;
return {
initInst: initInst,
destroyInst: destroyInst,
- addInstance: addInstance
+ addInstance: addInstance,
+ updateInstance: updateInstance,
+ removeInstance: removeInstance
};
}]);
}());
diff --git a/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js b/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js
index 9146cca..23d3153 100644
--- a/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js
@@ -36,7 +36,7 @@
it('should define api functions', function () {
expect(fs.areFunctions(wse, [
- 'sendEvent'
+ 'sendEvent', 'resetSid'
])).toBeTruthy();
});
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
index c294e2a..9bee3ec 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
@@ -39,15 +39,57 @@
it('should define api functions', function () {
expect(fs.areFunctions(sus, [
- 'createDragBehavior', 'loadGlow', 'cat7', 'translate'
+ 'createDragBehavior', 'loadGlow', 'cat7', 'translate', 'stripPx'
])).toBeTruthy();
});
// TODO: add unit tests for drag behavior
// TODO: add unit tests for loadGlow
- // TODO: add unit tests for cat7
+ // === cat7
+
+ it('should define two methods on the api', function () {
+ var cat7 = sus.cat7();
+ expect(fs.areFunctions(cat7, [
+ 'testCard', 'getColor'
+ ])).toBeTruthy();
+ });
+
+ it('should provide a certain shade of blue', function () {
+ expect(sus.cat7().getColor('foo', false, 'light')).toEqual('#3E5780');
+ });
+
+ it('should not matter what the ID really is for shade of blue', function () {
+ expect(sus.cat7().getColor('bar', false, 'light')).toEqual('#3E5780');
+ });
+
+ it('should provide different shade of blue for muted', function () {
+ expect(sus.cat7().getColor('foo', true, 'light')).toEqual('#A8B8CC');
+ });
+
+
+ it('should provide an alternate (dark) shade of blue', function () {
+ expect(sus.cat7().getColor('foo', false, 'dark')).toEqual('#3E5780');
+ });
+
+ it('should provide an alternate (dark) shade of blue for muted', function () {
+ expect(sus.cat7().getColor('foo', true, 'dark')).toEqual('#A8B8CC');
+ });
+
+ it('should iterate across the colors', function () {
+ expect(sus.cat7().getColor('foo', false, 'light')).toEqual('#3E5780');
+ expect(sus.cat7().getColor('bar', false, 'light')).toEqual('#78533B');
+ expect(sus.cat7().getColor('baz', false, 'light')).toEqual('#CB4D28');
+ expect(sus.cat7().getColor('goo', false, 'light')).toEqual('#018D61');
+ expect(sus.cat7().getColor('zoo', false, 'light')).toEqual('#8A2979');
+ expect(sus.cat7().getColor('pip', false, 'light')).toEqual('#006D73');
+ expect(sus.cat7().getColor('sdh', false, 'light')).toEqual('#56AF00');
+ // and cycle back to the first color for item #8
+ expect(sus.cat7().getColor('bri', false, 'light')).toEqual('#3E5780');
+ // and return the same color for the same ID
+ expect(sus.cat7().getColor('zoo', false, 'light')).toEqual('#8A2979');
+ });
// === translate()
diff --git a/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js b/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js
index c951fac..d70c87f 100644
--- a/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js
@@ -48,7 +48,7 @@
function verifyZoomerApi() {
expect(fs.areFunctions(zoomer, [
- 'panZoom', 'reset', 'translate', 'scale'
+ 'panZoom', 'reset', 'translate', 'scale', 'scaleExtent'
])).toBeTruthy();
}
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
index e5b7223..e21980d 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
@@ -38,7 +38,6 @@
$window.innerHeight = 200;
}));
-
// === Tests for isF()
it('isF(): null for undefined', function () {
expect(fs.isF(undefined)).toBeNull();
@@ -181,15 +180,33 @@
b: 'not-a-function'
}, ['b', 'a'])).toBeFalsy();
});
- it('areFunctions(): extraneous stuff ignored', function () {
+ it('areFunctions(): extraneous stuff NOT ignored', function () {
expect(fs.areFunctions({
a: function () {},
b: function () {},
c: 1,
d: 'foo'
+ }, ['a', 'b'])).toBeFalsy();
+ });
+ it('areFunctions(): extraneous stuff ignored (alternate fn)', function () {
+ expect(fs.areFunctionsNonStrict({
+ a: function () {},
+ b: function () {},
+ c: 1,
+ d: 'foo'
}, ['a', 'b'])).toBeTruthy();
});
+ // == use the now-tested areFunctions on our own api:
+ it('should define api functions', function () {
+ expect(fs.areFunctions(fs, [
+ 'isF', 'isA', 'isS', 'isO', 'contains',
+ 'areFunctions', 'areFunctionsNonStrict', 'windowSize', 'find'
+ ])).toBeTruthy();
+ });
+
+
+
// === Tests for windowSize()
it('windowSize(): noargs', function () {
@@ -215,4 +232,7 @@
expect(dim.width).toEqual(199);
expect(dim.height).toEqual(99);
});
+
+ // TODO: write unit tests for find()
+
});
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js
new file mode 100644
index 0000000..1ea4663
--- /dev/null
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Topo View -- Topo Instance Service - Unit Tests
+ */
+describe('factory: view/topo/topoInst.js', function() {
+ var $log, fs, tis;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
+
+ beforeEach(inject(function (_$log_, FnService, TopoInstService) {
+ $log = _$log_;
+ fs = FnService;
+ tis = TopoInstService;
+ }));
+
+ it('should define TopoInstService', function () {
+ expect(tis).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tis, [
+ 'initInst', 'destroyInst',
+ 'addInstance', 'updateInstance', 'removeInstance'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
diff --git a/web/gui/src/test/_karma/ev/migrate/ev_3_addInstance_B.json b/web/gui/src/test/_karma/ev/migrate/ev_3_addInstance_B.json
new file mode 100644
index 0000000..4313116
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/migrate/ev_3_addInstance_B.json
@@ -0,0 +1,14 @@
+{
+ "event": "addInstance",
+ "payload": {
+ "id": "instB",
+ "ip": "123.22.33.241",
+ "online": true,
+ "uiAttached": false,
+ "switches": 14,
+ "labels": [
+ "instB",
+ "123.22.33.241"
+ ]
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/migrate/ev_4_addInstance_C.json b/web/gui/src/test/_karma/ev/migrate/ev_4_addInstance_C.json
new file mode 100644
index 0000000..b3376c0
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/migrate/ev_4_addInstance_C.json
@@ -0,0 +1,14 @@
+{
+ "event": "addInstance",
+ "payload": {
+ "id": "instC",
+ "ip": "123.22.33.124",
+ "online": true,
+ "uiAttached": false,
+ "switches": 7,
+ "labels": [
+ "instC",
+ "123.22.33.124"
+ ]
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/migrate/ev_5_updateInstance_B.json b/web/gui/src/test/_karma/ev/migrate/ev_5_updateInstance_B.json
new file mode 100644
index 0000000..f3cc9c9
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/migrate/ev_5_updateInstance_B.json
@@ -0,0 +1,14 @@
+{
+ "event": "updateInstance",
+ "payload": {
+ "id": "instB",
+ "ip": "123.22.33.241",
+ "online": false,
+ "uiAttached": false,
+ "switches": 14,
+ "labels": [
+ "instB",
+ "123.22.33.241"
+ ]
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/migrate/ev_6_removeInstance_B.json b/web/gui/src/test/_karma/ev/migrate/ev_6_removeInstance_B.json
new file mode 100644
index 0000000..0ade799
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/migrate/ev_6_removeInstance_B.json
@@ -0,0 +1,14 @@
+{
+ "event": "removeInstance",
+ "payload": {
+ "id": "instB",
+ "ip": "123.22.33.241",
+ "online": false,
+ "uiAttached": false,
+ "switches": 14,
+ "labels": [
+ "instB",
+ "123.22.33.241"
+ ]
+ }
+}
diff --git a/web/gui/src/test/_karma/ev/migrate/ev_7_addInstance_D.json b/web/gui/src/test/_karma/ev/migrate/ev_7_addInstance_D.json
new file mode 100644
index 0000000..3ba5fca
--- /dev/null
+++ b/web/gui/src/test/_karma/ev/migrate/ev_7_addInstance_D.json
@@ -0,0 +1,14 @@
+{
+ "event": "addInstance",
+ "payload": {
+ "id": "instD",
+ "ip": "123.33.44.55",
+ "online": true,
+ "uiAttached": false,
+ "switches": 133,
+ "labels": [
+ "instD",
+ "123.33.44.55"
+ ]
+ }
+}