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
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java b/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
index 848e758..0700173 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/Highlights.java
@@ -21,6 +21,8 @@
 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 @@
  */
 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;
+    }
 }
diff --git a/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java b/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java
new file mode 100644
index 0000000..ce2d2db
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/ui/topo/HighlightsTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.
+
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
index 45e0a6c..31f07f8 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficMonitor.java
@@ -35,14 +35,14 @@
 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;
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java
index f382e22..0a12e1c 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/TopoJson.java
@@ -31,19 +31,21 @@
  * 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 @@
         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;
     }
 
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 f26f478..f4b089a 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -323,6 +323,10 @@
 /* --- Topo Nodes --- */
 
 #ov-topo svg .suppressed {
+    opacity: 0.5 !important;
+}
+
+#ov-topo svg .suppressedmax {
     opacity: 0.2 !important;
 }
 
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 1226664..f9b96ae 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoFilter.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
@@ -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) {
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 f3c053e..555fa50 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -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
         };
     }
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index 85dc0ff..4a432e0 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -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();
     }
 
diff --git a/web/gui/src/test/java/org/onosproject/ui/impl/topo/TopoJsonTest.java b/web/gui/src/test/java/org/onosproject/ui/impl/topo/TopoJsonTest.java
new file mode 100644
index 0000000..aab650c
--- /dev/null
+++ b/web/gui/src/test/java/org/onosproject/ui/impl/topo/TopoJsonTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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);
+    }
+}