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; ...@@ -21,6 +21,8 @@ import java.util.Collections;
21 import java.util.HashSet; 21 import java.util.HashSet;
22 import java.util.Set; 22 import java.util.Set;
23 23
24 +import static com.google.common.base.Preconditions.checkNotNull;
25 +
24 /** 26 /**
25 * Encapsulates highlights to be applied to the topology view, such as 27 * Encapsulates highlights to be applied to the topology view, such as
26 * highlighting links, displaying link labels, perhaps even decorating 28 * highlighting links, displaying link labels, perhaps even decorating
...@@ -28,36 +30,115 @@ import java.util.Set; ...@@ -28,36 +30,115 @@ import java.util.Set;
28 */ 30 */
29 public class Highlights { 31 public class Highlights {
30 32
33 + private static final String EMPTY = "";
34 + private static final String MIN = "min";
35 + private static final String MAX = "max";
36 +
37 + /**
38 + * A notion of amount.
39 + */
40 + public enum Amount {
41 + ZERO(EMPTY),
42 + MINIMALLY(MIN),
43 + MAXIMALLY(MAX);
44 +
45 + private final String s;
46 + Amount(String str) {
47 + s = str;
48 + }
49 +
50 + @Override
51 + public String toString() {
52 + return s;
53 + }
54 + }
55 +
31 private final Set<DeviceHighlight> devices = new HashSet<>(); 56 private final Set<DeviceHighlight> devices = new HashSet<>();
32 private final Set<HostHighlight> hosts = new HashSet<>(); 57 private final Set<HostHighlight> hosts = new HashSet<>();
33 private final Set<LinkHighlight> links = new HashSet<>(); 58 private final Set<LinkHighlight> links = new HashSet<>();
34 59
60 + private Amount subdueLevel = Amount.ZERO;
35 61
36 - public Highlights add(DeviceHighlight d) { 62 +
37 - devices.add(d); 63 + /**
64 + * Adds highlighting information for a device.
65 + *
66 + * @param dh device highlight
67 + * @return self, for chaining
68 + */
69 + public Highlights add(DeviceHighlight dh) {
70 + devices.add(dh);
38 return this; 71 return this;
39 } 72 }
40 73
41 - public Highlights add(HostHighlight h) { 74 + /**
42 - hosts.add(h); 75 + * Adds highlighting information for a host.
76 + *
77 + * @param hh host highlight
78 + * @return self, for chaining
79 + */
80 + public Highlights add(HostHighlight hh) {
81 + hosts.add(hh);
43 return this; 82 return this;
44 } 83 }
45 84
85 + /**
86 + * Adds highlighting information for a link.
87 + *
88 + * @param lh link highlight
89 + * @return self, for chaining
90 + */
46 public Highlights add(LinkHighlight lh) { 91 public Highlights add(LinkHighlight lh) {
47 links.add(lh); 92 links.add(lh);
48 return this; 93 return this;
49 } 94 }
50 95
96 + /**
97 + * Marks the amount by which all other elements (devices, hosts, links)
98 + * not explicitly referenced here will be "subdued" visually.
99 + *
100 + * @param amount amount to subdue other elements
101 + * @return self, for chaining
102 + */
103 + public Highlights subdueAllElse(Amount amount) {
104 + subdueLevel = checkNotNull(amount);
105 + return this;
106 + }
51 107
108 + /**
109 + * Returns the set of device highlights.
110 + *
111 + * @return device highlights
112 + */
52 public Set<DeviceHighlight> devices() { 113 public Set<DeviceHighlight> devices() {
53 return Collections.unmodifiableSet(devices); 114 return Collections.unmodifiableSet(devices);
54 } 115 }
55 116
117 + /**
118 + * Returns the set of host highlights.
119 + *
120 + * @return host highlights
121 + */
56 public Set<HostHighlight> hosts() { 122 public Set<HostHighlight> hosts() {
57 return Collections.unmodifiableSet(hosts); 123 return Collections.unmodifiableSet(hosts);
58 } 124 }
59 125
126 + /**
127 + * Returns the set of link highlights.
128 + *
129 + * @return link highlights
130 + */
60 public Set<LinkHighlight> links() { 131 public Set<LinkHighlight> links() {
61 return Collections.unmodifiableSet(links); 132 return Collections.unmodifiableSet(links);
62 } 133 }
134 +
135 + /**
136 + * Returns the amount by which all other elements not explicitly
137 + * referenced here should be "subdued".
138 + *
139 + * @return amount to subdue other elements
140 + */
141 + public Amount subdueLevel() {
142 + return subdueLevel;
143 + }
63 } 144 }
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + */
17 +
18 +package org.onosproject.ui.topo;
19 +
20 +import org.junit.Test;
21 +
22 +import static org.junit.Assert.assertEquals;
23 +
24 +/**
25 + * Unit tests for {@link Highlights}.
26 + */
27 +public class HighlightsTest {
28 +
29 + private Highlights hl;
30 +
31 + @Test
32 + public void basic() {
33 + hl = new Highlights();
34 +
35 + assertEquals("devices", 0, hl.devices().size());
36 + assertEquals("hosts", 0, hl.hosts().size());
37 + assertEquals("links", 0, hl.links().size());
38 + assertEquals("sudue", Highlights.Amount.ZERO, hl.subdueLevel());
39 + }
40 +
41 + // NOTE: further unit tests involving the Highlights class are done
42 + // in TopoJsonTest.
43 +
44 +}
...@@ -35,14 +35,14 @@ import org.onosproject.net.intent.OpticalPathIntent; ...@@ -35,14 +35,14 @@ import org.onosproject.net.intent.OpticalPathIntent;
35 import org.onosproject.net.intent.PathIntent; 35 import org.onosproject.net.intent.PathIntent;
36 import org.onosproject.net.statistic.Load; 36 import org.onosproject.net.statistic.Load;
37 import org.onosproject.ui.impl.topo.IntentSelection; 37 import org.onosproject.ui.impl.topo.IntentSelection;
38 -import org.onosproject.ui.topo.NodeSelection;
39 import org.onosproject.ui.impl.topo.ServicesBundle; 38 import org.onosproject.ui.impl.topo.ServicesBundle;
40 -import org.onosproject.ui.topo.TopoUtils;
41 import org.onosproject.ui.impl.topo.TopoIntentFilter; 39 import org.onosproject.ui.impl.topo.TopoIntentFilter;
42 import org.onosproject.ui.impl.topo.TrafficClass; 40 import org.onosproject.ui.impl.topo.TrafficClass;
43 import org.onosproject.ui.impl.topo.TrafficLink; 41 import org.onosproject.ui.impl.topo.TrafficLink;
44 import org.onosproject.ui.impl.topo.TrafficLinkMap; 42 import org.onosproject.ui.impl.topo.TrafficLinkMap;
45 import org.onosproject.ui.topo.Highlights; 43 import org.onosproject.ui.topo.Highlights;
44 +import org.onosproject.ui.topo.NodeSelection;
45 +import org.onosproject.ui.topo.TopoUtils;
46 import org.slf4j.Logger; 46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory; 47 import org.slf4j.LoggerFactory;
48 48
......
...@@ -31,19 +31,21 @@ import org.onosproject.ui.topo.PropertyPanel; ...@@ -31,19 +31,21 @@ import org.onosproject.ui.topo.PropertyPanel;
31 * JSON utilities for the Topology View. 31 * JSON utilities for the Topology View.
32 */ 32 */
33 public final class TopoJson { 33 public final class TopoJson {
34 - private static final String DEVICES = "devices"; 34 + // package-private for unit test access
35 - private static final String HOSTS = "hosts"; 35 + static final String DEVICES = "devices";
36 - private static final String LINKS = "links"; 36 + static final String HOSTS = "hosts";
37 + static final String LINKS = "links";
38 + static final String SUBDUE = "subdue";
37 39
38 - private static final String ID = "id"; 40 + static final String ID = "id";
39 - private static final String LABEL = "label"; 41 + static final String LABEL = "label";
40 - private static final String CSS = "css"; 42 + static final String CSS = "css";
41 43
42 - private static final String TITLE = "title"; 44 + static final String TITLE = "title";
43 - private static final String TYPE = "type"; 45 + static final String TYPE = "type";
44 - private static final String PROP_ORDER = "propOrder"; 46 + static final String PROP_ORDER = "propOrder";
45 - private static final String PROPS = "props"; 47 + static final String PROPS = "props";
46 - private static final String BUTTONS = "buttons"; 48 + static final String BUTTONS = "buttons";
47 49
48 50
49 private static final ObjectMapper MAPPER = new ObjectMapper(); 51 private static final ObjectMapper MAPPER = new ObjectMapper();
...@@ -80,6 +82,10 @@ public final class TopoJson { ...@@ -80,6 +82,10 @@ public final class TopoJson {
80 highlights.hosts().forEach(hh -> hosts.add(json(hh))); 82 highlights.hosts().forEach(hh -> hosts.add(json(hh)));
81 highlights.links().forEach(lh -> links.add(json(lh))); 83 highlights.links().forEach(lh -> links.add(json(lh)));
82 84
85 + Highlights.Amount toSubdue = highlights.subdueLevel();
86 + if (!toSubdue.equals(Highlights.Amount.ZERO)) {
87 + payload.put(SUBDUE, toSubdue.toString());
88 + }
83 return payload; 89 return payload;
84 } 90 }
85 91
......
...@@ -323,6 +323,10 @@ html[data-platform='iPad'] #topo-p-detail { ...@@ -323,6 +323,10 @@ html[data-platform='iPad'] #topo-p-detail {
323 /* --- Topo Nodes --- */ 323 /* --- Topo Nodes --- */
324 324
325 #ov-topo svg .suppressed { 325 #ov-topo svg .suppressed {
326 + opacity: 0.5 !important;
327 +}
328 +
329 +#ov-topo svg .suppressedmax {
326 opacity: 0.2 !important; 330 opacity: 0.2 !important;
327 } 331 }
328 332
......
...@@ -33,6 +33,8 @@ ...@@ -33,6 +33,8 @@
33 link() // get ref to D3 selection of links 33 link() // get ref to D3 selection of links
34 */ 34 */
35 35
36 + var smax = 'suppressedmax';
37 +
36 // which "layer" a particular item "belongs to" 38 // which "layer" a particular item "belongs to"
37 var layerLookup = { 39 var layerLookup = {
38 host: { 40 host: {
...@@ -93,23 +95,21 @@ ...@@ -93,23 +95,21 @@
93 api.node().each(function (d) { 95 api.node().each(function (d) {
94 var node = d.el; 96 var node = d.el;
95 if (inLayer(d, which)) { 97 if (inLayer(d, which)) {
96 - node.classed('suppressed', false); 98 + node.classed(smax, false);
97 } 99 }
98 }); 100 });
99 101
100 api.link().each(function (d) { 102 api.link().each(function (d) {
101 var link = d.el; 103 var link = d.el;
102 if (inLayer(d, which)) { 104 if (inLayer(d, which)) {
103 - link.classed('suppressed', false); 105 + link.classed(smax, false);
104 } 106 }
105 }); 107 });
106 } 108 }
107 109
108 function suppressLayers(b) { 110 function suppressLayers(b) {
109 - api.node().classed('suppressed', b); 111 + api.node().classed(smax, b);
110 - api.link().classed('suppressed', b); 112 + api.link().classed(smax, b);
111 -// d3.selectAll('svg .port').classed('inactive', false);
112 -// d3.selectAll('svg .portText').classed('inactive', false);
113 } 113 }
114 114
115 function showLayer(which) { 115 function showLayer(which) {
......
...@@ -491,16 +491,37 @@ ...@@ -491,16 +491,37 @@
491 suppressLayers(true); 491 suppressLayers(true);
492 node.each(function (n) { 492 node.each(function (n) {
493 if (n.master === id) { 493 if (n.master === id) {
494 - n.el.classed('suppressed', false); 494 + n.el.classed('suppressedmax', false);
495 } 495 }
496 }); 496 });
497 } 497 }
498 498
499 - function suppressLayers(b) { 499 + function supAmt(less) {
500 - node.classed('suppressed', b); 500 + return less ? "suppressed" : "suppressedmax";
501 - link.classed('suppressed', b); 501 + }
502 -// d3.selectAll('svg .port').classed('inactive', b); 502 +
503 -// d3.selectAll('svg .portText').classed('inactive', b); 503 + function suppressLayers(b, less) {
504 + var cls = supAmt(less);
505 + node.classed(cls, b);
506 + link.classed(cls, b);
507 + }
508 +
509 + function unsuppressNode(id, less) {
510 + var cls = supAmt(less);
511 + node.each(function (n) {
512 + if (n.id === id) {
513 + n.el.classed(cls, false);
514 + }
515 + });
516 + }
517 +
518 + function unsuppressLink(id, less) {
519 + var cls = supAmt(less);
520 + link.each(function (n) {
521 + if (n.id === id) {
522 + n.el.classed(cls, false);
523 + }
524 + });
504 } 525 }
505 526
506 function showBadLinks() { 527 function showBadLinks() {
...@@ -900,8 +921,12 @@ ...@@ -900,8 +921,12 @@
900 return { 921 return {
901 clearLinkTrafficStyle: clearLinkTrafficStyle, 922 clearLinkTrafficStyle: clearLinkTrafficStyle,
902 removeLinkLabels: removeLinkLabels, 923 removeLinkLabels: removeLinkLabels,
924 + findLinkById: tms.findLinkById,
903 updateLinks: updateLinks, 925 updateLinks: updateLinks,
904 - findLinkById: tms.findLinkById 926 + updateNodes: updateNodes,
927 + supLayers: suppressLayers,
928 + unsupNode: unsuppressNode,
929 + unsupLink: unsuppressLink
905 }; 930 };
906 } 931 }
907 932
......
...@@ -294,18 +294,34 @@ ...@@ -294,18 +294,34 @@
294 } 294 }
295 295
296 function showHighlights(data) { 296 function showHighlights(data) {
297 + var less;
298 +
297 /* 299 /*
298 API to topoForce 300 API to topoForce
299 clearLinkTrafficStyle() 301 clearLinkTrafficStyle()
300 removeLinkLabels() 302 removeLinkLabels()
301 - updateLinks()
302 findLinkById( id ) 303 findLinkById( id )
304 + updateLinks()
305 + updateNodes()
306 + supLayers( bool, [less] )
307 + unsupNode( id, [less] )
308 + unsupLink( id, [less] )
303 */ 309 */
304 310
305 // TODO: clear node highlighting 311 // TODO: clear node highlighting
306 api.clearLinkTrafficStyle(); 312 api.clearLinkTrafficStyle();
307 api.removeLinkLabels(); 313 api.removeLinkLabels();
308 314
315 + // handle element suppression
316 + if (data.subdue) {
317 + less = data.subdue === 'min';
318 + api.supLayers(true, less);
319 +
320 + } else {
321 + api.supLayers(false);
322 + api.supLayers(false, true);
323 + }
324 +
309 // TODO: device and host highlights 325 // TODO: device and host highlights
310 326
311 data.links.forEach(function (lnk) { 327 data.links.forEach(function (lnk) {
...@@ -314,6 +330,7 @@ ...@@ -314,6 +330,7 @@
314 units, portcls, magnitude; 330 units, portcls, magnitude;
315 331
316 if (ldata && !ldata.el.empty()) { 332 if (ldata && !ldata.el.empty()) {
333 + api.unsupLink(ldata.id, less);
317 ldata.el.classed(lnk.css, true); 334 ldata.el.classed(lnk.css, true);
318 ldata.label = lab; 335 ldata.label = lab;
319 336
...@@ -334,7 +351,7 @@ ...@@ -334,7 +351,7 @@
334 } 351 }
335 }); 352 });
336 353
337 - // TODO: api.updateNodes() 354 + api.updateNodes();
338 api.updateLinks(); 355 api.updateLinks();
339 } 356 }
340 357
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + */
17 +
18 +package org.onosproject.ui.impl.topo;
19 +
20 +import com.fasterxml.jackson.databind.node.ArrayNode;
21 +import com.fasterxml.jackson.databind.node.ObjectNode;
22 +import org.junit.Test;
23 +import org.onosproject.ui.JsonUtils;
24 +import org.onosproject.ui.topo.Highlights;
25 +import org.onosproject.ui.topo.Highlights.Amount;
26 +
27 +import static org.junit.Assert.assertEquals;
28 +
29 +/**
30 + * Unit tests for {@link TopoJson}.
31 + */
32 +public class TopoJsonTest {
33 +
34 + private ObjectNode payload;
35 +
36 + private void checkArrayLength(String key, int expLen) {
37 + ArrayNode a = (ArrayNode) payload.get(key);
38 + assertEquals("wrong size: " + key, expLen, a.size());
39 + }
40 +
41 + private void checkEmptyArrays() {
42 + checkArrayLength(TopoJson.DEVICES, 0);
43 + checkArrayLength(TopoJson.HOSTS, 0);
44 + checkArrayLength(TopoJson.LINKS, 0);
45 + }
46 +
47 + @Test
48 + public void basicHighlights() {
49 + Highlights h = new Highlights();
50 + payload = TopoJson.json(h);
51 + checkEmptyArrays();
52 + String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
53 + assertEquals("subdue", "", subdue);
54 + }
55 +
56 + @Test
57 + public void subdueMinimalHighlights() {
58 + Highlights h = new Highlights().subdueAllElse(Amount.MINIMALLY);
59 + payload = TopoJson.json(h);
60 + checkEmptyArrays();
61 + String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
62 + assertEquals("not min", "min", subdue);
63 + }
64 +
65 + @Test
66 + public void subdueMaximalHighlights() {
67 + Highlights h = new Highlights().subdueAllElse(Amount.MAXIMALLY);
68 + payload = TopoJson.json(h);
69 + checkEmptyArrays();
70 + String subdue = JsonUtils.string(payload, TopoJson.SUBDUE);
71 + assertEquals("not max", "max", subdue);
72 + }
73 +}