ONOS-6332: Flows: bad parsing of EXTENSION:of:0000nnnn identifier
- added special handling of ExtensionInstructionWrappers
- added unit tests for JavaScript regexp parsing
- added unit tests for rendering instructions
- added TEST_DEPS for default_drivers

Change-Id: I668ad6ad77ea6a0ce4659497bc5813ed48b1de0a
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
index 41fb033..6e49dba 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
@@ -95,6 +95,14 @@
     private static final String ONOS_PREFIX = "org.onosproject.";
     private static final String ONOS_MARKER = "*";
 
+    // json structure keys
+    private static final String IMMED = "immed";
+    private static final String DEFER = "defer";
+    private static final String METER = "meter";
+    private static final String TABLE = "table";
+    private static final String META = "meta";
+    private static final String CLEARDEF = "clearDef";
+
     // TODO: replace the use of the following constants with localized text
     private static final String MSG_NO_SELECTOR =
             "(No traffic selector criteria for this flow)";
@@ -344,7 +352,7 @@
             if (!instructs.isEmpty()) {
                 sb.append(type).append(SQUARE_O);
                 for (Instruction i : instructs) {
-                    sb.append(i).append(COMMA);
+                    sb.append(renderInstructionForDisplay(i)).append(COMMA);
                 }
                 removeTrailingComma(sb);
                 sb.append(SQUARE_C).append(COMMA);
@@ -456,17 +464,25 @@
         private ArrayNode jsonInstrList(List<Instruction> instructions) {
             ArrayNode array = arrayNode();
             for (Instruction i : instructions) {
-                array.add(i.toString());
+                array.add(renderInstructionForDisplay(i));
             }
             return array;
         }
     }
 
-    // json structure keys
-    private static final String IMMED = "immed";
-    private static final String DEFER = "defer";
-    private static final String METER = "meter";
-    private static final String TABLE = "table";
-    private static final String META = "meta";
-    private static final String CLEARDEF = "clearDef";
+    // package private to allow unit test access...
+    String renderInstructionForDisplay(Instruction instr) {
+
+        // special handling for Extension Instruction Wrappers...
+        if (instr instanceof Instructions.ExtensionInstructionWrapper) {
+            Instructions.ExtensionInstructionWrapper wrap =
+                    (Instructions.ExtensionInstructionWrapper) instr;
+            return wrap.type() + COLON + wrap.extensionInstruction();
+        }
+
+        // special handling of other instruction classes could be placed here
+
+        // default to the natural string representation otherwise
+        return instr.toString();
+    }
 }
diff --git a/web/gui/src/main/webapp/app/view/flow/flow.js b/web/gui/src/main/webapp/app/view/flow/flow.js
index f8c6491..2000997 100644
--- a/web/gui/src/main/webapp/app/view/flow/flow.js
+++ b/web/gui/src/main/webapp/app/view/flow/flow.js
@@ -273,7 +273,10 @@
 
                     function selCb($event, row) {
                         if ($scope.selId) {
-                            wss.sendEvent(detailsReq, { flowId: row.id, appId: row.appId });
+                            wss.sendEvent(detailsReq, {
+                                flowId: row.id,
+                                appId: row.appId
+                            });
                         } else {
                             $scope.hidePanel();
                         }
@@ -301,65 +304,67 @@
 
         .directive('flowDetailsPanel',
             ['$rootScope', '$window', '$timeout', 'KeyService',
-                function ($rootScope, $window, $timeout, ks) {
-                    return function (scope) {
-                        var unbindWatch;
+            function ($rootScope, $window, $timeout, ks) {
+                return function (scope) {
+                    var unbindWatch;
 
-                        function heightCalc() {
-                            pStartY = fs.noPxStyle(d3.select('.tabular-header'), 'height')
-                                + mast.mastHeight() + topPdg;
-                            wSize = fs.windowSize(pStartY);
-                            pHeight = wSize.height;
-                        }
+                    function heightCalc() {
+                        var tabhead = d3.select('.tabular-header');
 
-                        function initPanel() {
-                            heightCalc();
-                            createDetailsPane();
-                        }
+                        pStartY = fs.noPxStyle(tabhead, 'height') +
+                            mast.mastHeight() + topPdg;
+                        wSize = fs.windowSize(pStartY);
+                        pHeight = wSize.height;
+                    }
 
-                        // Safari has a bug where it renders the fixed-layout table wrong
-                        // if you ask for the window's size too early
-                        if (scope.onos.browser === 'safari') {
-                            $timeout(initPanel);
-                        } else {
-                            initPanel();
+                    function initPanel() {
+                        heightCalc();
+                        createDetailsPane();
+                    }
+
+                    // Safari has a bug where it renders the fixed-layout
+                    // table wrong if you ask for the window's size too early
+                    if (scope.onos.browser === 'safari') {
+                        $timeout(initPanel);
+                    } else {
+                        initPanel();
+                    }
+                    // create key bindings to handle panel
+                    ks.keyBindings({
+                        esc: [handleEscape, 'Close the details panel'],
+                        _helpFormat: ['esc'],
+                    });
+                    ks.gestureNotes([
+                        ['click', 'Select a row to show cluster node details'],
+                        ['scroll down', 'See available cluster nodes'],
+                    ]);
+                    // if the panelData changes
+                    scope.$watch('panelData', function () {
+                        if (!fs.isEmptyObject(scope.panelData)) {
+                            populateDetails(scope.panelData);
+                            detailsPanel.show();
                         }
-                        // create key bindings to handle panel
-                        ks.keyBindings({
-                            esc: [handleEscape, 'Close the details panel'],
-                            _helpFormat: ['esc'],
-                        });
-                        ks.gestureNotes([
-                            ['click', 'Select a row to show cluster node details'],
-                            ['scroll down', 'See available cluster nodes'],
-                        ]);
-                        // if the panelData changes
-                        scope.$watch('panelData', function () {
+                    });
+                    // if the window size changes
+                    unbindWatch = $rootScope.$watchCollection(
+                        function () {
+                            return {
+                                h: $window.innerHeight,
+                                w: $window.innerWidth,
+                            };
+                        }, function () {
                             if (!fs.isEmptyObject(scope.panelData)) {
+                                heightCalc();
                                 populateDetails(scope.panelData);
-                                detailsPanel.show();
                             }
-                        });
-                        // if the window size changes
-                        unbindWatch = $rootScope.$watchCollection(
-                            function () {
-                                return {
-                                    h: $window.innerHeight,
-                                    w: $window.innerWidth,
-                                };
-                            }, function () {
-                                if (!fs.isEmptyObject(scope.panelData)) {
-                                    heightCalc();
-                                    populateDetails(scope.panelData);
-                                }
-                            }
-                        );
+                        }
+                    );
 
-                        scope.$on('$destroy', function () {
-                            unbindWatch();
-                            ps.destroyPanel(pName);
-                        });
-                    };
-                }]);
+                    scope.$on('$destroy', function () {
+                        unbindWatch();
+                        ps.destroyPanel(pName);
+                    });
+                };
+            }]);
 
 }());
diff --git a/web/gui/src/main/webapp/tests/app/view/flow/flow-spec.js b/web/gui/src/main/webapp/tests/app/view/flow/flow-spec.js
index 48ca797..dc0fe47 100644
--- a/web/gui/src/main/webapp/tests/app/view/flow/flow-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/flow/flow-spec.js
@@ -39,4 +39,41 @@
         var controller = createController();
     });
 
+    // Unit test for defect ONOS-6332
+    // See lines 164-167 addLabVal(...) of flow.js which handles display of
+    //   properties in the details panel
+    //  (this is if we keep the device ID in the extension rendered string)
+    it('should break into two pieces, using first colon only', function () {
+        var lv = 'EXTENSION:of:0000000000000205/Ofdpa3SetMplsType{mplsType=32}',
+            b1 = 'EXTENSION',
+            b2 = 'of:0000000000000205/Ofdpa3SetMplsType{mplsType=32}';
+
+        var bits = lv.match(/^([^:]*):(.*)/);
+
+        // console.log(bits);
+
+        expect(bits.length).toBe(3);
+        expect(bits[0]).toBe(lv);
+        expect(bits[1]).toBe(b1);
+        expect(bits[2]).toBe(b2);
+    });
+
+    // Unit test for defect ONOS-6332
+    // See lines 164-167 addLabVal(...) of flow.js which handles display of
+    //   properties in the details panel
+    //  (this is if we DON'T keep the device ID in the extension rendered string)
+    it('should break into two pieces, using first colon only (2)', function () {
+        var lv = 'EXTENSION:Ofdpa3SetMplsType{mplsType=32}',
+            b1 = 'EXTENSION',
+            b2 = 'Ofdpa3SetMplsType{mplsType=32}';
+
+        var bits = lv.match(/^([^:]*):(.*)/);
+
+        // console.log(bits);
+
+        expect(bits.length).toBe(3);
+        expect(bits[0]).toBe(lv);
+        expect(bits[1]).toBe(b1);
+        expect(bits[2]).toBe(b2);
+    });
 });