Simon Hunt
Committed by Gerrit Code Review

ONOS-2186 - GUI Topo Overlay - (WIP)

- Basic ability to visually suppress (two levels) other links/nodes, added to Highlights model.
- Added javadocs to Highlights class.
- Added unit tests for Highlights and TopoJson classes.

Change-Id: Id7a9990dcbad20139a4dab89cf54e476c2174ec0
......@@ -21,6 +21,8 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Encapsulates highlights to be applied to the topology view, such as
* highlighting links, displaying link labels, perhaps even decorating
......@@ -28,36 +30,115 @@ import java.util.Set;
*/
public class Highlights {
private static final String EMPTY = "";
private static final String MIN = "min";
private static final String MAX = "max";
/**
* A notion of amount.
*/
public enum Amount {
ZERO(EMPTY),
MINIMALLY(MIN),
MAXIMALLY(MAX);
private final String s;
Amount(String str) {
s = str;
}
@Override
public String toString() {
return s;
}
}
private final Set<DeviceHighlight> devices = new HashSet<>();
private final Set<HostHighlight> hosts = new HashSet<>();
private final Set<LinkHighlight> links = new HashSet<>();
private Amount subdueLevel = Amount.ZERO;
public Highlights add(DeviceHighlight d) {
devices.add(d);
/**
* Adds highlighting information for a device.
*
* @param dh device highlight
* @return self, for chaining
*/
public Highlights add(DeviceHighlight dh) {
devices.add(dh);
return this;
}
public Highlights add(HostHighlight h) {
hosts.add(h);
/**
* Adds highlighting information for a host.
*
* @param hh host highlight
* @return self, for chaining
*/
public Highlights add(HostHighlight hh) {
hosts.add(hh);
return this;
}
/**
* Adds highlighting information for a link.
*
* @param lh link highlight
* @return self, for chaining
*/
public Highlights add(LinkHighlight lh) {
links.add(lh);
return this;
}
/**
* Marks the amount by which all other elements (devices, hosts, links)
* not explicitly referenced here will be "subdued" visually.
*
* @param amount amount to subdue other elements
* @return self, for chaining
*/
public Highlights subdueAllElse(Amount amount) {
subdueLevel = checkNotNull(amount);
return this;
}
/**
* Returns the set of device highlights.
*
* @return device highlights
*/
public Set<DeviceHighlight> devices() {
return Collections.unmodifiableSet(devices);
}
/**
* Returns the set of host highlights.
*
* @return host highlights
*/
public Set<HostHighlight> hosts() {
return Collections.unmodifiableSet(hosts);
}
/**
* Returns the set of link highlights.
*
* @return link highlights
*/
public Set<LinkHighlight> links() {
return Collections.unmodifiableSet(links);
}
/**
* Returns the amount by which all other elements not explicitly
* referenced here should be "subdued".
*
* @return amount to subdue other elements
*/
public Amount subdueLevel() {
return subdueLevel;
}
}
......
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.onosproject.ui.topo;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link Highlights}.
*/
public class HighlightsTest {
private Highlights hl;
@Test
public void basic() {
hl = new Highlights();
assertEquals("devices", 0, hl.devices().size());
assertEquals("hosts", 0, hl.hosts().size());
assertEquals("links", 0, hl.links().size());
assertEquals("sudue", Highlights.Amount.ZERO, hl.subdueLevel());
}
// NOTE: further unit tests involving the Highlights class are done
// in TopoJsonTest.
}
......@@ -35,14 +35,14 @@ import org.onosproject.net.intent.OpticalPathIntent;
import org.onosproject.net.intent.PathIntent;
import org.onosproject.net.statistic.Load;
import org.onosproject.ui.impl.topo.IntentSelection;
import org.onosproject.ui.topo.NodeSelection;
import org.onosproject.ui.impl.topo.ServicesBundle;
import org.onosproject.ui.topo.TopoUtils;
import org.onosproject.ui.impl.topo.TopoIntentFilter;
import org.onosproject.ui.impl.topo.TrafficClass;
import org.onosproject.ui.impl.topo.TrafficLink;
import org.onosproject.ui.impl.topo.TrafficLinkMap;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.NodeSelection;
import org.onosproject.ui.topo.TopoUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......
......@@ -31,19 +31,21 @@ import org.onosproject.ui.topo.PropertyPanel;
* JSON utilities for the Topology View.
*/
public final class TopoJson {
private static final String DEVICES = "devices";
private static final String HOSTS = "hosts";
private static final String LINKS = "links";
// package-private for unit test access
static final String DEVICES = "devices";
static final String HOSTS = "hosts";
static final String LINKS = "links";
static final String SUBDUE = "subdue";
private static final String ID = "id";
private static final String LABEL = "label";
private static final String CSS = "css";
static final String ID = "id";
static final String LABEL = "label";
static final String CSS = "css";
private static final String TITLE = "title";
private static final String TYPE = "type";
private static final String PROP_ORDER = "propOrder";
private static final String PROPS = "props";
private static final String BUTTONS = "buttons";
static final String TITLE = "title";
static final String TYPE = "type";
static final String PROP_ORDER = "propOrder";
static final String PROPS = "props";
static final String BUTTONS = "buttons";
private static final ObjectMapper MAPPER = new ObjectMapper();
......@@ -80,6 +82,10 @@ public final class TopoJson {
highlights.hosts().forEach(hh -> hosts.add(json(hh)));
highlights.links().forEach(lh -> links.add(json(lh)));
Highlights.Amount toSubdue = highlights.subdueLevel();
if (!toSubdue.equals(Highlights.Amount.ZERO)) {
payload.put(SUBDUE, toSubdue.toString());
}
return payload;
}
......
......@@ -323,6 +323,10 @@ html[data-platform='iPad'] #topo-p-detail {
/* --- Topo Nodes --- */
#ov-topo svg .suppressed {
opacity: 0.5 !important;
}
#ov-topo svg .suppressedmax {
opacity: 0.2 !important;
}
......
......@@ -33,6 +33,8 @@
link() // get ref to D3 selection of links
*/
var smax = 'suppressedmax';
// which "layer" a particular item "belongs to"
var layerLookup = {
host: {
......@@ -93,23 +95,21 @@
api.node().each(function (d) {
var node = d.el;
if (inLayer(d, which)) {
node.classed('suppressed', false);
node.classed(smax, false);
}
});
api.link().each(function (d) {
var link = d.el;
if (inLayer(d, which)) {
link.classed('suppressed', false);
link.classed(smax, 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);
api.node().classed(smax, b);
api.link().classed(smax, b);
}
function showLayer(which) {
......
......@@ -491,16 +491,37 @@
suppressLayers(true);
node.each(function (n) {
if (n.master === id) {
n.el.classed('suppressed', false);
n.el.classed('suppressedmax', false);
}
});
}
function suppressLayers(b) {
node.classed('suppressed', b);
link.classed('suppressed', b);
// d3.selectAll('svg .port').classed('inactive', b);
// d3.selectAll('svg .portText').classed('inactive', b);
function supAmt(less) {
return less ? "suppressed" : "suppressedmax";
}
function suppressLayers(b, less) {
var cls = supAmt(less);
node.classed(cls, b);
link.classed(cls, b);
}
function unsuppressNode(id, less) {
var cls = supAmt(less);
node.each(function (n) {
if (n.id === id) {
n.el.classed(cls, false);
}
});
}
function unsuppressLink(id, less) {
var cls = supAmt(less);
link.each(function (n) {
if (n.id === id) {
n.el.classed(cls, false);
}
});
}
function showBadLinks() {
......@@ -900,8 +921,12 @@
return {
clearLinkTrafficStyle: clearLinkTrafficStyle,
removeLinkLabels: removeLinkLabels,
findLinkById: tms.findLinkById,
updateLinks: updateLinks,
findLinkById: tms.findLinkById
updateNodes: updateNodes,
supLayers: suppressLayers,
unsupNode: unsuppressNode,
unsupLink: unsuppressLink
};
}
......
......@@ -294,18 +294,34 @@
}
function showHighlights(data) {
var less;
/*
API to topoForce
clearLinkTrafficStyle()
removeLinkLabels()
updateLinks()
findLinkById( id )
updateLinks()
updateNodes()
supLayers( bool, [less] )
unsupNode( id, [less] )
unsupLink( id, [less] )
*/
// TODO: clear node highlighting
api.clearLinkTrafficStyle();
api.removeLinkLabels();
// handle element suppression
if (data.subdue) {
less = data.subdue === 'min';
api.supLayers(true, less);
} else {
api.supLayers(false);
api.supLayers(false, true);
}
// TODO: device and host highlights
data.links.forEach(function (lnk) {
......@@ -314,6 +330,7 @@
units, portcls, magnitude;
if (ldata && !ldata.el.empty()) {
api.unsupLink(ldata.id, less);
ldata.el.classed(lnk.css, true);
ldata.label = lab;
......@@ -334,7 +351,7 @@
}
});
// TODO: api.updateNodes()
api.updateNodes();
api.updateLinks();
}
......
/*
* Copyright 2015 Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.onosproject.ui.impl.topo;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;
import org.onosproject.ui.JsonUtils;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.Highlights.Amount;
import static org.junit.Assert.assertEquals;
/**
* Unit tests for {@link TopoJson}.
*/
public class TopoJsonTest {
private ObjectNode payload;
private void checkArrayLength(String key, int expLen) {
ArrayNode a = (ArrayNode) payload.get(key);
assertEquals("wrong size: " + key, expLen, a.size());
}
private void checkEmptyArrays() {
checkArrayLength(TopoJson.DEVICES, 0);
checkArrayLength(TopoJson.HOSTS, 0);
checkArrayLength(TopoJson.LINKS, 0);
}
@Test
public void basicHighlights() {
Highlights h = new Highlights();
payload = TopoJson.json(h);
checkEmptyArrays();
String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
assertEquals("subdue", "", subdue);
}
@Test
public void subdueMinimalHighlights() {
Highlights h = new Highlights().subdueAllElse(Amount.MINIMALLY);
payload = TopoJson.json(h);
checkEmptyArrays();
String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
assertEquals("not min", "min", subdue);
}
@Test
public void subdueMaximalHighlights() {
Highlights h = new Highlights().subdueAllElse(Amount.MAXIMALLY);
payload = TopoJson.json(h);
checkEmptyArrays();
String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
assertEquals("not max", "max", subdue);
}
}