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
Showing
9 changed files
with
282 additions
and
32 deletions
... | @@ -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; | ||
61 | + | ||
35 | 62 | ||
36 | - public Highlights add(DeviceHighlight d) { | 63 | + /** |
37 | - devices.add(d); | 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 | +} |
-
Please register or login to post a comment