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