GUI -- TopoView - re-implemented All/Pkt/Opt filter radio buttons.
Change-Id: I41cf0eca60a685606a631c0edf4779d7730bb649
Showing
6 changed files
with
251 additions
and
13 deletions
... | @@ -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 | +} | ... | ... |
... | @@ -30,10 +30,128 @@ | ... | @@ -30,10 +30,128 @@ |
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 | + }; | ||
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 | + } | ||
36 | 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 | }); | ... | ... |
-
Please register or login to post a comment