Simon Hunt

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

Change-Id: I41cf0eca60a685606a631c0edf4779d7730bb649
......@@ -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
......
<!-- 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>
......
......@@ -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;
}
......
......@@ -30,10 +30,128 @@
var api;
/*
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
};
}]);
}());
......
......@@ -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();
......
......@@ -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..
});
......