Simon Hunt

GUI -- TopoView - re-implemented All/Pkt/Opt filter radio buttons.

Change-Id: I41cf0eca60a685606a631c0edf4779d7730bb649
...@@ -57,3 +57,11 @@ ...@@ -57,3 +57,11 @@
57 .dark #mast .title { 57 .dark #mast .title {
58 color: #eee; 58 color: #eee;
59 } 59 }
60 +
61 +#mast-right {
62 + display: inline-block;
63 + padding-top: 8px;
64 + padding-right: 16px;
65 + float: right;
66 + /*border: 1px solid red;*/
67 +}
...\ No newline at end of file ...\ No newline at end of file
......
1 <!-- Masthead partial HTML --> 1 <!-- Masthead partial HTML -->
2 <img class="logo" src="../data/img/onos-logo.png" ng-click="mastCtrl.toggleNav()"> 2 <img class="logo" src="../data/img/onos-logo.png" ng-click="mastCtrl.toggleNav()">
3 <span class="title">Open Network Operating System</span> 3 <span class="title">Open Network Operating System</span>
4 +<div id="mast-right"></div>
......
...@@ -515,3 +515,40 @@ ...@@ -515,3 +515,40 @@
515 .dark #ov-topo svg .linkLabel text { 515 .dark #ov-topo svg .linkLabel text {
516 stroke: #777; 516 stroke: #777;
517 } 517 }
518 +
519 +
520 +/* radio buttons injected into masthead */
521 +
522 +#topo-radio-group span.radio {
523 + font-size: 10pt;
524 + margin: 4px 2px;
525 + padding: 1px 6px;
526 + -moz-border-radius: 3px;
527 + border-radius: 3px;
528 + cursor: pointer;
529 +}
530 +
531 +.light #topo-radio-group span.radio {
532 + border: 1px dotted #222;
533 + color: #eee;
534 +}
535 +.dark #topo-radio-group span.radio {
536 + border: 1px dotted #bbb;
537 + color: #888;
538 +}
539 +
540 +#topo-radio-group span.radio.active {
541 + padding: 1px 6px;
542 +}
543 +
544 +.light #topo-radio-group span.radio.active {
545 + background-color: #bbb;
546 + border: 1px solid #eee;
547 + color: #666;
548 +
549 +}
550 +.dark #topo-radio-group span.radio.active {
551 + background-color: #222;
552 + border: 1px solid #eee;
553 + color: #78a;
554 +}
......
...@@ -29,11 +29,129 @@ ...@@ -29,11 +29,129 @@
29 // api to topoForce 29 // api to topoForce
30 var api; 30 var api;
31 /* 31 /*
32 - node() // get ref to D3 selection of nodes 32 + node() // get ref to D3 selection of nodes
33 + link() // get ref to D3 selection of links
33 */ 34 */
34 35
35 - // internal state 36 + // which "layer" a particular item "belongs to"
37 + var layerLookup = {
38 + host: {
39 + endstation: 'pkt', // default, if host event does not define type
40 + router: 'pkt',
41 + bgpSpeaker: 'pkt'
42 + },
43 + device: {
44 + switch: 'pkt',
45 + roadm: 'opt'
46 + },
47 + link: {
48 + hostLink: 'pkt',
49 + direct: 'pkt',
50 + indirect: '',
51 + tunnel: '',
52 + optical: 'opt'
53 + }
54 + };
36 55
56 + var idPrefix = 'topo-rb-';
57 +
58 + var dispatch = {
59 + all: function () { suppressLayers(false); },
60 + pkt: function () { showLayer('pkt'); },
61 + opt: function () { showLayer('opt'); }
62 + },
63 + filterButtons = [
64 + { text: 'All Layers', id: 'all' },
65 + { text: 'Packet Only', id: 'pkt' },
66 + { text: 'Optical Only', id: 'opt' }
67 + ],
68 + btnG,
69 + btnDef = {},
70 + targetDiv;
71 +
72 +
73 + function injectButtons(div) {
74 + targetDiv = div;
75 +
76 + btnG = div.append('div').attr('id', 'topo-radio-group');
77 +
78 + filterButtons.forEach(function (btn, i) {
79 + var bid = btn.id,
80 + txt = btn.text,
81 + uid = idPrefix + bid,
82 + button = btnG.append('span')
83 + .attr({
84 + id: uid,
85 + 'class': 'radio'
86 + })
87 + .text(txt);
88 + btnDef[uid] = btn;
89 +
90 + if (i === 0) {
91 + button.classed('active', true);
92 + btnG.selected = bid;
93 + }
94 + });
95 +
96 + btnG.selectAll('span')
97 + .on('click', function () {
98 + var button = d3.select(this),
99 + uid = button.attr('id'),
100 + btn = btnDef[uid],
101 + act = button.classed('active');
102 +
103 + if (!act) {
104 + btnG.selectAll('span').classed('active', false);
105 + button.classed('active', true);
106 + btnG.selected = btn.id;
107 + clickAction(btn.id);
108 + }
109 + });
110 + }
111 +
112 + function clickAction(which) {
113 + dispatch[which]();
114 + }
115 +
116 + function selected() {
117 + return btnG ? btnG.selected : '';
118 + }
119 +
120 + // code to manipulate the nodes and links as per the filter settings
121 + function inLayer(d, layer) {
122 + var type = d.class === 'link' ? d.type() : d.type,
123 + look = layerLookup[d.class],
124 + lyr = look && look[type];
125 + return lyr === layer;
126 + }
127 +
128 + function unsuppressLayer(which) {
129 + api.node().each(function (d) {
130 + var node = d.el;
131 + if (inLayer(d, which)) {
132 + node.classed('suppressed', false);
133 + }
134 + });
135 +
136 + api.link().each(function (d) {
137 + var link = d.el;
138 + if (inLayer(d, which)) {
139 + link.classed('suppressed', false);
140 + }
141 + });
142 + }
143 +
144 + function suppressLayers(b) {
145 + api.node().classed('suppressed', b);
146 + api.link().classed('suppressed', b);
147 +// d3.selectAll('svg .port').classed('inactive', false);
148 +// d3.selectAll('svg .portText').classed('inactive', false);
149 + }
150 +
151 + function showLayer(which) {
152 + suppressLayers(true);
153 + unsuppressLayer(which);
154 + }
37 155
38 // === ----------------------------------------------------- 156 // === -----------------------------------------------------
39 // === MODULE DEFINITION === 157 // === MODULE DEFINITION ===
...@@ -52,15 +170,23 @@ ...@@ -52,15 +170,23 @@
52 tps = _tps_; 170 tps = _tps_;
53 tts = _tts_; 171 tts = _tts_;
54 172
55 - function initFilter(_api_) { 173 + function initFilter(_api_, div) {
56 api = _api_; 174 api = _api_;
175 + injectButtons(div);
57 } 176 }
58 177
59 - function destroyFilter() { } 178 + function destroyFilter() {
179 + targetDiv.select('#topo-radio-group').remove();
180 + btnG = null;
181 + btnDef = {};
182 + }
60 183
61 return { 184 return {
62 initFilter: initFilter, 185 initFilter: initFilter,
63 - destroyFilter: destroyFilter 186 + destroyFilter: destroyFilter,
187 +
188 + clickAction: clickAction,
189 + selected: selected
64 }; 190 };
65 }]); 191 }]);
66 }()); 192 }());
......
...@@ -23,7 +23,8 @@ ...@@ -23,7 +23,8 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, icfg, uplink; 26 + var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, fltr,
27 + icfg, uplink;
27 28
28 // configuration 29 // configuration
29 var labelConfig = { 30 var labelConfig = {
...@@ -1078,14 +1079,21 @@ ...@@ -1078,14 +1079,21 @@
1078 } 1079 }
1079 } 1080 }
1080 1081
1082 + function mkFilterApi(uplink) {
1083 + return {
1084 + node: function () { return node; },
1085 + link: function () { return link; }
1086 + };
1087 + }
1088 +
1081 angular.module('ovTopo') 1089 angular.module('ovTopo')
1082 .factory('TopoForceService', 1090 .factory('TopoForceService',
1083 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', 1091 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
1084 'FlashService', 'TopoInstService', 'TopoModelService', 1092 'FlashService', 'TopoInstService', 'TopoModelService',
1085 - 'TopoSelectService', 'TopoTrafficService', 1093 + 'TopoSelectService', 'TopoTrafficService', 'TopoFilterService',
1086 1094
1087 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, 1095 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
1088 - _tis_, _tms_, _tss_, _tts_) { 1096 + _tis_, _tms_, _tss_, _tts_, _fltr_) {
1089 $log = _$log_; 1097 $log = _$log_;
1090 fs = _fs_; 1098 fs = _fs_;
1091 sus = _sus_; 1099 sus = _sus_;
...@@ -1096,6 +1104,7 @@ ...@@ -1096,6 +1104,7 @@
1096 tms = _tms_; 1104 tms = _tms_;
1097 tss = _tss_; 1105 tss = _tss_;
1098 tts = _tts_; 1106 tts = _tts_;
1107 + fltr = _fltr_;
1099 1108
1100 icfg = is.iconConfig(); 1109 icfg = is.iconConfig();
1101 1110
...@@ -1117,6 +1126,7 @@ ...@@ -1117,6 +1126,7 @@
1117 tms.initModel(mkModelApi(uplink), dim); 1126 tms.initModel(mkModelApi(uplink), dim);
1118 tss.initSelect(mkSelectApi(uplink)); 1127 tss.initSelect(mkSelectApi(uplink));
1119 tts.initTraffic(mkTrafficApi(uplink)); 1128 tts.initTraffic(mkTrafficApi(uplink));
1129 + fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
1120 1130
1121 settings = angular.extend({}, defaultSettings, opts); 1131 settings = angular.extend({}, defaultSettings, opts);
1122 1132
...@@ -1151,6 +1161,7 @@ ...@@ -1151,6 +1161,7 @@
1151 } 1161 }
1152 1162
1153 function destroyForce() { 1163 function destroyForce() {
1164 + fltr.destroyFilter();
1154 tts.destroyTraffic(); 1165 tts.destroyTraffic();
1155 tss.destroySelect(); 1166 tss.destroySelect();
1156 tms.destroyModel(); 1167 tms.destroyModel();
......
...@@ -18,25 +18,80 @@ ...@@ -18,25 +18,80 @@
18 ONOS GUI -- Topo View -- Topo Filter Service - Unit Tests 18 ONOS GUI -- Topo View -- Topo Filter Service - Unit Tests
19 */ 19 */
20 describe('factory: view/topo/topoFilter.js', function() { 20 describe('factory: view/topo/topoFilter.js', function() {
21 - var $log, fs, filter; 21 + var $log, fs, fltr, d3Elem, api;
22 +
23 + var mockNodes = {
24 + each: function () {},
25 + classed: function () {}
26 + },
27 + mockLinks = {
28 + each: function () {},
29 + classed: function () {}
30 + };
22 31
23 beforeEach(module('ovTopo', 'onosUtil', 'onosLayer')); 32 beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
24 33
25 beforeEach(inject(function (_$log_, FnService, TopoFilterService) { 34 beforeEach(inject(function (_$log_, FnService, TopoFilterService) {
26 $log = _$log_; 35 $log = _$log_;
27 fs = FnService; 36 fs = FnService;
28 - filter = TopoFilterService; 37 + fltr = TopoFilterService;
38 + d3Elem = d3.select('body').append('div').attr('id', 'myMastDiv');
39 +
40 + api = {
41 + node: function () { return mockNodes; },
42 + link: function () { return mockLinks; }
43 + };
29 })); 44 }));
30 45
46 + afterEach(function () {
47 + d3.select('#myMastDiv').remove();
48 + });
49 +
31 it('should define TopoFilterService', function () { 50 it('should define TopoFilterService', function () {
32 - expect(filter).toBeDefined(); 51 + expect(fltr).toBeDefined();
33 }); 52 });
34 53
35 it('should define api functions', function () { 54 it('should define api functions', function () {
36 - expect(fs.areFunctions(filter, [ 55 + expect(fs.areFunctions(fltr, [
37 - 'initFilter', 'destroyFilter' 56 + 'initFilter', 'destroyFilter',
57 + 'clickAction', 'selected'
38 ])).toBeTruthy(); 58 ])).toBeTruthy();
39 }); 59 });
40 60
41 - // TODO: more tests... 61 + it('should inject the buttons into the given div', function () {
62 + fltr.initFilter(api, d3Elem);
63 + var grpdiv = d3Elem.select('#topo-radio-group');
64 + expect(grpdiv.size()).toBe(1);
65 +
66 + var btns = grpdiv.selectAll('span');
67 + expect(btns.size()).toBe(3);
68 +
69 + var prefix = 'topo-rb-',
70 + expIds = [ 'all', 'pkt', 'opt' ];
71 +
72 + btns.each(function (d, i) {
73 + var b = d3.select(this);
74 + expect(b.attr('id')).toEqual(prefix + expIds[i]);
75 + // 0th button is active - others are not
76 + expect(b.classed('active')).toEqual(i === 0);
77 + });
78 + });
79 +
80 + it('should remove the buttons from the given div', function () {
81 + fltr.initFilter(api, d3Elem);
82 + var grpdiv = d3Elem.select('#topo-radio-group');
83 + expect(grpdiv.size()).toBe(1);
84 +
85 + fltr.destroyFilter();
86 + grpdiv = d3Elem.select('#topo-radio-group');
87 + expect(grpdiv.size()).toBe(0);
88 + });
89 +
90 + it('should report the selected button', function () {
91 + fltr.initFilter(api, d3Elem);
92 + expect(fltr.selected()).toEqual('all');
93 + });
94 +
95 + // TODO: figure out how to trigger the click function on the spans..
96 +
42 }); 97 });
......