This is an automated email from the ASF dual-hosted git repository.

mcgilman pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 4104cfd78d NIFI-11520: Add a menu to display Flow Analysis report 
results (#8273)
4104cfd78d is described below

commit 4104cfd78d829ec4ab6bf4470ae472a6eda74171
Author: Shane Ardell <sard...@users.noreply.github.com>
AuthorDate: Tue Apr 16 17:34:19 2024 -0400

    NIFI-11520: Add a menu to display Flow Analysis report results (#8273)
    
    * NIFI-11520: init ui work for flow analysis UI
    
    * NIFI-11520: use .text() to render user input data
    
    * NIFI-11520: update urls for analysis requests
    
    * NIFI-11520: add WARN enforcement level to ui
    
    * NIFI-11520: ui bug fixes
    
    * fix rule bindings
    * use correct count for rule violations
    
    * NIFI-11520: move drawer markup into partial file
    
    * NIFI-11520: remove old recs and policies naming
    
    * NIFI-11520: comments and code cleanup
    
    * NIFI-11520: fix linting errors
    
    * NIFI-11520: restore refresh button logic
    
    * NIFI-11520: remove timer
    
    * NIFI-11520: add checkbox to only show warning violations
    
    * NIFI-11520: style and copy changes
    
    * change copy of violation checkboxes
    * show correct details in violation dialog
    * add overflow to violations menu
    
    * NIFI-11520: add missing license header
    
    * NIFI-11520: remove unused function
    
    * NIFI-11520: cleanup rule and violation menu handling
    
    * NIFI-11520: remove single use functions
    
    * NIFI-11520: change function name to match established pattern
    
    * NIFI-11520: rename function
    
    * NIFI-11520: remove rule and violation details menu
    
    * NIFI-11520: fix issue causing wrong documentation to be displayed
    
    * NIFI-11520: fix go to button for components in nested process groups
    
    * NIFI-11520: use refresh interval returned from the backend
    
    * NIFI-11520: reload analysis when canvas is refreshed
    
    * NIFI-11520: add violation details dialog with correct message
    
    * NIFI-11520: disabled go to component when root group is violation
    
    * NIFI-11520: remove unused CSS styles
    
    * NIFI-11520: addressing more feedback:
    
    * fix flow analysis drawer button styling
    * disable edit rule if user does not have read permission
    * fix broken warning list
    * add loader and disable check now button while report is running
    
    * NIFI-11520: handle violations without read permission
    
    * NIFI-11520: disable go to component if not processor
    
    * NIFI-11520: remove create analysis button and logic
    
    * NIFI-11520: add pending analysis message
    
    * NIFI-11520: protect against scenario where currentUser not loaded
    
    * NIFI-11520: determine root group based on groupId being null
    
    * NIFI-11520: address review feedback
    
    * simplify go to logic by only showing for processors
    * hide go to button instead of disabling
    
    * NIFI-11520: fix hidden state
    
    * NIFI-11520: hide go to based on permissions
    
    This closes #8273
---
 .../nifi-framework/nifi-web/nifi-web-ui/pom.xml    |   2 +
 .../src/main/resources/filters/summary.properties  |   1 +
 .../src/main/webapp/WEB-INF/pages/canvas.jsp       |   2 +
 .../partials/canvas/flow-analysis-drawer.jsp       |  84 +++
 .../webapp/WEB-INF/partials/canvas/flow-status.jsp |   1 +
 .../canvas/violation-description-dialog.jsp        |  28 +
 .../nifi-web-ui/src/main/webapp/css/canvas.css     |   1 +
 .../src/main/webapp/css/flow-analysis-drawer.css   | 463 ++++++++++++++++
 .../nf-ng-canvas-flow-status-controller.js         | 584 ++++++++++++++++++++-
 .../src/main/webapp/js/nf/canvas/nf-actions.js     |   1 +
 .../webapp/js/nf/canvas/nf-canvas-bootstrap.js     |   2 +-
 .../webapp/js/nf/canvas/nf-flow-analysis-rule.js   |  10 +-
 .../nifi-web-ui/src/main/webapp/js/nf/nf-common.js |  19 +
 13 files changed, 1185 insertions(+), 13 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
index c401e4b440..f1d087c979 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
@@ -741,6 +741,7 @@
                                                 
<include>${staging.dir}/css/settings.css</include>
                                                 
<include>${staging.dir}/css/about.css</include>
                                                 
<include>${staging.dir}/css/status-history.css</include>
+                                                
<include>${staging.dir}/css/flow-analysis-drawer.css</include>
                                             </includes>
                                         </aggregation>
                                         <aggregation>
@@ -780,6 +781,7 @@
                                                 
<include>${staging.dir}/css/processor-details.css</include>
                                                 
<include>${staging.dir}/css/connection-details.css</include>
                                                 
<include>${staging.dir}/css/status-history.css</include>
+                                                
<include>${staging.dir}/css/flow-analysis-drawer.css</include>
                                                 
<include>${staging.dir}/css/summary.css</include>
                                             </includes>
                                         </aggregation>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
index a45a07a430..9f3cc38b0d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/summary.properties
@@ -43,4 +43,5 @@ nf.summary.style.tags=<link rel="stylesheet" 
href="css/main.css?${project.versio
 <link rel="stylesheet" href="css/connection-details.css?${project.version}" 
type="text/css" />\n\
 <link rel="stylesheet" href="css/message-pane.css?${project.version}" 
type="text/css" />\n\
 <link rel="stylesheet" href="css/status-history.css?${project.version}" 
type="text/css" />\n\
+<link rel="stylesheet" href="css/flow-analysis-drawer.css?${project.version}" 
type="text/css" />\n\
 <link rel="stylesheet" href="css/summary.css?${project.version}" 
type="text/css" />
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
index 51e1e08160..152435b891 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
@@ -128,6 +128,7 @@
         <jsp:include 
page="/WEB-INF/partials/canvas/registry-configuration-dialog.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/new-registry-client-dialog.jsp"/>
         <div id="canvas-container" class="unselectable"></div>
+        <jsp:include page="/WEB-INF/partials/canvas/flow-analysis-drawer.jsp"/>
         <div id="canvas-tooltips">
             <div id="processor-tooltips"></div>
             <div id="port-tooltips"></div>
@@ -145,6 +146,7 @@
         <jsp:include 
page="/WEB-INF/partials/canvas/parameter-provider-configuration.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/processor-configuration.jsp"/>
         <jsp:include page="/WEB-INF/partials/processor-details.jsp"/>
+        <jsp:include 
page="/WEB-INF/partials/canvas/violation-description-dialog.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/process-group-configuration.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/override-policy-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/policy-management.jsp"/>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-analysis-drawer.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-analysis-drawer.jsp
new file mode 100644
index 0000000000..ad1f64ab1f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-analysis-drawer.jsp
@@ -0,0 +1,84 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<section id="flow-analysis-drawer">
+    <div class="flow-analysis-header">
+        <div id="flow-analysis-loading-container" 
class="flow-analysis-loading-container"></div>
+        <div id="flow-analysis-loading-message" 
class="flow-analysis-loading-message">Rules analysis pending...</div>
+    </div>
+    <div class="flow-analysis-flow-guide-container">
+        <div class="flow-analysis-flow-guide">
+            <div class="flow-analysis-flow-guide-title">Flow Guide</div>
+            <div>
+                <div class="flow-analysis-violations-options">
+                    <div class="nf-checkbox checkbox-unchecked" 
id="show-only-violations"></div>
+                    <span class="nf-checkbox-label 
show-only-violations-label">Show enforced violations</span>
+                </div>
+                <div class="flow-analysis-warnings-options">
+                    <div class="nf-checkbox checkbox-unchecked" 
id="show-only-warnings"></div>
+                    <span class="nf-checkbox-label 
show-only-warnings-label">Show warning violations</span>
+                </div>
+            </div>
+        </div>
+        <div class="flow-analysis-flow-guide-breadcrumb">NiFi Flow</div>
+    </div>
+    <div id="flow-analysis-rules-accordion" 
class="flow-analysis-rules-accordion">
+
+        <div id="required-rules" class="required-rules">
+            <div>
+                <div>Enforced Rules <span id="required-rule-count" 
class="required-rule-count"></span></div>
+            </div>
+            <ul id="required-rules-list" class="required-rules-list">
+            </ul>
+        </div>
+
+        <div id="recommended-rules" class="recommended-rules">
+            <div>
+                <div>Warning Rules <span id="recommended-rule-count" 
class="recommended-rule-count"></span></div>
+            </div>
+            <ul id="recommended-rules-list" 
class="recommended-rules-list"></ul>
+        </div>
+
+        <div id="rule-violations" class="rule-violations">
+            <div class="rules-violations-header">
+                <div>Enforced Violations <span id="rule-violation-count" 
class="rule-violation-count"></span></div>
+            </div>
+            <ul id="rule-violations-list" class="rule-violations-list"></ul>
+        </div>
+
+        <div id="rule-warnings" class="rule-warnings">
+            <div class="rules-warnings-header">
+                <div>Warning Violations <span id="rule-warning-count" 
class="rule-warning-count"></span></div>
+            </div>
+            <ul id="rule-warnings-list" class="rule-warnings-list"></ul>
+        </div>
+
+        <div class="rule-menu" id="rule-menu">
+            <ul>
+                <li class="rule-menu-option" 
id="rule-menu-view-documentation"><i class="fa fa-info-circle 
rule-menu-option-icon" aria-hidden="true"></i>View Documentation</li>
+                <li class="rule-menu-option" id="rule-menu-edit-rule"><i 
class="fa fa-pencil rule-menu-option-icon" aria-hidden="true"></i>Edit Rule</li>
+            </ul>
+        </div>
+
+        <div class="violation-menu" id="violation-menu">
+            <ul>
+                <li class="violation-menu-option" 
id="violation-menu-more-info"><i class="fa fa-info-circle 
violation-menu-option-icon" aria-hidden="true"></i>Violation details</li>
+                <li class="violation-menu-option" id="violation-menu-go-to"><i 
class="fa fa-pencil violation-menu-option-icon" aria-hidden="true"></i>Go to 
component</li>
+            </ul>
+        </div>
+    </div>
+</section>
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
index fbf33a9715..6f339a320e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
@@ -71,6 +71,7 @@
             <button id="search-button" 
ng-click="appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.search.toggleSearchField();"><i
 class="fa fa-search"></i></button>
             <input id="search-field" type="text" placeholder="Search"/>
         </div>
+        <button id="flow-analysis" class="flow-analysis"><i class="fa 
fa-lightbulb-o flow-analysis-notification-icon"></i></button>
         <button id="bulletin-button"><i class="fa 
fa-sticky-note-o"></i></button>
     </div>
 </div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/violation-description-dialog.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/violation-description-dialog.jsp
new file mode 100644
index 0000000000..23dc2d10e1
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/violation-description-dialog.jsp
@@ -0,0 +1,28 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="violation-menu-more-info-dialog" layout="column" class="hidden 
medium-dialog">
+    <div class="dialog-content">
+        <div class="violation-info-head">
+            <div id="violation-name" class="violation-name">Violation</div>
+            <div id="violation-type-pill" class="violation-type-pill"></div>
+        </div>
+        <div id="violation-display-name" class="violation-display-name"></div>
+
+        <p id="violation-description" class="violation-description"></p>
+
+        <i class="fa fa-book violation-docs-link-icon" 
aria-hidden="true"></i><a href="" class="violation-docs-link">View 
Documentation</a>
+    </div>
+</div>
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
index 8042c22a13..240dd14765 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
@@ -53,3 +53,4 @@
 @import url(status-history.css);
 @import url(../fonts/flowfont/flowfont.css);
 @import url(../assets/font-awesome/css/font-awesome.css);
+@import url(flow-analysis-drawer.css);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-analysis-drawer.css
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-analysis-drawer.css
new file mode 100644
index 0000000000..50299bd982
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/flow-analysis-drawer.css
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+.flow-analysis {
+  background-color: #E3E8EB;
+  padding: 0;
+}
+
+.flow-analysis:hover,
+.flow-analysis.opened {
+  background-color: #FFFFFF;
+}
+
+#flow-status .flow-analysis .fa-lightbulb-o {
+  padding: 4px;
+  width: 14px;
+  height: 14px;
+  border-radius: 15px;
+}
+
+#flow-status .flow-analysis .fa-lightbulb-o.recommendations {
+  border: 1px solid #176e83;
+  border-radius: 15px;
+}
+
+#flow-status .flow-analysis .fa-lightbulb-o.violations {
+  border: 0;
+  padding: 4px;
+  color: white;
+  width: 14px;
+  height: 14px;
+  background-color: #ba554a;
+  border-radius: 15px;
+}
+
+#flow-analysis-drawer {
+  background-color: rgba(249, 250, 251, 0.9);
+  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
+  height: calc(100vh - 115px);
+  overflow-y: scroll;
+  padding: 20px;
+  position: fixed;
+  right: -400px;
+  transition: right 0.3s ease-in-out;
+  width: 341px;
+  z-index: 2;
+}
+
+#flow-analysis-drawer.opened {
+  right: 0;
+}
+
+.flow-analysis-header {
+  align-items: center;
+  display: flex;
+  width: 100%;
+}
+
+.flow-analysis-loading-container {
+  background-color: transparent;
+  float: left;
+  height: 16px;
+  margin-left: 0;
+  margin-right: 5px;
+  margin-top: 0;
+  width: 16px;
+}
+
+.flow-analysis-loading-message {
+  display: none;
+}
+
+.flow-analysis-flow-guide-title {
+  color: #728e9b;
+  font-family: Roboto Slab;
+  font-size: 16px;
+  font-stretch: normal;
+  font-style: normal;
+  font-weight: 500;
+  letter-spacing: normal;
+  line-height: normal;
+  margin-bottom: 5px;
+}
+
+.flow-analysis-flow-guide {
+  align-items: center;
+  display: flex;
+  justify-content: space-between;
+}
+
+.flow-analysis-violations-options {
+  align-items: center;
+  display: flex;
+  margin-bottom: 4px;
+}
+
+.flow-analysis-refresh {
+  align-items: center;
+  display: flex;
+}
+
+.flow-analysis-flow-guide-container {
+  margin-top: 22px;
+}
+
+#flow-analysis-drawer .flow-analysis-rules-accordion {
+  margin-top: 10px;
+  padding: 0 0 50px 0;
+}
+
+#flow-analysis-drawer
+  .flow-analysis-rules-accordion
+  .ui-accordion-header,
+#flow-analysis-drawer .rules-violations-header,
+#flow-analysis-drawer .rules-warnings-header {
+  background-color: transparent;
+  border: 0;
+  color: black;
+  display: flex;
+  font-family: Roboto Slab;
+  font-size: 12px;
+  font-weight: 500;
+  justify-content: space-between;
+  padding: 10px 0;
+  border-bottom: 1px solid #ddd;
+  align-items: center;
+}
+
+.flow-analysis-rules-accordion .ui-accordion-header-icon {
+  order: 2;
+}
+
+#flow-analysis-drawer .recs-poliicies-accordion-header-text {
+  color: #262626;
+  font-family: Roboto Slab;
+  font-size: 12px;
+  font-weight: 500;
+  letter-spacing: 0.01px;
+  margin: 6px 5px 6px 0;
+}
+
+#flow-analysis-drawer .recommended-rules-list,
+#flow-analysis-drawer .required-rules-list,
+#flow-analysis-drawer .rule-warnings-list {
+  padding: 0;
+  background-color: transparent;
+  border: 0;
+}
+
+#flow-analysis-drawer .ui-icon {
+  background-image: none;
+  text-indent: 0;
+}
+
+.required-rule-count,
+.recommended-rule-count,
+.rule-violation-count,
+.rule-warning-count {
+  font-family: Roboto;
+  font-size: 14px;
+  font-weight: 400;
+}
+
+#flow-analysis-drawer .rules-list-item {
+  border-bottom: 1px solid #ddd;
+}
+
+#flow-analysis-drawer .rules-list-rule-info {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 10px 0 10px 10px;
+  font-family: Roboto;
+  font-size: 12px;
+  font-weight: normal;
+  font-stretch: normal;
+  font-style: normal;
+  line-height: normal;
+  letter-spacing: normal;
+  text-align: left;
+  color: #262626;
+}
+
+#flow-analysis-drawer .show-only-violations-label,
+#flow-analysis-drawer .show-only-warnings-label {
+  font-family: Roboto;
+  font-size: 13px;
+  font-weight: normal;
+  font-stretch: normal;
+  font-style: normal;
+  line-height: normal;
+  letter-spacing: normal;
+  text-align: left;
+  color: #262626;
+}
+
+#flow-analysis-drawer .rules-list-item-menu-target {
+  font-size: 14px;
+  color: #054849;
+}
+
+#flow-analysis-drawer .rule-menu-btn,
+#flow-analysis-drawer .violation-menu-btn {
+  border: 0;
+}
+
+#flow-analysis-drawer .rule-menu,
+#flow-analysis-drawer .violation-menu {
+  width: 200px;
+  box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.3);
+  border: solid 1px #004849;
+  background-color: #fff;
+}
+
+#flow-analysis-drawer .rule-menu-option,
+#flow-analysis-drawer .violation-menu-option {
+  padding: 3px 0 3px 10px;
+  background-color: #fff;
+  font-family: Roboto;
+  font-size: 13px;
+  font-weight: normal;
+  font-stretch: normal;
+  font-style: normal;
+  line-height: 1.15;
+  letter-spacing: normal;
+  text-align: left;
+  color: #262626;
+  display: flex;
+  align-items: center;
+  margin: 3px 0;
+}
+
+#flow-analysis-drawer .rule-menu-option.hidden,
+#flow-analysis-drawer .violation-menu-option.hidden {
+  display: none;
+}
+
+#flow-analysis-drawer .rule-menu-option:hover:not(.disabled),
+#flow-analysis-drawer .violation-menu-option:hover:not(.disabled) {
+  background-color: #dce3e6;
+  color: #262626;
+  cursor: pointer;
+}
+
+#flow-analysis-drawer .violation-menu-option.disabled,
+#flow-analysis-drawer .rule-menu-option.disabled,
+#flow-analysis-drawer .violation-menu-option-icon,
+#flow-analysis-drawer .rule-menu-option-icon {
+  float: none;
+}
+
+#flow-analysis-drawer .rule-menu-option-icon,
+#flow-analysis-drawer .violation-menu-option-icon {
+  color: #004849;
+  font-size: 18px;
+  margin-right: 10px;
+}
+
+/* Rule Information Modal */
+.rule-info-head,
+.violation-info-head {
+  margin-top: 20px;
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 5px;
+}
+
+.rule-name,
+.violation-name {
+  align-self: flex-end;
+  font-family: Roboto Slab;
+  font-size: 12px;
+  letter-spacing: 0.3px;
+  text-align: left;
+  color: #262626;
+}
+
+.rule-type-pill,
+.violation-type-pill {
+  padding: 5px 10px;
+  border-radius: 12px;
+  font-family: Roboto;
+  font-size: 12px;
+  font-weight: 500;
+  letter-spacing: 0.12px;
+}
+
+.rule-type-pill.enforce,
+.violation-type-pill.enforce {
+  background-color: #ba554a;
+  color: #fff;
+}
+
+.rule-type-pill.warn,
+.violation-type-pill.warn {
+  border: solid 1px #176e83;
+  background-color: #fff;
+}
+
+.rule-name,
+.violation-name {
+  font-family: Roboto Slab;
+  font-size: 12px;
+  line-height: normal;
+}
+
+.rule-display-name,
+.violation-display-name {
+  font-family: Roboto;
+  font-size: 13px;
+  font-weight: 500;
+  color: #775351;
+  margin-bottom: 20px;
+}
+
+.rule-description,
+.violation-description {
+  font-family: Roboto;
+  font-size: 13px;
+  color: #262626;
+  margin: 20px 0;
+}
+
+.fa.rule-docs-link-icon,
+.fa.violation-docs-link-icon {
+  margin: 0 5px 0 0;
+  font-family: FontAwesome;
+  font-size: 16px;
+  color: #004849;
+}
+
+.rule-docs-link,
+.violation-docs-link {
+  font-family: Roboto;
+  font-size: 13px;
+  color: #004849;
+}
+
+/* Violations */
+
+.rule-violations-count {
+  font-family: Roboto;
+  font-size: 12px;
+  font-weight: 500;
+  color: #ba554a;
+  margin-left: 10px;
+}
+
+.rule-recommendations-count {
+  font-family: Roboto;
+  font-size: 12px;
+  font-weight: 500;
+  color: #176e83;
+  margin-left: 10px;
+}
+
+.rule-violations-list,
+.rule-warnings-list,
+.rule-recommendations-list {
+  list-style: none;
+}
+
+.rule-violations-list li,
+.rule-warnings-list li,
+.rule-recommendations-list li {
+  border-bottom: 1px solid #ddd;
+}
+
+.violation-menu-btn,
+.recommendation-menu-btn {
+  margin-left: auto;
+  border: 0;
+}
+
+.violation-list-item,
+.recommendation-list-item,
+.rule-violations-list-item-wrapper,
+.warning-list-item {
+  padding: 10px 0 10px 15px;
+  display: flex;
+  align-items: center;
+  position: relative;
+}
+
+.violation-list-item-name::before,
+.warning-list-item-name::before,
+.recommendation-list-item-name::before {
+  content: '';
+  position: absolute;
+  left: 9px;
+  top: 13px;
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+}
+
+.violation-list-item-name::before,
+.rule-violations-list-item-wrapper:before {
+  background-color: #ba554a;
+}
+
+.recommendation-list-item-name::before,
+.rule-warnings-list-item-name::before,
+.warning-list-item-name::before,
+.recommendation-list-item-name::before {
+  background-color: transparent;
+  border: 1px solid #176e83;
+}
+
+.violation-list-item-wrapper,
+.recommendation-list-item-wrapper,
+.warning-list-item-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  width: calc(100% - 5px);
+  margin-left: 5px;
+}
+
+.violation-list-item-name,
+.recommendation-list-item-name,
+.warning-list-item-name {
+  font-family: Roboto;
+  font-size: 11px;
+  font-weight: 500;
+}
+
+.rule-violations-list-item-name,
+.rule-warnings-list-item-name {
+  font-family: Roboto;
+  font-size: 12px;
+  font-weight: 500;
+  margin-top: 11px;
+}
+
+.rule-violations-list-item-name {
+  color: #ba554a;
+}
+
+.rule-warnings-list-item-name {
+  color: #176e83;
+}
+
+.violation-list-item-id,
+.recommendation-list-item-id {
+  font-size: 11px;
+  line-height: normal;
+  color: #606060;
+}
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-flow-status-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-flow-status-controller.js
index 58e99759ac..5ac793a650 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-flow-status-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-flow-status-controller.js
@@ -29,9 +29,10 @@
                 'nf.Settings',
                 'nf.ParameterContexts',
                 'nf.ProcessGroup',
-                'nf.ProcessGroupConfiguration'],
-            function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, 
nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, 
nfProcessGroup, nfProcessGroupConfiguration) {
-                return (nf.ng.Canvas.FlowStatusCtrl = factory($, nfCommon, 
nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, 
nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration));
+                'nf.ProcessGroupConfiguration',
+                'nf.Shell'],
+            function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, 
nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, 
nfProcessGroup, nfProcessGroupConfiguration, nfShell) {
+                return (nf.ng.Canvas.FlowStatusCtrl = factory($, nfCommon, 
nfDialog, nfCanvasUtils, nfContextMenu, nfClusterSummary, nfErrorHandler, 
nfSettings, nfParameterContexts, nfProcessGroup, nfProcessGroupConfiguration, 
nfShell));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ng.Canvas.FlowStatusCtrl =
@@ -45,7 +46,8 @@
                 require('nf.Settings'),
                 require('nf.ParameterContexts'),
                 require('nf.ProcessGroup'),
-                require('nf.ProcessGroupConfiguration')));
+                require('nf.ProcessGroupConfiguration'),
+                require('nf.Shell')));
     } else {
         nf.ng.Canvas.FlowStatusCtrl = factory(root.$,
             root.nf.Common,
@@ -57,9 +59,10 @@
             root.nf.Settings,
             root.nf.ParameterContexts,
             root.nf.ProcessGroup,
-            root.nf.ProcessGroupConfiguration);
+            root.nf.ProcessGroupConfiguration,
+            root.nf.Shell);
     }
-}(this, function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, 
nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, 
nfProcessGroup, nfProcessGroupConfiguration) {
+}(this, function ($, nfCommon, nfDialog, nfCanvasUtils, nfContextMenu, 
nfClusterSummary, nfErrorHandler, nfSettings, nfParameterContexts, 
nfProcessGroup, nfProcessGroupConfiguration, nfShell) {
     'use strict';
 
     return function (serviceProvider) {
@@ -69,10 +72,13 @@
             search: 'Search',
             urls: {
                 search: '../nifi-api/flow/search-results',
-                status: '../nifi-api/flow/status'
+                status: '../nifi-api/flow/status',
+                flowAnalysis: '../nifi-api/controller/analyze-flow'
             }
         };
 
+        var previousRulesResponse = {};
+
         function FlowStatusCtrl() {
             this.connectedNodesCount = "-";
             this.clusterConnectionWarning = false;
@@ -400,6 +406,561 @@
                 }
             }
 
+            /**
+             * The flow analysis controller.
+             */
+
+            this.flowAnalysis = {
+
+                /**
+                 * Create the list of rule violations
+                 */
+                buildRuleViolationsList: function(rules, violationsAndRecs) {
+                    var ruleViolationCountEl = $('#rule-violation-count');
+                    var ruleViolationListEl = $('#rule-violations-list');
+                    var ruleWarningCountEl = $('#rule-warning-count');
+                    var ruleWarningListEl = $('#rule-warnings-list');
+                    var violations = violationsAndRecs.filter(function 
(violation) {
+                        return violation.enforcementPolicy === 'ENFORCE'
+                    });
+                    var warnings = violationsAndRecs.filter(function 
(violation) {
+                        return violation.enforcementPolicy === 'WARN'
+                    });
+                    ruleViolationCountEl.empty().text('(' + violations.length 
+ ')');
+                    ruleWarningCountEl.empty().text('(' + warnings.length + 
')');
+                    ruleViolationListEl.empty();
+                    ruleWarningListEl.empty();
+                    violations.forEach(function(violation) {
+                        var rule = rules.find(function(rule) {
+                            return rule.id === violation.ruleId;
+                        });
+                        // create DOM elements
+                        var violationListItemEl = $('<li></li>');
+                        var violationEl = $('<div 
class="violation-list-item"></div>');
+                        var violationListItemWrapperEl = $('<div 
class="violation-list-item-wrapper"></div>');
+                        var violationRuleEl = $('<div 
class="rule-violations-list-item-name"></div>');
+                        var violationListItemNameEl = $('<div 
class="violation-list-item-name"></div>');
+                        var violationListItemIdEl = $('<span 
class="violation-list-item-id"></span>');
+                        var violationInfoButtonEl = $('<button 
class="violation-menu-btn"><i class="fa fa-ellipsis-v 
rules-list-item-menu-target" aria-hidden="true"></i></button>');
+
+                        // add text content and button data
+                        $(violationRuleEl).text(rule.name);
+                        violation.subjectPermissionDto.canRead ? 
$(violationListItemNameEl).text(violation.subjectDisplayName) : 
$(violationListItemNameEl).text('Unauthorized').addClass('unauthorized');
+                        $(violationListItemIdEl).text(violation.subjectId);
+                        
$(violationListItemEl).append(violationRuleEl).append(violationListItemWrapperEl);
+                        $(violationInfoButtonEl).data('violationInfo', 
violation);
+
+                        // build list DOM structure
+                        
violationListItemWrapperEl.append(violationListItemNameEl).append(violationListItemIdEl);
+                        
violationEl.append(violationListItemWrapperEl).append(violationInfoButtonEl);
+                        
violationListItemEl.append(violationRuleEl).append(violationEl)
+                        ruleViolationListEl.append(violationListItemEl);
+                    });
+
+                    warnings.forEach(function(warning) {
+                        var rule = rules.find(function(rule) {
+                            return rule.id === warning.ruleId;
+                        });
+                        // create DOM elements
+                        var warningListItemEl = $('<li></li>');
+                        var warningEl = $('<div 
class="warning-list-item"></div>');
+                        var warningListItemWrapperEl = $('<div 
class="warning-list-item-wrapper"></div>');
+                        var warningRuleEl = $('<div 
class="rule-warnings-list-item-name"></div>');
+                        var warningListItemNameEl = $('<div 
class="warning-list-item-name"></div>');
+                        var warningListItemIdEl = $('<span 
class="warning-list-item-id"></span>');
+                        var warningInfoButtonEl = $('<button 
class="violation-menu-btn"><i class="fa fa-ellipsis-v 
rules-list-item-menu-target" aria-hidden="true"></i></button>');
+
+                        // add text content and button data
+                        $(warningRuleEl).text(rule.name);
+                        warning.subjectPermissionDto.canRead ? 
$(warningListItemNameEl).text(warning.subjectDisplayName) : 
$(warningListItemNameEl).text('Unauthorized').addClass('unauthorized');
+                        $(warningListItemIdEl).text(warning.subjectId);
+                        
$(warningListItemEl).append(warningRuleEl).append(warningListItemWrapperEl);
+                        $(warningInfoButtonEl).data('violationInfo', warning);
+
+                        // build list DOM structure
+                        
warningListItemWrapperEl.append(warningListItemNameEl).append(warningListItemIdEl);
+                        
warningEl.append(warningListItemWrapperEl).append(warningInfoButtonEl);
+                        
warningListItemEl.append(warningRuleEl).append(warningEl)
+                        ruleWarningListEl.append(warningListItemEl);
+                    });
+                },
+
+                /**
+                 * 
+                 * Render a new list when it differs from the previous 
violations response
+                 */
+                buildRuleViolations: function(rules, violations) {
+                    if (Object.keys(previousRulesResponse).length !== 0) {
+                        var previousRulesResponseSorted = 
_.sortBy(previousRulesResponse.ruleViolations, 'subjectId');
+                        var violationsSorted = _.sortBy(violations, 
'subjectId');
+                        if (!_.isEqual(previousRulesResponseSorted, 
violationsSorted)) {
+                            this.buildRuleViolationsList(rules, violations);
+                        }
+                    } else {
+                        this.buildRuleViolationsList(rules, violations);
+                    }
+                },
+
+                /**
+                 * Create the list of flow policy rules
+                 */
+                buildRuleList: function(ruleType, violationsMap, rec) {
+                    var requiredRulesListEl = $('#required-rules-list');
+                    var recommendedRulesListEl = $('#recommended-rules-list');
+                    var rule = $('<li 
class="rules-list-item"></li>').append($(rec.requirement).append(rec.requirementInfoButton))
+                    var violationsListEl = '';
+                    var violationCountEl = '';
+                    
+                    var violations = violationsMap.get(rec.id);
+                    if (!!violations) {
+                        if (violations.length === 1) {
+                            violationCountEl = '<div class="rule-' + ruleType 
+ 's-count">' + violations.length + ' ' + ruleType + '</div>';
+                        } else {
+                            violationCountEl = '<div class="rule-' + ruleType 
+ 's-count">' + violations.length + ' ' + ruleType + 's</div>';
+                        }
+                        violationsListEl = $('<ul class="rule-' + ruleType + 
's-list"></ul>');
+                        violations.forEach(function(violation) {
+                            // create DOM elements
+                            var violationListItemEl = $('<li class="' + 
ruleType + '-list-item"></li>');
+                            var violationWrapperEl = $('<div class="' + 
ruleType + '-list-item-wrapper"></div>');
+                            var violationNameEl = $('<div class="' + ruleType 
+ '-list-item-name"></div>');
+                            var violationIdEl = $('<span class="' + ruleType + 
'-list-item-id"></span>');
+                            var violationInfoButtonEl = $('<button 
class="violation-menu-btn"><i class="fa fa-ellipsis-v 
rules-list-item-menu-target" aria-hidden="true"></i></button>');
+
+                            // add text content and button data
+                            violation.subjectPermissionDto.canRead ? 
violationNameEl.text(violation.subjectDisplayName) : 
violationNameEl.text('Unauthorized');
+                            violationIdEl.text(violation.subjectId);
+
+                            // build list DOM structure
+                            violationListItemEl.append(violationWrapperEl);
+                            
violationWrapperEl.append(violationNameEl).append(violationIdEl)
+                            violationInfoButtonEl.data('violationInfo', 
violation);
+                            
(violationsListEl).append(violationListItemEl.append(violationInfoButtonEl));
+                        });
+                        rule.append(violationCountEl).append(violationsListEl);
+                    }
+                    ruleType === 'violation' ? 
requiredRulesListEl.append(rule) : recommendedRulesListEl.append(rule);
+                },
+
+                /**
+                 * Loads the current status of the flow.
+                 */
+                loadFlowPolicies: function () {
+                    var flowAnalysisCtrl = this;
+                    var requiredRulesListEl = $('#required-rules-list');
+                    var recommendedRulesListEl = $('#recommended-rules-list');
+                    var requiredRuleCountEl = $('#required-rule-count');
+                    var recommendedRuleCountEl = $('#recommended-rule-count');
+                    var flowAnalysisLoader = 
$('#flow-analysis-loading-container');
+                    var flowAnalysisLoadMessage = 
$('#flow-analysis-loading-message');
+
+                    var groupId = nfCanvasUtils.getGroupId();
+                    if (groupId !== 'root') {
+                        $.ajax({
+                            type: 'GET',
+                            url: '../nifi-api/flow/flow-analysis/results/' + 
groupId,
+                            dataType: 'json',
+                            context: this
+                        }).done(function (response) {
+                            var recommendations = [];
+                            var requirements = [];
+                            var requirementsTotal = 0;
+                            var recommendationsTotal = 0;
+
+                            if (!_.isEqual(previousRulesResponse, response)) {
+                                // clear previous accordion content
+                                requiredRulesListEl.empty();
+                                recommendedRulesListEl.empty();
+                                
flowAnalysisCtrl.buildRuleViolations(response.rules, response.ruleViolations);
+
+                                if (response.flowAnalysisPending) {
+                                    
flowAnalysisLoader.addClass('ajax-loading');
+                                    flowAnalysisLoadMessage.show();
+                                } else {
+                                    
flowAnalysisLoader.removeClass('ajax-loading');
+                                    flowAnalysisLoadMessage.hide();
+                                }
+
+                                // For each ruleViolations: 
+                                // * group violations by ruleId
+                                // * build DOM elements
+                                // * get the ruleId and find the matching rule 
id
+                                // * append violation list to matching rule 
list item
+                                var violationsMap = new Map();
+                                
response.ruleViolations.forEach(function(violation) {
+                                    if (violationsMap.has(violation.ruleId)){
+                                        
violationsMap.get(violation.ruleId).push(violation);
+                                     } else {
+                                        violationsMap.set(violation.ruleId, 
[violation]);
+                                     }
+                                });
+    
+                                // build list of recommendations
+                                response.rules.forEach(function(rule) {
+                                    if (rule.enforcementPolicy === 'WARN') {
+                                        var requirement = '<div 
class="rules-list-rule-info"></div>';
+                                        var requirementName = 
$('<div></div>').text(rule.name);
+                                        var requirementInfoButton = '<button 
class="rule-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" 
aria-hidden="true"></i></button>';
+                                        recommendations.push(
+                                            {
+                                                'requirement': 
$(requirement).append(requirementName),
+                                                'requirementInfoButton': 
$(requirementInfoButton).data('ruleInfo', rule),
+                                                'id': rule.id
+                                            }
+                                        )
+                                        recommendationsTotal++;
+                                    }
+                                });
+
+                                // add class to notification icon for 
recommended rules
+                                var hasRecommendations = 
response.ruleViolations.findIndex(function(violation) {
+                                    return violation.enforcementPolicy === 
'WARN';
+                                });
+                                if (hasRecommendations !== -1) {
+                                    $('#flow-analysis 
.flow-analysis-notification-icon ').addClass('recommendations');
+                                } else {
+                                    $('#flow-analysis 
.flow-analysis-notification-icon ').removeClass('recommendations');
+                                }
+    
+                                // build list of requirements
+                                recommendedRuleCountEl.empty().append('(' + 
recommendationsTotal + ')');
+                                recommendations.forEach(function(rec) {
+                                    
flowAnalysisCtrl.buildRuleList('recommendation', violationsMap, rec);
+                                });
+
+                                response.rules.forEach(function(rule) {
+                                    if (rule.enforcementPolicy === 'ENFORCE') {
+                                        var requirement = '<div 
class="rules-list-rule-info"></div>';
+                                        var requirementName = 
$('<div></div>').text(rule.name);
+                                        var requirementInfoButton = '<button 
class="rule-menu-btn"><i class="fa fa-ellipsis-v rules-list-item-menu-target" 
aria-hidden="true"></i></button>';
+                                        requirements.push(
+                                            {
+                                                'requirement': 
$(requirement).append(requirementName),
+                                                'requirementInfoButton': 
$(requirementInfoButton).data('ruleInfo', rule),
+                                                'id': rule.id
+                                            }
+                                        )
+                                        requirementsTotal++;
+                                    }
+                                });
+
+                                // add class to notification icon for required 
rules
+                                var hasViolations = 
response.ruleViolations.findIndex(function(violation) {
+                                    return violation.enforcementPolicy === 
'ENFORCE';
+                                })
+                                if (hasViolations !== -1) {
+                                    $('#flow-analysis 
.flow-analysis-notification-icon ').addClass('violations');
+                                } else {
+                                    $('#flow-analysis 
.flow-analysis-notification-icon ').removeClass('violations');
+                                }
+    
+                                requiredRuleCountEl.empty().append('(' + 
requirementsTotal + ')');
+                                
+                                // build violations
+                                requirements.forEach(function(rec) {
+                                    
flowAnalysisCtrl.buildRuleList('violation', violationsMap, rec);                
              
+                                });
+
+                                $('#required-rules').accordion('refresh');
+                                $('#recommended-rules').accordion('refresh');
+                                // report the updated status
+                                previousRulesResponse = response;
+    
+                                // setup rule menu handling
+                                flowAnalysisCtrl.setRuleMenuHandling();
+
+                                // setup violation menu handling
+                                
flowAnalysisCtrl.setViolationMenuHandling(response.rules);
+                            }
+                        }).fail(nfErrorHandler.handleAjaxError);
+                    }
+                },
+
+                /**
+                 * Set event bindings for rule menus
+                 */
+                setRuleMenuHandling: function() {
+                    $('.rule-menu-btn').click(function(event) {
+                        // stop event from immediately bubbling up to document 
and triggering closeRuleWindow
+                        event.stopPropagation();
+                        // unbind previously bound rule data that may still 
exist
+                        unbindRuleMenuHandling();
+
+                        var ruleInfo = $(this).data('ruleInfo');
+                        $('#violation-menu').hide();
+                        $('#rule-menu').show();
+                        $('#rule-menu').position({
+                            my: "left top",
+                            at: "left top",
+                            of: event
+                        });
+
+                        // rule menu bindings
+                        if (nfCommon.canAccessController()) {
+                            $('#rule-menu-edit-rule').removeClass('disabled');
+                            $('#rule-menu-edit-rule 
.rule-menu-option-icon').removeClass('disabled');
+                            $('#rule-menu-edit-rule').on('click', 
openRuleDetailsDialog);
+                        } else {
+                            $('#rule-menu-edit-rule').addClass('disabled');
+                            $('#rule-menu-edit-rule 
.rule-menu-option-icon').addClass('disabled');
+                        }
+                        $('#rule-menu-view-documentation').on('click', 
viewRuleDocumentation);
+                        $(document).on('click', closeRuleWindow);
+
+                        function viewRuleDocumentation(e) {
+                            nfShell.showPage('../nifi-docs/documentation?' + 
$.param({
+                                select: ruleInfo.type,
+                                group: ruleInfo.bundle.group,
+                                artifact: ruleInfo.bundle.artifact,
+                                version: ruleInfo.bundle.version
+                            })).done(function () {});
+                            $("#rule-menu").hide();
+                            unbindRuleMenuHandling();
+                        }
+
+                        function closeRuleWindow(e) {
+                            if ($(e.target).parents("#rule-menu").length === 
0) {
+                                $("#rule-menu").hide();
+                                unbindRuleMenuHandling();
+                            }
+                        }
+
+                        function openRuleDetailsDialog() {
+                            $('#rule-menu').hide();
+                            nfSettings.showSettings().done(function() {
+                                nfSettings.selectFlowAnalysisRule(ruleInfo.id);
+                            });
+                            unbindRuleMenuHandling();
+                        }
+
+                        function unbindRuleMenuHandling() {
+                            $('#rule-menu-edit-rule').off("click");
+                            $('#rule-menu-view-documentation').off("click");
+                            $(document).unbind('click', closeRuleWindow);
+                        }
+
+                    });
+                },
+
+                /**
+                 * Set event bindings for violation menus
+                 */
+                setViolationMenuHandling: function(rules) {
+                    $('.violation-menu-btn').click(function(event) {
+                        // stop event from immediately bubbling up to document 
and triggering closeViolationWindow
+                        event.stopPropagation();
+                        var violationInfo = $(this).data('violationInfo');
+                        $('#rule-menu').hide();
+                        $('#violation-menu').show();
+                        $('#violation-menu').position({
+                            my: "left top",
+                            at: "left top",
+                            of: event
+                        });
+
+                        // violation menu bindings
+                        if (violationInfo.subjectPermissionDto.canRead) {
+                            
$('#violation-menu-more-info').removeClass('disabled');
+                            $('#violation-menu-more-info 
.violation-menu-option-icon').removeClass('disabled');
+                            $('#violation-menu-more-info').on( "click", 
openRuleViolationMoreInfoDialog);
+                        } else {
+                            
$('#violation-menu-more-info').addClass('disabled');
+                            $('#violation-menu-more-info 
.violation-menu-option-icon').addClass('disabled');
+                        }
+                        
+                        if (violationInfo.subjectComponentType === 'PROCESSOR' 
&& violationInfo.subjectPermissionDto.canRead) {
+                            $('#violation-menu-go-to').removeClass('hidden');
+                            $('#violation-menu-go-to').on('click', 
goToComponent);
+                        } else {
+                            $('#violation-menu-go-to').addClass('hidden');
+                        }
+                        $(document).on('click', closeViolationWindow);
+
+                        function closeViolationWindow(e) {
+                            if ($(e.target).parents("#violation-menu").length 
=== 0) {
+                                $("#violation-menu").hide();
+                                unbindViolationMenuHandling();
+                            }
+                        }
+
+                        function openRuleViolationMoreInfoDialog() {
+                            var rule = rules.find(function(rule){ 
+                                return rule.id === violationInfo.ruleId;
+                            });
+                            $('#violation-menu').hide();
+                            $('#violation-type-pill').empty()
+                                                    .removeClass()
+                                                    
.addClass(violationInfo.enforcementPolicy.toLowerCase() + ' 
violation-type-pill')
+                                                    
.append(violationInfo.enforcementPolicy);
+                            
$('#violation-description').empty().append(violationInfo.violationMessage);
+                            $('#violation-menu-more-info-dialog').modal( 
"show" );
+                            $('.violation-docs-link').click(function () {
+                                // open the documentation for this flow 
analysis rule
+                                nfShell.showPage('../nifi-docs/documentation?' 
+ $.param({
+                                    select: rule.type,
+                                    group: rule.bundle.group,
+                                    artifact: rule.bundle.artifact,
+                                    version: rule.bundle.version
+                                })).done(function () {});
+                            });
+                            unbindViolationMenuHandling();
+                        }
+
+                        function goToComponent() {
+                            $('#violation-menu').hide();
+                            nfCanvasUtils.showComponent(violationInfo.groupId, 
violationInfo.subjectId);
+                            unbindViolationMenuHandling();
+                        }
+
+                        function unbindViolationMenuHandling() {
+                            $('#violation-menu-more-info').off("click");
+                            $('#violation-menu-go-to').off("click");
+                            $(document).unbind('click', closeViolationWindow);
+                        }
+                    });
+                },
+
+                /**
+                 * Initialize the flow analysis controller.
+                 */
+                init: function () {
+                    var flowAnalysisCtrl = this;
+                    var drawer = $('#flow-analysis-drawer');
+                    var requiredRulesEl = $('#required-rules');
+                    var recommendedRulesEl = $('#recommended-rules');
+                    var flowAnalysisRefreshIntervalSeconds = 
nfCommon.getAutoRefreshInterval();
+
+                    $('#flow-analysis').click(function () {
+                        $(this).toggleClass('opened');
+                        drawer.toggleClass('opened');
+                    });
+                    requiredRulesEl.accordion({
+                        collapsible: true,
+                        active: false,
+                        icons: {
+                            "header": "fa fa-chevron-down",
+                            "activeHeader": "fa fa-chevron-up"
+                        }
+                    });
+
+                    recommendedRulesEl.accordion({
+                        collapsible: true,
+                        active: false,
+                        icons: {
+                            "header": "fa fa-chevron-down",
+                            "activeHeader": "fa fa-chevron-up"
+                        }
+                    });
+                    $('#rule-menu').hide();
+                    $('#violation-menu').hide();
+                    $('#rule-menu-more-info-dialog').modal({
+                        scrollableContentStyle: 'scrollable',
+                        headerText: 'Rule Information',
+                        buttons: [{
+                            buttonText: 'OK',
+                                color: {
+                                    base: '#728E9B',
+                                    hover: '#004849',
+                                    text: '#ffffff'
+                                },
+                            handler: {
+                                click: function () {
+                                    $(this).modal('hide');
+                                }
+                            }
+                        }],
+                        handler: {
+                            close: function () {}
+                        }
+                    });
+                    $('#violation-menu-more-info-dialog').modal({
+                        scrollableContentStyle: 'scrollable',
+                        headerText: 'Violation Information',
+                        buttons: [{
+                            buttonText: 'OK',
+                                color: {
+                                    base: '#728E9B',
+                                    hover: '#004849',
+                                    text: '#ffffff'
+                                },
+                            handler: {
+                                click: function () {
+                                    $(this).modal('hide');
+                                }
+                            }
+                        }],
+                        handler: {
+                            close: function () {}
+                        }
+                    });
+
+                    this.loadFlowPolicies();
+                    setInterval(this.loadFlowPolicies.bind(this), 
flowAnalysisRefreshIntervalSeconds * 1000);
+                    
+                    this.toggleOnlyViolations(false);
+                    this.toggleOnlyWarnings(false);
+                    // handle show only violations checkbox
+                    $('#show-only-violations').on('change', function(event) {
+                        var isChecked = $(this).hasClass('checkbox-checked');
+                        flowAnalysisCtrl.toggleOnlyViolations(isChecked);
+                    });
+
+                    $('#show-only-warnings').on('change', function(event) {
+                        var isChecked = $(this).hasClass('checkbox-checked');
+                        flowAnalysisCtrl.toggleOnlyWarnings(isChecked);
+                    });
+                },
+
+                /**
+                 * Show/hide violations menu
+                 */
+                toggleOnlyViolations: function(isViolationsChecked) {
+                    var requiredRulesEl = $('#required-rules');
+                    var recommendedRulesEl = $('#recommended-rules');
+                    var ruleViolationsEl = $('#rule-violations');
+
+                    var isWarningsChecked = $('#show-only-warnings').hasClass(
+                      'checkbox-checked'
+                    );
+
+                    isViolationsChecked
+                      ? ruleViolationsEl.show()
+                      : ruleViolationsEl.hide();
+                    if (isViolationsChecked || isWarningsChecked) {
+                      requiredRulesEl.hide();
+                      recommendedRulesEl.hide();
+                    } else {
+                      requiredRulesEl.show();
+                      recommendedRulesEl.show();
+                    }
+                    this.loadFlowPolicies();
+                },
+
+                /**
+                 * Show/hide warnings menu
+                 */
+                toggleOnlyWarnings: function (isWarningsChecked) {
+                    var requiredRulesEl = $('#required-rules');
+                    var recommendedRulesEl = $('#recommended-rules');
+                    var ruleWarningsEl = $('#rule-warnings');
+                    var isViolationsChecked = 
$('#show-only-violations').hasClass(
+                      'checkbox-checked'
+                    );
+    
+                    isWarningsChecked
+                      ? ruleWarningsEl.show()
+                      : ruleWarningsEl.hide();
+                    if (isWarningsChecked || isViolationsChecked) {
+                      requiredRulesEl.hide();
+                      recommendedRulesEl.hide();
+                    } else {
+                      requiredRulesEl.show();
+                      recommendedRulesEl.show();
+                    }
+                    this.loadFlowPolicies();
+                  },
+            }
+
             /**
              * The bulletins controller.
              */
@@ -468,6 +1029,7 @@
              */
             init: function () {
                 this.search.init();
+                this.flowAnalysis.init();
             },
 
             /**
@@ -684,6 +1246,14 @@
              */
             updateBulletins: function (response) {
                 this.bulletins.update(response);
+            },
+
+            /**
+             * Reloads flow analysis rules
+             *
+             */
+            reloadFlowPolicies: function () {
+                this.flowAnalysis.loadFlowPolicies();
             }
         }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
index 458f6d84f0..f8f6b99298 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
@@ -1223,6 +1223,7 @@
          */
         reload: function () {
             nfCanvasUtils.reload();
+            nfNgBridge.injector.get('flowStatusCtrl').reloadFlowPolicies();
         },
 
         /**
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
index 8e5afb46a5..c120b6f5a5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
@@ -333,7 +333,7 @@
 
                     // get the auto refresh interval
                     var autoRefreshIntervalSeconds = 
parseInt(configDetails.autoRefreshIntervalSeconds, 10);
-
+                    
nfCommon.setAutoRefreshInterval(autoRefreshIntervalSeconds);
                     // record whether we can configure the authorizer
                     
nfCanvas.setManagedAuthorizer(configDetails.supportsManagedAuthorizer);
                     
nfCanvas.setConfigurableAuthorizer(configDetails.supportsConfigurableAuthorizer);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
index b59b3c070b..72874d4005 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js
@@ -464,11 +464,11 @@
                         value: 'ENFORCE',
                         description: 'Treat violations of this rule as errors 
the correction of which is mandatory.'
                     }
-//                    , {
-//                        text: 'Warn',
-//                        value: 'WARN',
-//                        description: 'Treat violations of by this rule as 
warnings the correction of which is recommended but not mandatory.'
-//                    }
+                   , {
+                       text: 'Warn',
+                       value: 'WARN',
+                       description: 'Treat violations of by this rule as 
warnings the correction of which is recommended but not mandatory.'
+                   }
                     ],
                     selectedOption: {
                         value: flowAnalysisRule['enforcementPolicy']
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 3c6d0b1115..a3e37d2855 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -173,6 +173,7 @@
 
     var nfCommon = {
         ANONYMOUS_USER_TEXT: 'Anonymous user',
+        autoRefreshInterval: null,
 
         config: {
             sensitiveText: 'Sensitive value set',
@@ -1895,6 +1896,24 @@
             });
 
             return 
sortedAuthorizedParameterContexts.concat(sortedUnauthorizedParameterContexts);
+        },
+
+        /**
+         * Sets the global auto refresh value
+         *
+         * @param {integer} interval in seconds       The numeric value for 
the auto refresh interval
+         */
+        setAutoRefreshInterval: function (interval) {
+            nfCommon.config.autoRefreshInterval = interval;
+        },
+
+        /**
+         * Gets the global auto refresh value
+         *
+         * @returns {integer}
+         */
+        getAutoRefreshInterval: function () {
+            return nfCommon.config.autoRefreshInterval;
         }
 
     };

Reply via email to