Simon Hunt

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
...@@ -132,8 +132,97 @@ ...@@ -132,8 +132,97 @@
132 $log.warn('SvgUtilService: loadGlow -- To Be Implemented'); 132 $log.warn('SvgUtilService: loadGlow -- To Be Implemented');
133 } 133 }
134 134
135 + // --- Ordinal scales for 7 values.
136 + // TODO: tune colors for light and dark themes
137 + // Note: These colors look good on the white background. Still, need to tune for dark.
138 +
139 + // blue brown brick red sea green purple dark teal lime
140 + var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
141 + lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
142 +
143 + darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
144 + darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
145 +
146 + var colors= {
147 + light: {
148 + norm: d3.scale.ordinal().range(lightNorm),
149 + mute: d3.scale.ordinal().range(lightMute)
150 + },
151 + dark: {
152 + norm: d3.scale.ordinal().range(darkNorm),
153 + mute: d3.scale.ordinal().range(darkMute)
154 + }
155 + };
156 +
135 function cat7() { 157 function cat7() {
136 - $log.warn('SvgUtilService: cat7 -- To Be Implemented'); 158 + var tcid = 'd3utilTestCard';
159 +
160 + function getColor(id, muted, theme) {
161 + // NOTE: since we are lazily assigning domain ids, we need to
162 + // get the color from all 4 scales, to keep the domains
163 + // in sync.
164 + var ln = colors.light.norm(id),
165 + lm = colors.light.mute(id),
166 + dn = colors.dark.norm(id),
167 + dm = colors.dark.mute(id);
168 + if (theme === 'dark') {
169 + return muted ? dm : dn;
170 + } else {
171 + return muted ? lm : ln;
172 + }
173 + }
174 +
175 + function testCard(svg) {
176 + var g = svg.select('g#' + tcid),
177 + dom = d3.range(7),
178 + k, muted, theme, what;
179 +
180 + if (!g.empty()) {
181 + g.remove();
182 +
183 + } else {
184 + g = svg.append('g')
185 + .attr('id', tcid)
186 + .attr('transform', 'scale(4)translate(20,20)');
187 +
188 + for (k=0; k<4; k++) {
189 + muted = k%2;
190 + what = muted ? ' muted' : ' normal';
191 + theme = k < 2 ? 'light' : 'dark';
192 + dom.forEach(function (id, i) {
193 + var x = i * 20,
194 + y = k * 20,
195 + f = get(id, muted, theme);
196 + g.append('circle').attr({
197 + cx: x,
198 + cy: y,
199 + r: 5,
200 + fill: f
201 + });
202 + });
203 + g.append('rect').attr({
204 + x: 140,
205 + y: k * 20 - 5,
206 + width: 32,
207 + height: 10,
208 + rx: 2,
209 + fill: '#888'
210 + });
211 + g.append('text').text(theme + what)
212 + .attr({
213 + x: 142,
214 + y: k * 20 + 2,
215 + fill: 'white'
216 + })
217 + .style('font-size', '4pt');
218 + }
219 + }
220 + }
221 +
222 + return {
223 + testCard: testCard,
224 + getColor: getColor
225 + };
137 } 226 }
138 227
139 function translate(x, y) { 228 function translate(x, y) {
......
...@@ -45,7 +45,38 @@ ...@@ -45,7 +45,38 @@
45 45
46 // Returns true if all names in the array are defined as functions 46 // Returns true if all names in the array are defined as functions
47 // on the given api object; false otherwise. 47 // on the given api object; false otherwise.
48 + // Also returns false if there are properties on the api that are NOT
49 + // listed in the array of names.
48 function areFunctions(api, fnNames) { 50 function areFunctions(api, fnNames) {
51 + var fnLookup = {},
52 + extraFound = false;
53 +
54 + if (!isA(fnNames)) {
55 + return false;
56 + }
57 + var n = fnNames.length,
58 + i, name;
59 + for (i=0; i<n; i++) {
60 + name = fnNames[i];
61 + if (!isF(api[name])) {
62 + return false;
63 + }
64 + fnLookup[name] = true;
65 + }
66 +
67 + // check for properties on the API that are not listed in the array,
68 + angular.forEach(api, function (value, key) {
69 + if (!fnLookup[key]) {
70 + extraFound = true;
71 + }
72 + });
73 + return !extraFound;
74 + }
75 +
76 + // Returns true if all names in the array are defined as functions
77 + // on the given api object; false otherwise. This is a non-strict version
78 + // that does not care about other properties on the api.
79 + function areFunctionsNonStrict(api, fnNames) {
49 if (!isA(fnNames)) { 80 if (!isA(fnNames)) {
50 return false; 81 return false;
51 } 82 }
...@@ -71,6 +102,21 @@ ...@@ -71,6 +102,21 @@
71 }; 102 };
72 } 103 }
73 104
105 + // search through an array of objects, looking for the one with the
106 + // tagged property matching the given key. tag defaults to 'id'.
107 + // returns the index of the matching object, or -1 for no match.
108 + function find(key, array, tag) {
109 + var _tag = tag || 'id',
110 + idx, n, d;
111 + for (idx = 0, n = array.length; idx < n; idx++) {
112 + d = array[idx];
113 + if (d[_tag] === key) {
114 + return idx;
115 + }
116 + }
117 + return -1;
118 + }
119 +
74 angular.module('onosUtil') 120 angular.module('onosUtil')
75 .factory('FnService', ['$window', function (_$window_) { 121 .factory('FnService', ['$window', function (_$window_) {
76 $window = _$window_; 122 $window = _$window_;
...@@ -82,7 +128,9 @@ ...@@ -82,7 +128,9 @@
82 isO: isO, 128 isO: isO,
83 contains: contains, 129 contains: contains,
84 areFunctions: areFunctions, 130 areFunctions: areFunctions,
85 - windowSize: windowSize 131 + areFunctionsNonStrict: areFunctionsNonStrict,
132 + windowSize: windowSize,
133 + find: find
86 }; 134 };
87 }]); 135 }]);
88 136
......
...@@ -30,7 +30,9 @@ ...@@ -30,7 +30,9 @@
30 30
31 var evHandler = { 31 var evHandler = {
32 showSummary: showSummary, 32 showSummary: showSummary,
33 - addInstance: addInstance 33 + addInstance: addInstance,
34 + updateInstance: updateInstance,
35 + removeInstance: removeInstance
34 // TODO: implement remaining handlers.. 36 // TODO: implement remaining handlers..
35 37
36 }; 38 };
...@@ -51,6 +53,16 @@ ...@@ -51,6 +53,16 @@
51 tis.addInstance(ev.payload); 53 tis.addInstance(ev.payload);
52 } 54 }
53 55
56 + function updateInstance(ev) {
57 + $log.debug(' **** Update Instance **** ', ev.payload);
58 + tis.updateInstance(ev.payload);
59 + }
60 +
61 + function removeInstance(ev) {
62 + $log.debug(' **** Remove Instance **** ', ev.payload);
63 + tis.removeInstance(ev.payload);
64 + }
65 +
54 // ========================== 66 // ==========================
55 67
56 var dispatcher = { 68 var dispatcher = {
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, ps, sus, gs; 26 + var $log, ps, sus, gs, ts, fs;
27 27
28 // configuration 28 // configuration
29 var instCfg = { 29 var instCfg = {
...@@ -79,6 +79,23 @@ ...@@ -79,6 +79,23 @@
79 } 79 }
80 } 80 }
81 81
82 + function removeInstance(data) {
83 + var id = data.id,
84 + d = onosInstances[id];
85 + if (d) {
86 + var idx = fs.find(id, onosOrder);
87 + if (idx >= 0) {
88 + onosOrder.splice(idx, 1);
89 + }
90 + delete onosInstances[id];
91 + updateInstances();
92 + } else {
93 + logicError('removeInstance lookup fail. ID = "' + id + '"');
94 + }
95 + }
96 +
97 + // ==========================
98 +
82 function computeDim(self) { 99 function computeDim(self) {
83 var css = window.getComputedStyle(self); 100 var css = window.getComputedStyle(self);
84 return { 101 return {
...@@ -143,9 +160,7 @@ ...@@ -143,9 +160,7 @@
143 } 160 }
144 161
145 function instColor(id, online) { 162 function instColor(id, online) {
146 - // TODO: fix this.. 163 + return sus.cat7().getColor(id, !online, ts.theme());
147 - //return cat7.get(id, !online, network.view.getTheme());
148 - return '#3E5780';
149 } 164 }
150 165
151 // ============================== 166 // ==============================
...@@ -288,17 +303,22 @@ ...@@ -288,17 +303,22 @@
288 angular.module('ovTopo') 303 angular.module('ovTopo')
289 .factory('TopoInstService', 304 .factory('TopoInstService',
290 ['$log', 'PanelService', 'SvgUtilService', 'GlyphService', 305 ['$log', 'PanelService', 'SvgUtilService', 'GlyphService',
306 + 'ThemeService', 'FnService',
291 307
292 - function (_$log_, _ps_, _sus_, _gs_) { 308 + function (_$log_, _ps_, _sus_, _gs_, _ts_, _fs_) {
293 $log = _$log_; 309 $log = _$log_;
294 ps = _ps_; 310 ps = _ps_;
295 sus = _sus_; 311 sus = _sus_;
296 gs = _gs_; 312 gs = _gs_;
313 + ts = _ts_;
314 + fs = _fs_;
297 315
298 return { 316 return {
299 initInst: initInst, 317 initInst: initInst,
300 destroyInst: destroyInst, 318 destroyInst: destroyInst,
301 - addInstance: addInstance 319 + addInstance: addInstance,
320 + updateInstance: updateInstance,
321 + removeInstance: removeInstance
302 }; 322 };
303 }]); 323 }]);
304 }()); 324 }());
......
...@@ -36,7 +36,7 @@ describe('factory: fw/remote/wsevent.js', function () { ...@@ -36,7 +36,7 @@ describe('factory: fw/remote/wsevent.js', function () {
36 36
37 it('should define api functions', function () { 37 it('should define api functions', function () {
38 expect(fs.areFunctions(wse, [ 38 expect(fs.areFunctions(wse, [
39 - 'sendEvent' 39 + 'sendEvent', 'resetSid'
40 ])).toBeTruthy(); 40 ])).toBeTruthy();
41 }); 41 });
42 42
......
...@@ -39,15 +39,57 @@ describe('factory: fw/svg/svgUtil.js', function() { ...@@ -39,15 +39,57 @@ describe('factory: fw/svg/svgUtil.js', function() {
39 39
40 it('should define api functions', function () { 40 it('should define api functions', function () {
41 expect(fs.areFunctions(sus, [ 41 expect(fs.areFunctions(sus, [
42 - 'createDragBehavior', 'loadGlow', 'cat7', 'translate' 42 + 'createDragBehavior', 'loadGlow', 'cat7', 'translate', 'stripPx'
43 ])).toBeTruthy(); 43 ])).toBeTruthy();
44 }); 44 });
45 45
46 46
47 // TODO: add unit tests for drag behavior 47 // TODO: add unit tests for drag behavior
48 // TODO: add unit tests for loadGlow 48 // TODO: add unit tests for loadGlow
49 - // TODO: add unit tests for cat7
50 49
50 + // === cat7
51 +
52 + it('should define two methods on the api', function () {
53 + var cat7 = sus.cat7();
54 + expect(fs.areFunctions(cat7, [
55 + 'testCard', 'getColor'
56 + ])).toBeTruthy();
57 + });
58 +
59 + it('should provide a certain shade of blue', function () {
60 + expect(sus.cat7().getColor('foo', false, 'light')).toEqual('#3E5780');
61 + });
62 +
63 + it('should not matter what the ID really is for shade of blue', function () {
64 + expect(sus.cat7().getColor('bar', false, 'light')).toEqual('#3E5780');
65 + });
66 +
67 + it('should provide different shade of blue for muted', function () {
68 + expect(sus.cat7().getColor('foo', true, 'light')).toEqual('#A8B8CC');
69 + });
70 +
71 +
72 + it('should provide an alternate (dark) shade of blue', function () {
73 + expect(sus.cat7().getColor('foo', false, 'dark')).toEqual('#3E5780');
74 + });
75 +
76 + it('should provide an alternate (dark) shade of blue for muted', function () {
77 + expect(sus.cat7().getColor('foo', true, 'dark')).toEqual('#A8B8CC');
78 + });
79 +
80 + it('should iterate across the colors', function () {
81 + expect(sus.cat7().getColor('foo', false, 'light')).toEqual('#3E5780');
82 + expect(sus.cat7().getColor('bar', false, 'light')).toEqual('#78533B');
83 + expect(sus.cat7().getColor('baz', false, 'light')).toEqual('#CB4D28');
84 + expect(sus.cat7().getColor('goo', false, 'light')).toEqual('#018D61');
85 + expect(sus.cat7().getColor('zoo', false, 'light')).toEqual('#8A2979');
86 + expect(sus.cat7().getColor('pip', false, 'light')).toEqual('#006D73');
87 + expect(sus.cat7().getColor('sdh', false, 'light')).toEqual('#56AF00');
88 + // and cycle back to the first color for item #8
89 + expect(sus.cat7().getColor('bri', false, 'light')).toEqual('#3E5780');
90 + // and return the same color for the same ID
91 + expect(sus.cat7().getColor('zoo', false, 'light')).toEqual('#8A2979');
92 + });
51 93
52 // === translate() 94 // === translate()
53 95
......
...@@ -48,7 +48,7 @@ describe('factory: fw/svg/zoom.js', function() { ...@@ -48,7 +48,7 @@ describe('factory: fw/svg/zoom.js', function() {
48 48
49 function verifyZoomerApi() { 49 function verifyZoomerApi() {
50 expect(fs.areFunctions(zoomer, [ 50 expect(fs.areFunctions(zoomer, [
51 - 'panZoom', 'reset', 'translate', 'scale' 51 + 'panZoom', 'reset', 'translate', 'scale', 'scaleExtent'
52 ])).toBeTruthy(); 52 ])).toBeTruthy();
53 } 53 }
54 54
......
...@@ -38,7 +38,6 @@ describe('factory: fw/util/fn.js', function() { ...@@ -38,7 +38,6 @@ describe('factory: fw/util/fn.js', function() {
38 $window.innerHeight = 200; 38 $window.innerHeight = 200;
39 })); 39 }));
40 40
41 -
42 // === Tests for isF() 41 // === Tests for isF()
43 it('isF(): null for undefined', function () { 42 it('isF(): null for undefined', function () {
44 expect(fs.isF(undefined)).toBeNull(); 43 expect(fs.isF(undefined)).toBeNull();
...@@ -181,15 +180,33 @@ describe('factory: fw/util/fn.js', function() { ...@@ -181,15 +180,33 @@ describe('factory: fw/util/fn.js', function() {
181 b: 'not-a-function' 180 b: 'not-a-function'
182 }, ['b', 'a'])).toBeFalsy(); 181 }, ['b', 'a'])).toBeFalsy();
183 }); 182 });
184 - it('areFunctions(): extraneous stuff ignored', function () { 183 + it('areFunctions(): extraneous stuff NOT ignored', function () {
185 expect(fs.areFunctions({ 184 expect(fs.areFunctions({
186 a: function () {}, 185 a: function () {},
187 b: function () {}, 186 b: function () {},
188 c: 1, 187 c: 1,
189 d: 'foo' 188 d: 'foo'
189 + }, ['a', 'b'])).toBeFalsy();
190 + });
191 + it('areFunctions(): extraneous stuff ignored (alternate fn)', function () {
192 + expect(fs.areFunctionsNonStrict({
193 + a: function () {},
194 + b: function () {},
195 + c: 1,
196 + d: 'foo'
190 }, ['a', 'b'])).toBeTruthy(); 197 }, ['a', 'b'])).toBeTruthy();
191 }); 198 });
192 199
200 + // == use the now-tested areFunctions on our own api:
201 + it('should define api functions', function () {
202 + expect(fs.areFunctions(fs, [
203 + 'isF', 'isA', 'isS', 'isO', 'contains',
204 + 'areFunctions', 'areFunctionsNonStrict', 'windowSize', 'find'
205 + ])).toBeTruthy();
206 + });
207 +
208 +
209 +
193 210
194 // === Tests for windowSize() 211 // === Tests for windowSize()
195 it('windowSize(): noargs', function () { 212 it('windowSize(): noargs', function () {
...@@ -215,4 +232,7 @@ describe('factory: fw/util/fn.js', function() { ...@@ -215,4 +232,7 @@ describe('factory: fw/util/fn.js', function() {
215 expect(dim.width).toEqual(199); 232 expect(dim.width).toEqual(199);
216 expect(dim.height).toEqual(99); 233 expect(dim.height).toEqual(99);
217 }); 234 });
235 +
236 + // TODO: write unit tests for find()
237 +
218 }); 238 });
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/*
18 + ONOS GUI -- Topo View -- Topo Instance Service - Unit Tests
19 + */
20 +describe('factory: view/topo/topoInst.js', function() {
21 + var $log, fs, tis;
22 +
23 + beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
24 +
25 + beforeEach(inject(function (_$log_, FnService, TopoInstService) {
26 + $log = _$log_;
27 + fs = FnService;
28 + tis = TopoInstService;
29 + }));
30 +
31 + it('should define TopoInstService', function () {
32 + expect(tis).toBeDefined();
33 + });
34 +
35 + it('should define api functions', function () {
36 + expect(fs.areFunctions(tis, [
37 + 'initInst', 'destroyInst',
38 + 'addInstance', 'updateInstance', 'removeInstance'
39 + ])).toBeTruthy();
40 + });
41 +
42 + // TODO: more tests...
43 +});
1 +{
2 + "event": "addInstance",
3 + "payload": {
4 + "id": "instB",
5 + "ip": "123.22.33.241",
6 + "online": true,
7 + "uiAttached": false,
8 + "switches": 14,
9 + "labels": [
10 + "instB",
11 + "123.22.33.241"
12 + ]
13 + }
14 +}
1 +{
2 + "event": "addInstance",
3 + "payload": {
4 + "id": "instC",
5 + "ip": "123.22.33.124",
6 + "online": true,
7 + "uiAttached": false,
8 + "switches": 7,
9 + "labels": [
10 + "instC",
11 + "123.22.33.124"
12 + ]
13 + }
14 +}
1 +{
2 + "event": "updateInstance",
3 + "payload": {
4 + "id": "instB",
5 + "ip": "123.22.33.241",
6 + "online": false,
7 + "uiAttached": false,
8 + "switches": 14,
9 + "labels": [
10 + "instB",
11 + "123.22.33.241"
12 + ]
13 + }
14 +}
1 +{
2 + "event": "removeInstance",
3 + "payload": {
4 + "id": "instB",
5 + "ip": "123.22.33.241",
6 + "online": false,
7 + "uiAttached": false,
8 + "switches": 14,
9 + "labels": [
10 + "instB",
11 + "123.22.33.241"
12 + ]
13 + }
14 +}
1 +{
2 + "event": "addInstance",
3 + "payload": {
4 + "id": "instD",
5 + "ip": "123.33.44.55",
6 + "online": true,
7 + "uiAttached": false,
8 + "switches": 133,
9 + "labels": [
10 + "instD",
11 + "123.33.44.55"
12 + ]
13 + }
14 +}