[SDFAB-705] Fix GUI for the control and data plane resiliency

Additionally, fix similar issues in GUI2 and add initial
support for ports with name in GUI/GUI2.

This is also the first step towards supporting port with name widely in ONOS

Change-Id: Ib04f780bf0b7171e82a6beb69b39c0aaeb4be957
(cherry picked from commit 178046ba11ab21d94a1e818fb893931bb015734b)
diff --git a/core/api/src/main/java/org/onosproject/net/ConnectPoint.java b/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
index 7a311aa..ac04a05 100644
--- a/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
+++ b/core/api/src/main/java/org/onosproject/net/ConnectPoint.java
@@ -178,14 +178,25 @@
          * - scheme:ip:port/path/cp
          *
          * The assumption is the last `/` will separate the device ID
-         * from the connection point number.
+         * from the connection point number. If the cp is a named port
+         * `/` can be included in the port name. We use `[` as heuristic
+         * to separate the device ID from the port number.
          */
         checkNotNull(string);
         int idx = string.lastIndexOf("/");
         checkArgument(idx != -1, NO_SEP_SPECIFIED);
 
-        String id = string.substring(0, idx);
-        String cp = string.substring(idx + 1);
+        String id = "";
+        String cp = "";
+        // deviceId/[ which means the id is 2 chars behind
+        int nameIdx = string.lastIndexOf("[");
+        if (nameIdx > 0) {
+            id = string.substring(0, nameIdx - 1);
+            cp = string.substring(nameIdx);
+        } else if (nameIdx < 0) {
+            id = string.substring(0, idx);
+            cp = string.substring(idx + 1);
+        }
         checkArgument(!cp.isEmpty(), SEP_NO_VALUE);
 
         return new ConnectPoint(DeviceId.deviceId(id), PortNumber.fromString(cp));
diff --git a/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java b/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
index 85c51f6..ae9d445 100644
--- a/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
+++ b/core/api/src/main/java/org/onosproject/ui/topo/NodeSelection.java
@@ -36,7 +36,7 @@
 import java.util.Set;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
-import static org.onosproject.net.ConnectPoint.deviceConnectPoint;
+import static org.onosproject.net.ConnectPoint.fromString;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.net.HostId.hostId;
 
@@ -301,8 +301,8 @@
                     continue;
                 }
 
-                cpSrc = deviceConnectPoint(connectPoints[0]);
-                cpDst = deviceConnectPoint(connectPoints[1]);
+                cpSrc = fromString(connectPoints[0]);
+                cpDst = fromString(connectPoints[1]);
                 link = linkService.getLink(cpSrc, cpDst);
 
                 if (link != null) {
diff --git a/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java b/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
index 73dfe79..0a7516d 100644
--- a/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
+++ b/core/api/src/test/java/org/onosproject/net/ConnectPointTest.java
@@ -71,14 +71,47 @@
     }
 
     @Test
-    public void testParseFromString() {
+    public void testParseFromStringOF() {
+        String cp = "of:0011223344556677/1";
+
+        ConnectPoint connectPoint = ConnectPoint.fromString(cp);
+        assertEquals("of:0011223344556677", connectPoint.deviceId().toString());
+        assertEquals("1", connectPoint.port().toString());
+
+        expectStringParseException("[");
+        expectStringParseException("1/[");
+        expectStringParseException("1/[aasksk");
+        expectStringParseException("1/[alksas]");
+    }
+
+    @Test
+    public void testParseFromStringNetconf() {
         String cp = "netconf:127.0.0.1/[TYPE](1)";
 
         ConnectPoint connectPoint = ConnectPoint.fromString(cp);
         assertEquals("netconf:127.0.0.1", connectPoint.deviceId().toString());
         assertEquals("[TYPE](1)", connectPoint.port().toString());
         assertEquals(connectPoint, ConnectPoint.fromString(connectPoint.toString()));
+    }
 
+    @Test
+    public void testParseFromStringBmv2() {
+        String cp = "device:leaf1/[leaf1-eth4](1)";
+
+        ConnectPoint connectPoint = ConnectPoint.fromString(cp);
+        assertEquals("device:leaf1", connectPoint.deviceId().toString());
+        assertEquals("[leaf1-eth4](1)", connectPoint.port().toString());
+        assertEquals(connectPoint, ConnectPoint.fromString(connectPoint.toString()));
+    }
+
+    @Test
+    public void testParseFromStringStratum() {
+        String cp = "device:leaf1/[3/0](1)";
+
+        ConnectPoint connectPoint = ConnectPoint.fromString(cp);
+        assertEquals("device:leaf1", connectPoint.deviceId().toString());
+        assertEquals("[3/0](1)", connectPoint.port().toString());
+        assertEquals(connectPoint, ConnectPoint.fromString(connectPoint.toString()));
     }
 
     /**
@@ -95,6 +128,20 @@
         }
     }
 
+    /**
+     * Parse a string connect point and expect an exception to be thrown.
+     *
+     * @param string string to parse
+     */
+    private static void expectStringParseException(String string) {
+        try {
+            ConnectPoint.fromString(string);
+            fail(String.format("Expected exception was not thrown for '%s'", string));
+        } catch (Exception e) {
+            assertTrue(true);
+        }
+    }
+
     @Test
     public void testParseHostConnectPoint() {
         String cp = "16:3A:BD:6E:31:E4/-1/1";
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
index 311d44f..92a051c 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/PortViewMessageHandler.java
@@ -131,7 +131,7 @@
         }
 
         private void populateRow(TableModel.Row row, PortStatistics stat) {
-            row.cell(ID, stat.portNumber().toLong())
+            row.cell(ID, stat.portNumber())
                 .cell(PKT_RX, stat.packetsReceived())
                 .cell(PKT_TX, stat.packetsSent())
                 .cell(BYTES_RX, stat.bytesReceived())
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 02bc1ff..776ea58 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -81,7 +81,7 @@
 import static java.util.concurrent.Executors.newSingleThreadExecutor;
 import static org.onlab.util.Tools.groupedThreads;
 import static org.onosproject.cluster.ClusterEvent.Type.INSTANCE_ADDED;
-import static org.onosproject.net.ConnectPoint.deviceConnectPoint;
+import static org.onosproject.net.ConnectPoint.fromString;
 import static org.onosproject.net.DeviceId.deviceId;
 import static org.onosproject.net.HostId.hostId;
 import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
@@ -394,7 +394,7 @@
                 if (isEdgeLink) {
                     HostId hid = hostId(srcId);
                     String cpstr = tgtId + SLASH + string(payload, TARGET_PORT);
-                    ConnectPoint cp = deviceConnectPoint(cpstr);
+                    ConnectPoint cp = fromString(cpstr);
 
                     pp = edgeLinkDetails(hid, cp);
                     overlayCache.currentOverlay().modifyEdgeLinkDetails(pp, hid, cp);
@@ -402,8 +402,8 @@
                 } else {
                     String cpAstr = srcId + SLASH + string(payload, SOURCE_PORT);
                     String cpBstr = tgtId + SLASH + string(payload, TARGET_PORT);
-                    ConnectPoint cpA = deviceConnectPoint(cpAstr);
-                    ConnectPoint cpB = deviceConnectPoint(cpBstr);
+                    ConnectPoint cpA = fromString(cpAstr);
+                    ConnectPoint cpB = fromString(cpBstr);
 
                     pp = infraLinkDetails(cpA, cpB);
                     overlayCache.currentOverlay().modifyInfraLinkDetails(pp, cpA, cpB);
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index ff52584..3bd4985 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -377,7 +377,7 @@
     private ObjectNode hostConnect(HostLocation location) {
         return objectNode()
                 .put("device", location.deviceId().toString())
-                .put("port", location.port().toLong());
+                .put("port", location.port().toString());
     }
 
     // Encodes the specified list of labels a JSON array.
@@ -639,7 +639,7 @@
         pp.addProp(LPL_A_TYPE, lion.getSafe(LPL_A_TYPE), lion.getSafe(DEVICE))
                 .addProp(LPL_A_ID, lion.getSafe(LPL_A_ID), did.toString())
                 .addProp(LPL_A_FRIENDLY, lion.getSafe(LPL_A_FRIENDLY), friendlyDevice(did))
-                .addProp(LPL_A_PORT, lion.getSafe(LPL_A_PORT), cp.port().toLong())
+                .addProp(LPL_A_PORT, lion.getSafe(LPL_A_PORT), cp.port().toString())
                 .addSeparator();
     }
 
@@ -649,7 +649,7 @@
         pp.addProp(LPL_B_TYPE, lion.getSafe(LPL_B_TYPE), lion.getSafe(DEVICE))
                 .addProp(LPL_B_ID, lion.getSafe(LPL_B_ID), did.toString())
                 .addProp(LPL_B_FRIENDLY, lion.getSafe(LPL_B_FRIENDLY), friendlyDevice(did))
-                .addProp(LPL_B_PORT, lion.getSafe(LPL_B_PORT), cp.port().toLong())
+                .addProp(LPL_B_PORT, lion.getSafe(LPL_B_PORT), cp.port().toString())
                 .addSeparator();
     }
 
diff --git a/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.spec.ts b/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.spec.ts
index 5ebc0d6..96142af 100644
--- a/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.spec.ts
+++ b/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.spec.ts
@@ -30,7 +30,7 @@
 import {SubRegionNodeSvgComponent} from './visuals/subregionnodesvg/subregionnodesvg.component';
 import {HostNodeSvgComponent} from './visuals/hostnodesvg/hostnodesvg.component';
 import {LinkSvgComponent} from './visuals/linksvg/linksvg.component';
-import {Device, Host, Link, LinkType, LinkHighlight, Region} from './models';
+import {Device, Host, Link, LinkType, LinkHighlight, Region, Node} from './models';
 import {ChangeDetectorRef, SimpleChange} from '@angular/core';
 import {TopologyService} from '../../topology.service';
 import {BadgeSvgComponent} from './visuals/badgesvg/badgesvg.component';
@@ -1413,6 +1413,122 @@
         "subregions": [],
         "links": [
             {
+                "id": "00:00:00:00:00:22/120~device:leaf4/[3/0](3)",
+                "epA": "00:00:00:00:00:22/120",
+                "epB": "device:leaf4",
+                "type": "UiEdgeLink",
+                "portB": "[3/0](3)",
+                "rollup": [
+                    {
+                        "id": "00:00:00:00:00:22/120~device:leaf4/[3/0](3)",
+                        "epA": "00:00:00:00:00:22/120",
+                        "epB": "device:leaf4",
+                        "type": "UiEdgeLink",
+                        "portB": "[3/0](3)"
+                    }
+                ]
+            },
+            {
+                "id": "00:00:00:00:00:1E/None~device:leaf4/[2/0](2)",
+                "epA": "00:00:00:00:00:1E/None",
+                "epB": "device:leaf4",
+                "type": "UiEdgeLink",
+                "portB": "[2/0](2)",
+                "rollup": [
+                    {
+                        "id": "00:00:00:00:00:1E/None~device:leaf4/[2/0](2)",
+                        "epA": "00:00:00:00:00:1E/None",
+                        "epB": "device:leaf4",
+                        "type": "UiEdgeLink",
+                        "portB": "[2/0](2)"
+                    }
+                ]
+            },
+            {
+                "id": "device:leaf4/[1/0](1)~device:spine1/[3/0](3)",
+                "epA": "device:leaf4/[1/0](1)",
+                "epB": "device:spine1/[3/0](3)",
+                "type": "UiDeviceLink",
+                "portA": "[1/0](1)",
+                "portB": "[3/0](3)",
+                "rollup": [
+                    {
+                        "id": "device:leaf4/[1/0](1)~device:spine1/[3/0](3)",
+                        "epA": "device:leaf4/[1/0](1)",
+                        "epB": "device:spine1/[3/0](3)",
+                        "type": "UiDeviceLink",
+                        "portA": "[1/0](1)",
+                        "portB": "[3/0](3)"
+                    }
+                ]
+            },
+            {
+                "id": "00:00:00:00:00:21/120~device:leaf3/[leaf3-eth3](3)",
+                "epA": "00:00:00:00:00:21/120",
+                "epB": "device:leaf3",
+                "type": "UiEdgeLink",
+                "portB": "[leaf3-eth3](3)",
+                "rollup": [
+                    {
+                        "id": "00:00:00:00:00:21/120~device:leaf3/[leaf3-eth3](3)",
+                        "epA": "00:00:00:00:00:21/120",
+                        "epB": "device:leaf3",
+                        "type": "UiEdgeLink",
+                        "portB": "[leaf3-eth3](3)"
+                    }
+                ]
+            },
+            {
+                "id": "00:00:00:00:00:1D/None~device:leaf3/[leaf3-eth2](2)",
+                "epA": "00:00:00:00:00:1D/None",
+                "epB": "device:leaf3",
+                "type": "UiEdgeLink",
+                "portB": "[leaf3-eth2](2)",
+                "rollup": [
+                    {
+                        "id": "00:00:00:00:00:1D/None~device:leaf3/[leaf3-eth2](2)",
+                        "epA": "00:00:00:00:00:1D/None",
+                        "epB": "device:leaf3",
+                        "type": "UiEdgeLink",
+                        "portB": "[leaf3-eth2](2)"
+                    }
+                ]
+            },
+            {
+                "id": "device:leaf3/[leaf3-eth1](1)~device:spine1/[spine1-eth3](3)",
+                "epA": "device:leaf3/[leaf3-eth1](1)",
+                "epB": "device:spine1/[spine1-eth3](3)",
+                "type": "UiDeviceLink",
+                "portA": "[leaf3-eth1](1)",
+                "portB": "[spine1-eth3](3)",
+                "rollup": [
+                    {
+                        "id": "device:leaf3/[leaf3-eth1](1)~device:spine1/[spine1-eth3](3)",
+                        "epA": "device:leaf3/[leaf3-eth1](1)",
+                        "epB": "device:spine1/[spine1-eth3](3)",
+                        "type": "UiDeviceLink",
+                        "portA": "[leaf3-eth1](1)",
+                        "portB": "[spine1-eth3](3)"
+                    }
+                ]
+            },
+            {
+                "id": "00:00:00:00:00:1F/120~device:leaf1/7",
+                "epA": "00:00:00:00:00:1F/120",
+                "epB": "device:leaf1",
+                "type": "UiEdgeLink",
+                "portB": "7",
+                "rollup": [
+                    {
+                        "id": "00:00:00:00:00:1F/120~device:leaf1/7",
+                        "epA": "00:00:00:00:00:1F/120",
+                        "epB": "device:leaf1",
+                        "type": "UiEdgeLink",
+                        "portB": "7"
+                    }
+                ]
+            },
+            {
                 "id": "device:leaf1/1~device:spine1/1",
                 "epA": "device:leaf1/1",
                 "epB": "device:spine1/1",
@@ -1570,6 +1686,52 @@
             [],
             [
                 {
+                    "id": "device:leaf4",
+                    "nodeType": "device",
+                    "type": "switch",
+                    "online": true,
+                    "master": "172.24.0.3",
+                    "layer": "def",
+                    "props": {
+                        "managementAddress": "grpc://pippo:50003?device_id=1",
+                        "protocol": "P4Runtime, gNMI, gNOI",
+                        "gridX": "400.0",
+                        "gridY": "400.0",
+                        "driver": "stratum-tofino",
+                        "name": "device:leaf4",
+                        "p4DeviceId": "1",
+                        "locType": "grid"
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 400.0,
+                        "longOrX": 400.0
+                    }
+                },
+                {
+                    "id": "device:leaf3",
+                    "nodeType": "device",
+                    "type": "switch",
+                    "online": true,
+                    "master": "172.24.0.3",
+                    "layer": "def",
+                    "props": {
+                        "managementAddress": "grpc://mininet:50003?device_id=1",
+                        "protocol": "P4Runtime, gNMI, gNOI",
+                        "gridX": "400.0",
+                        "gridY": "400.0",
+                        "driver": "stratum-bmv2",
+                        "name": "device:leaf3",
+                        "p4DeviceId": "1",
+                        "locType": "grid"
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 400.0,
+                        "longOrX": 400.0
+                    }
+                },
+                {
                     "id": "device:spine1",
                     "nodeType": "device",
                     "type": "switch",
@@ -1668,6 +1830,116 @@
             [],
             [
                 {
+                    "id": "00:00:00:00:00:22/120",
+                    "nodeType": "host",
+                    "layer": "def",
+                    "ips": [
+                        "2001:2:3::1"
+                    ],
+                    "props": {
+                        "gridX": "750.0",
+                        "gridY": "700.0",
+                        "latitude": null,
+                        "name": "h3",
+                        "locType": "grid",
+                        "longitude": null
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 700.0,
+                        "longOrX": 750.0
+                    },
+                    "configured": false
+                },
+                {
+                    "id": "00:00:00:00:00:21/120",
+                    "nodeType": "host",
+                    "layer": "def",
+                    "ips": [
+                        "2001:2:3::1"
+                    ],
+                    "props": {
+                        "gridX": "750.0",
+                        "gridY": "700.0",
+                        "latitude": null,
+                        "name": "h3",
+                        "locType": "grid",
+                        "longitude": null
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 700.0,
+                        "longOrX": 750.0
+                    },
+                    "configured": false
+                },
+                {
+                    "id": "00:00:00:00:00:1F/120",
+                    "nodeType": "host",
+                    "layer": "def",
+                    "ips": [
+                        "2001:2:3::1"
+                    ],
+                    "props": {
+                        "gridX": "750.0",
+                        "gridY": "700.0",
+                        "latitude": null,
+                        "name": "h3",
+                        "locType": "grid",
+                        "longitude": null
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 700.0,
+                        "longOrX": 750.0
+                    },
+                    "configured": false
+                },
+                {
+                    "id": "00:00:00:00:00:1E/None",
+                    "nodeType": "host",
+                    "layer": "def",
+                    "ips": [
+                        "2001:2:3::1"
+                    ],
+                    "props": {
+                        "gridX": "750.0",
+                        "gridY": "700.0",
+                        "latitude": null,
+                        "name": "h3",
+                        "locType": "grid",
+                        "longitude": null
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 700.0,
+                        "longOrX": 750.0
+                    },
+                    "configured": false
+                },
+                {
+                    "id": "00:00:00:00:00:1D/None",
+                    "nodeType": "host",
+                    "layer": "def",
+                    "ips": [
+                        "2001:2:3::1"
+                    ],
+                    "props": {
+                        "gridX": "750.0",
+                        "gridY": "700.0",
+                        "latitude": null,
+                        "name": "h3",
+                        "locType": "grid",
+                        "longitude": null
+                    },
+                    "location": {
+                        "locType": "grid",
+                        "latOrY": 700.0,
+                        "longOrX": 750.0
+                    },
+                    "configured": false
+                },
+                {
                     "id": "00:00:00:00:00:30/None",
                     "nodeType": "host",
                     "layer": "def",
@@ -1795,6 +2067,41 @@
     "hosts": [],
     "links": [
       {
+        "id": "00:00:00:00:00:22/120~device:leaf4/[3/0](3)",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
+        "id": "00:00:00:00:00:1E/None~device:leaf4/[2/0](2)",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
+        "id": "device:leaf4/[1/0](1)~device:spine1/[3/0](3)",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
+        "id": "00:00:00:00:00:21/120~device:leaf3/[leaf3-eth3](3)",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
+        "id": "00:00:00:00:00:1D/None~device:leaf3/[leaf3-eth2](2)",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
+        "id": "device:leaf3/[leaf3-eth1](1)~device:spine1/[spine1-eth3](3)",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
+        "id": "00:00:00:00:00:1F/120~device:leaf1/7",
+        "label": "964.91 Kbps",
+        "css": "secondary port-traffic-green"
+      },
+      {
         "id": "device:leaf2/2~device:spine2/2",
         "label": "964.91 Kbps",
         "css": "secondary port-traffic-green"
@@ -1828,6 +2135,16 @@
   }
 }`;
 
+const topo2Highlights_sample2 = `
+{
+  "event": "topo2Highlights",
+  "payload": {
+    "devices": [],
+    "hosts": [],
+    "links": []
+  }
+}`;
+
 class MockActivatedRoute extends ActivatedRoute {
     constructor(params: Params) {
         super();
@@ -2071,6 +2388,23 @@
         // Like epB of second example in sampleData file - endPtStr does not contain port number
         expect(ForceSvgComponent.extractNodeName('of:0000000000000206', '6'))
             .toEqual('of:0000000000000206');
+
+        // bmv2 case - no port in the endpoint
+        expect(ForceSvgComponent.extractNodeName('device:leaf1', '[leaf1-eth1](1)'))
+            .toEqual('device:leaf1');
+
+        // bmv2 case - port in the endpoint
+        expect(ForceSvgComponent.extractNodeName('device:leaf1/[leaf1-eth1](1)', '[leaf1-eth1](1)'))
+            .toEqual('device:leaf1');
+
+        // tofino case - no port in the endpoint
+        expect(ForceSvgComponent.extractNodeName('device:leaf1', '[1/0](1)'))
+            .toEqual('device:leaf1');
+
+        // tofino case - port in the endpoint
+        expect(ForceSvgComponent.extractNodeName('device:leaf1/[1/0](1)', '[1/0](1)'))
+            .toEqual('device:leaf1');
+
     });
 
     it('should handle openflow regionData change - sample Region', () => {
@@ -2100,9 +2434,23 @@
         component.ngOnChanges(
             {'regionData' : new SimpleChange(<Region>{}, topo2BaseRegionData, true)});
 
-        expect(component.graph.links.length).toBe(9);
+        expect(component.graph.links.length).toBe(16);
+        expect(component.graph.nodes.length).toBe(16)
+        expect(linkHightlights.length).toBe(13);
 
-        expect(linkHightlights.length).toBe(6);
+        // sanitize deviceNameFromEp
+        component.graph.links.forEach((l: Link) => {
+            if (<LinkType><unknown>LinkType[l.type] === LinkType.UiEdgeLink) {
+                // edge link has only one epoint valid (the other is not deviceId)
+                const foundNode = component.graph.nodes.find((n: Node) => n.id === Link.deviceNameFromEp(l.epB));
+                expect(foundNode).toBeDefined();
+            } else {
+                var foundNode = component.graph.nodes.find((n: Node) => n.id === Link.deviceNameFromEp(l.epA));
+                expect(foundNode).toBeDefined();
+                foundNode = component.graph.nodes.find((n: Node) => n.id === Link.deviceNameFromEp(l.epB));
+                expect(foundNode).toBeDefined();
+            }
+        });
 
         // should be able to find all of the highlighted links in the original data set
         linkHightlights.forEach((lh: LinkHighlight) => {
diff --git a/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.ts b/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.ts
index dc67bc2..234f1e9 100644
--- a/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2-topo-lib/lib/layer/forcesvg/forcesvg.component.ts
@@ -123,6 +123,7 @@
      * In some cases - have to remove the port number from the end of a device
      * name
      * @param endPtStr The end point name
+     * @param portStr The port name
      */
     static extractNodeName(endPtStr: string, portStr: string): string {
         if (portStr === undefined || endPtStr === undefined) {
diff --git a/web/gui2-topo-lib/lib/layer/forcesvg/models/link.ts b/web/gui2-topo-lib/lib/layer/forcesvg/models/link.ts
index d5ff2a7..f3b839c 100644
--- a/web/gui2-topo-lib/lib/layer/forcesvg/models/link.ts
+++ b/web/gui2-topo-lib/lib/layer/forcesvg/models/link.ts
@@ -55,7 +55,12 @@
 
     public static deviceNameFromEp(ep: string): string {
         if (ep !== undefined && ep.lastIndexOf('/') > 0) {
-            return ep.substr(0, ep.lastIndexOf('/'));
+            // named port format is [name](number)
+            if (ep.includes('[')) {
+                return ep.substr(0, ep.lastIndexOf('[') - 1);
+            } else {
+                return ep.substr(0, ep.lastIndexOf('/'));
+            }
         }
         return ep;
     }
@@ -66,17 +71,47 @@
      * @param linkId The id of the link in either format
      */
     public static linkIdFromShowHighlights(linkId: string) {
-        if (linkId.includes('-')) {
-            const parts: string[] = linkId.split('-');
+        // Already in the right format
+        if (linkId.includes('~')) {
+            const parts: string[] = linkId.split('~');
+            // remove host part if needed
             const part0 = Link.removeHostPortNum(parts[0]);
             const part1 = Link.removeHostPortNum(parts[1]);
             return part0 + '~' + part1;
         }
+
+        // Custom traffic highlight
+        if (linkId.includes('-')) {
+            // "-" is used only as separator between the links
+            if (linkId.indexOf('-') === linkId.lastIndexOf('-')) {
+                const parts: string[] = linkId.split('-');
+                const part0 = Link.removeHostPortNum(parts[0]);
+                const part1 = Link.removeHostPortNum(parts[1]);
+                return part0 + '~' + part1;
+            } else if (linkId.includes(')')) {
+                // "-" is used in the port name
+                var index = linkId.indexOf(')');
+                // the format is [name](number) on both ends
+                if (linkId.charAt(index + 1) === '-') {
+                    const part0 = Link.removeHostPortNum(linkId.substr(0, index + 1));
+                    const part1 = Link.removeHostPortNum(linkId.substr(index + 2, linkId.length));
+                    return part0 + '~' + part1;
+                } else {
+                    index = linkId.indexOf('-');
+                    const part0 = Link.removeHostPortNum(linkId.substr(0, index));
+                    const part1 = Link.removeHostPortNum(linkId.substr(index + 1, linkId.length));
+                    return part0 + '~' + part1;
+                }
+            }
+        }
+
+        // unknown format
         return linkId;
     }
 
     private static removeHostPortNum(hostStr: string) {
-        if (hostStr.includes('/None/')) {
+        // Regex is for the tagged hosts
+        if (hostStr.includes('/None/') || hostStr.match('/[+-]?[0-9]+/')) {
             const subparts = hostStr.split('/');
             return subparts[0] + '/' + subparts[1];
         }