GUI -- TopoView - re-implemented All/Pkt/Opt filter radio buttons.
Change-Id: I41cf0eca60a685606a631c0edf4779d7730bb649
diff --git a/web/gui/src/main/webapp/app/fw/mast/mast.css b/web/gui/src/main/webapp/app/fw/mast/mast.css
index 38baba1..3c7d97d 100644
--- a/web/gui/src/main/webapp/app/fw/mast/mast.css
+++ b/web/gui/src/main/webapp/app/fw/mast/mast.css
@@ -57,3 +57,11 @@
.dark #mast .title {
color: #eee;
}
+
+#mast-right {
+ display: inline-block;
+ padding-top: 8px;
+ padding-right: 16px;
+ float: right;
+ /*border: 1px solid red;*/
+}
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/app/fw/mast/mast.html b/web/gui/src/main/webapp/app/fw/mast/mast.html
index e10d6ca..59c99a3 100644
--- a/web/gui/src/main/webapp/app/fw/mast/mast.html
+++ b/web/gui/src/main/webapp/app/fw/mast/mast.html
@@ -1,3 +1,4 @@
<!-- Masthead partial HTML -->
<img class="logo" src="../data/img/onos-logo.png" ng-click="mastCtrl.toggleNav()">
<span class="title">Open Network Operating System</span>
+<div id="mast-right"></div>
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index b9613d4..f725634 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -515,3 +515,40 @@
.dark #ov-topo svg .linkLabel text {
stroke: #777;
}
+
+
+/* radio buttons injected into masthead */
+
+#topo-radio-group span.radio {
+ font-size: 10pt;
+ margin: 4px 2px;
+ padding: 1px 6px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+ cursor: pointer;
+}
+
+.light #topo-radio-group span.radio {
+ border: 1px dotted #222;
+ color: #eee;
+}
+.dark #topo-radio-group span.radio {
+ border: 1px dotted #bbb;
+ color: #888;
+}
+
+#topo-radio-group span.radio.active {
+ padding: 1px 6px;
+}
+
+.light #topo-radio-group span.radio.active {
+ background-color: #bbb;
+ border: 1px solid #eee;
+ color: #666;
+
+}
+.dark #topo-radio-group span.radio.active {
+ background-color: #222;
+ border: 1px solid #eee;
+ color: #78a;
+}
diff --git a/web/gui/src/main/webapp/app/view/topo/topoFilter.js b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
index eba31ae..c2e7a62 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoFilter.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
@@ -29,11 +29,129 @@
// api to topoForce
var api;
/*
- node() // get ref to D3 selection of nodes
+ node() // get ref to D3 selection of nodes
+ link() // get ref to D3 selection of links
*/
- // internal state
+ // which "layer" a particular item "belongs to"
+ var layerLookup = {
+ host: {
+ endstation: 'pkt', // default, if host event does not define type
+ router: 'pkt',
+ bgpSpeaker: 'pkt'
+ },
+ device: {
+ switch: 'pkt',
+ roadm: 'opt'
+ },
+ link: {
+ hostLink: 'pkt',
+ direct: 'pkt',
+ indirect: '',
+ tunnel: '',
+ optical: 'opt'
+ }
+ };
+ var idPrefix = 'topo-rb-';
+
+ var dispatch = {
+ all: function () { suppressLayers(false); },
+ pkt: function () { showLayer('pkt'); },
+ opt: function () { showLayer('opt'); }
+ },
+ filterButtons = [
+ { text: 'All Layers', id: 'all' },
+ { text: 'Packet Only', id: 'pkt' },
+ { text: 'Optical Only', id: 'opt' }
+ ],
+ btnG,
+ btnDef = {},
+ targetDiv;
+
+
+ function injectButtons(div) {
+ targetDiv = div;
+
+ btnG = div.append('div').attr('id', 'topo-radio-group');
+
+ filterButtons.forEach(function (btn, i) {
+ var bid = btn.id,
+ txt = btn.text,
+ uid = idPrefix + bid,
+ button = btnG.append('span')
+ .attr({
+ id: uid,
+ 'class': 'radio'
+ })
+ .text(txt);
+ btnDef[uid] = btn;
+
+ if (i === 0) {
+ button.classed('active', true);
+ btnG.selected = bid;
+ }
+ });
+
+ btnG.selectAll('span')
+ .on('click', function () {
+ var button = d3.select(this),
+ uid = button.attr('id'),
+ btn = btnDef[uid],
+ act = button.classed('active');
+
+ if (!act) {
+ btnG.selectAll('span').classed('active', false);
+ button.classed('active', true);
+ btnG.selected = btn.id;
+ clickAction(btn.id);
+ }
+ });
+ }
+
+ function clickAction(which) {
+ dispatch[which]();
+ }
+
+ function selected() {
+ return btnG ? btnG.selected : '';
+ }
+
+ // code to manipulate the nodes and links as per the filter settings
+ function inLayer(d, layer) {
+ var type = d.class === 'link' ? d.type() : d.type,
+ look = layerLookup[d.class],
+ lyr = look && look[type];
+ return lyr === layer;
+ }
+
+ function unsuppressLayer(which) {
+ api.node().each(function (d) {
+ var node = d.el;
+ if (inLayer(d, which)) {
+ node.classed('suppressed', false);
+ }
+ });
+
+ api.link().each(function (d) {
+ var link = d.el;
+ if (inLayer(d, which)) {
+ link.classed('suppressed', false);
+ }
+ });
+ }
+
+ function suppressLayers(b) {
+ api.node().classed('suppressed', b);
+ api.link().classed('suppressed', b);
+// d3.selectAll('svg .port').classed('inactive', false);
+// d3.selectAll('svg .portText').classed('inactive', false);
+ }
+
+ function showLayer(which) {
+ suppressLayers(true);
+ unsuppressLayer(which);
+ }
// === -----------------------------------------------------
// === MODULE DEFINITION ===
@@ -52,15 +170,23 @@
tps = _tps_;
tts = _tts_;
- function initFilter(_api_) {
+ function initFilter(_api_, div) {
api = _api_;
+ injectButtons(div);
}
- function destroyFilter() { }
+ function destroyFilter() {
+ targetDiv.select('#topo-radio-group').remove();
+ btnG = null;
+ btnDef = {};
+ }
return {
initFilter: initFilter,
- destroyFilter: destroyFilter
+ destroyFilter: destroyFilter,
+
+ clickAction: clickAction,
+ selected: selected
};
}]);
}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 10e924f..7dadaeb 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -23,7 +23,8 @@
'use strict';
// injected refs
- var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, icfg, uplink;
+ var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, fltr,
+ icfg, uplink;
// configuration
var labelConfig = {
@@ -1078,14 +1079,21 @@
}
}
+ function mkFilterApi(uplink) {
+ return {
+ node: function () { return node; },
+ link: function () { return link; }
+ };
+ }
+
angular.module('ovTopo')
.factory('TopoForceService',
['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
'FlashService', 'TopoInstService', 'TopoModelService',
- 'TopoSelectService', 'TopoTrafficService',
+ 'TopoSelectService', 'TopoTrafficService', 'TopoFilterService',
function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
- _tis_, _tms_, _tss_, _tts_) {
+ _tis_, _tms_, _tss_, _tts_, _fltr_) {
$log = _$log_;
fs = _fs_;
sus = _sus_;
@@ -1096,6 +1104,7 @@
tms = _tms_;
tss = _tss_;
tts = _tts_;
+ fltr = _fltr_;
icfg = is.iconConfig();
@@ -1117,6 +1126,7 @@
tms.initModel(mkModelApi(uplink), dim);
tss.initSelect(mkSelectApi(uplink));
tts.initTraffic(mkTrafficApi(uplink));
+ fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
settings = angular.extend({}, defaultSettings, opts);
@@ -1151,6 +1161,7 @@
}
function destroyForce() {
+ fltr.destroyFilter();
tts.destroyTraffic();
tss.destroySelect();
tms.destroyModel();
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
index 983469c..79a4398 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
@@ -18,25 +18,80 @@
ONOS GUI -- Topo View -- Topo Filter Service - Unit Tests
*/
describe('factory: view/topo/topoFilter.js', function() {
- var $log, fs, filter;
+ var $log, fs, fltr, d3Elem, api;
+
+ var mockNodes = {
+ each: function () {},
+ classed: function () {}
+ },
+ mockLinks = {
+ each: function () {},
+ classed: function () {}
+ };
beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
beforeEach(inject(function (_$log_, FnService, TopoFilterService) {
$log = _$log_;
fs = FnService;
- filter = TopoFilterService;
+ fltr = TopoFilterService;
+ d3Elem = d3.select('body').append('div').attr('id', 'myMastDiv');
+
+ api = {
+ node: function () { return mockNodes; },
+ link: function () { return mockLinks; }
+ };
}));
+ afterEach(function () {
+ d3.select('#myMastDiv').remove();
+ });
+
it('should define TopoFilterService', function () {
- expect(filter).toBeDefined();
+ expect(fltr).toBeDefined();
});
it('should define api functions', function () {
- expect(fs.areFunctions(filter, [
- 'initFilter', 'destroyFilter'
+ expect(fs.areFunctions(fltr, [
+ 'initFilter', 'destroyFilter',
+ 'clickAction', 'selected'
])).toBeTruthy();
});
- // TODO: more tests...
+ it('should inject the buttons into the given div', function () {
+ fltr.initFilter(api, d3Elem);
+ var grpdiv = d3Elem.select('#topo-radio-group');
+ expect(grpdiv.size()).toBe(1);
+
+ var btns = grpdiv.selectAll('span');
+ expect(btns.size()).toBe(3);
+
+ var prefix = 'topo-rb-',
+ expIds = [ 'all', 'pkt', 'opt' ];
+
+ btns.each(function (d, i) {
+ var b = d3.select(this);
+ expect(b.attr('id')).toEqual(prefix + expIds[i]);
+ // 0th button is active - others are not
+ expect(b.classed('active')).toEqual(i === 0);
+ });
+ });
+
+ it('should remove the buttons from the given div', function () {
+ fltr.initFilter(api, d3Elem);
+ var grpdiv = d3Elem.select('#topo-radio-group');
+ expect(grpdiv.size()).toBe(1);
+
+ fltr.destroyFilter();
+ grpdiv = d3Elem.select('#topo-radio-group');
+ expect(grpdiv.size()).toBe(0);
+ });
+
+ it('should report the selected button', function () {
+ fltr.initFilter(api, d3Elem);
+ expect(fltr.selected()).toEqual('all');
+ });
+
+ // TODO: figure out how to trigger the click function on the spans..
+
});