[NIFI-8006] Additional options on UI to leave current process group (#5678)

- 'Leave group' action button is added to 'Navigation'
- 'Leave group' action works with 'esc' hotkey if no modal, context menu, etc. is open
- 'esc' key closes context menu if it is open
- user guide is updated with new navigation options

This closes #5678 
diff --git a/nifi-docs/src/main/asciidoc/user-guide.adoc b/nifi-docs/src/main/asciidoc/user-guide.adoc
index 7583759..fb1e612 100644
--- a/nifi-docs/src/main/asciidoc/user-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/user-guide.adoc
@@ -1781,7 +1781,7 @@
 [[navigating]]
 == Navigating within a DataFlow
 
-NiFi provides various mechanisms for getting around a dataflow. The <<User_Interface>> section describes various ways to navigate around the NiFi canvas; however, once a flow exists on the canvas, there are additional ways to get from one component to another. When multiple Process Groups exist in a flow, breadcrumbs appear at the bottom of the screen, providing a way to navigate between them. In addition, to enter a Process Group that is currently visible on the canvas, simply double-click it, thereby "drilling down" into it. Connections also provide a way to jump from one location to another within the flow. Right-click on a connection and select "Go to source" or "Go to destination" in order to jump to one end of the connection or another. This can be very useful in large, complex dataflows, where the connection lines may be long and span large areas of the canvas. Finally, all components provide the ability to jump forward or backward within the flow. Right-click any component (e.g., a processor, process group, port, etc.) and select either "Upstream connections" or "Downstream connections". A dialog window will open, showing the available upstream or downstream connections that the user may jump to. This can be especially useful when trying to follow a dataflow in a backward direction. It is typically easy to follow the path of a dataflow from start to finish, drilling down into nested process groups; however, it can be more difficult to follow the dataflow in the other direction.
+NiFi provides various mechanisms for getting around a dataflow. The <<User_Interface>> section describes various ways to navigate around the NiFi canvas; however, once a flow exists on the canvas, there are additional ways to get from one component to another. When multiple Process Groups exist in a flow, breadcrumbs appear at the bottom of the screen, providing a way to navigate between them. In addition, to enter a Process Group that is currently visible on the canvas, simply double-click it, thereby "drilling down" into it. To leave a Process Group there are multiple ways: from the context menu opened by right-click on the canvas; with the 'Leave group' button on the 'Navigation' panel; with 'esc' key if no modal or context menu is open. Connections also provide a way to jump from one location to another within the flow. Right-click on a connection and select "Go to source" or "Go to destination" in order to jump to one end of the connection or another. This can be very useful in large, complex dataflows, where the connection lines may be long and span large areas of the canvas. Finally, all components provide the ability to jump forward or backward within the flow. Right-click any component (e.g., a processor, process group, port, etc.) and select either "Upstream connections" or "Downstream connections". A dialog window will open, showing the available upstream or downstream connections that the user may jump to. This can be especially useful when trying to follow a dataflow in a backward direction. It is typically easy to follow the path of a dataflow from start to finish, drilling down into nested process groups; however, it can be more difficult to follow the dataflow in the other direction.
 
 [[component_linking]]
 === Component Linking
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
index 67bda8c..eb2fc62 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
@@ -60,6 +60,12 @@
                      ng-click="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.zoomActualSize();">
                     <button><div class="graph-control-action-icon icon icon-zoom-actual"></div></button>
                 </div>
+                <div class="button-spacer-large">&nbsp;</div>
+                <div id="naviagte-leave-group" class="action-button right" title="Leave group"
+                     ng-if="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.isNotRootGroup()"
+                     ng-click="appCtrl.serviceProvider.graphControlsCtrl.navigateCtrl.leaveGroup();">
+                    <button><div class="graph-control-action-icon fa fa-level-up"></div></button>
+                </div>
                 <div class="clear"></div>
             </div>
             <div id="birdseye"></div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
index 0c23683..0ae829b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
@@ -184,6 +184,10 @@
     float: left;
 }
 
+div.action-button.right {
+    float: right;
+}
+
 #operate-delete button {
     width: inherit;
     padding: 0 7px;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-navigate-controller.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-navigate-controller.js
index 6f1430f..6926308 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-navigate-controller.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-navigate-controller.js
@@ -75,6 +75,22 @@
                     nfCanvasUtils.actualSizeCanvas();
                 }, 0);
             };
+
+            /**
+             * Leaves current process group.
+             */
+            this.leaveGroup = function () {
+                $timeout(function () {
+                    nfCanvasUtils.executeAction('leaveGroup');
+                }, 0);
+            };
+
+            /**
+             * Returns true if the current Process Group is not the root.
+             */
+            this.isNotRootGroup = function () {
+                return nfCanvasUtils.getParentGroupId() !== null;
+            };
         }
 
         NavigateCtrl.prototype = {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 5588422..fff859b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -2257,6 +2257,17 @@
          */
         getCanvasOffset: function () {
             return nfCanvas.CANVAS_OFFSET;
+        },
+
+        /**
+         * Executes the specified action with the optional selection.
+         *
+         * @param {string} action
+         * @param {selection} selection
+         */
+        executeAction: function (action, selection) {
+            // execute the action
+            nfActions[action](selection);
         }
     };
     return nfCanvasUtils;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index a569a29..3bc7ff9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -823,6 +823,22 @@
                         }
 
                         // default prevented in nf-universal-capture.js
+                    } else if (evt.keyCode === 27) {
+                        // esc
+                        var contextMenuVisible = $('#context-menu').is(':visible');
+                        // if context menu is visible, then hide it
+                        if (contextMenuVisible) {
+                            nfContextMenu.hide();
+                            return;
+                        }
+
+                        var cancellables = $('.cancellable');
+                        var isAnyCancellableVisible = cancellables.length
+                            && cancellables.toArray().some(function (c) { return $(c).is(':visible'); });
+                        // if no cancellable (modals, etc.) and context menu is visible, then leave current process group
+                        if (!isAnyCancellableVisible && !contextMenuVisible && nfCanvasUtils.getParentGroupId() !== null) {
+                            nfActions['leaveGroup']();
+                        }
                     }
                 }
             });