mcgilman commented on code in PR #8273:
URL: https://github.com/apache/nifi/pull/8273#discussion_r1507857234


##########
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:
##########
@@ -400,6 +406,556 @@
                 }
             }
 
+            /**
+             * 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();
+                    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);
+                        
$(violationListItemNameEl).text(violation.subjectDisplayName);
+                        $(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);
+                        
$(warningListItemNameEl).text(warning.subjectDisplayName);
+                        $(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
+                            violationNameEl.text(violation.subjectDisplayName);
+                            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 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);
+
+                                // 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
+                        $('#rule-menu-edit-rule').on('click', 
openRuleDetailsDialog);
+                        $('#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
+                        $('#violation-menu-more-info').on( "click", 
openRuleViolationMoreInfoDialog);
+                        // If the groupId and subjectId are not the same, we 
can select the component
+                        console.log(violationInfo.groupId !== 
violationInfo.subjectId);
+                        console.log(violationInfo);
+                        if (violationInfo.groupId !== violationInfo.subjectId) 
{
+                            $('#violation-menu-go-to').removeClass('disabled');
+                            $('#violation-menu-go-to 
.violation-menu-option-icon').removeClass('disabled');
+                            $('#violation-menu-go-to').on('click', 
goToComponent);
+                        } else {
+                            $('#violation-menu-go-to').addClass('disabled');
+                            $('#violation-menu-go-to 
.violation-menu-option-icon').addClass('disabled');
+                        }
+                        $(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 newFlowAnalsysisBtnEl = 
$('#flow-analysis-check-now-btn');
+                    var flowAnalysisRefreshIntervalSeconds = 
nfCommon.getAutoRefreshInterval();
+
+                    $('#flow-analysis').click(function () {
+                        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);
+
+                    // add click event listener to refresh button
+                    newFlowAnalsysisBtnEl.on('click', 
this.createNewFlowAnalysisRequest);
+                    
+                    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) {

Review Comment:
   Choosing `show-only-warnings` is showing some confusing results.
   
   ![Kapture 2024-02-29 at 11 23 
45](https://github.com/apache/nifi/assets/123395/c67d6bc9-1041-44b0-9988-000c988fda56)
   



##########
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:
##########
@@ -400,6 +406,556 @@
                 }
             }
 
+            /**
+             * 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();
+                    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);
+                        
$(violationListItemNameEl).text(violation.subjectDisplayName);
+                        $(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);
+                        
$(warningListItemNameEl).text(warning.subjectDisplayName);
+                        $(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
+                            violationNameEl.text(violation.subjectDisplayName);
+                            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 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);
+
+                                // 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
+                        $('#rule-menu-edit-rule').on('click', 
openRuleDetailsDialog);
+                        $('#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
+                        $('#violation-menu-more-info').on( "click", 
openRuleViolationMoreInfoDialog);
+                        // If the groupId and subjectId are not the same, we 
can select the component
+                        console.log(violationInfo.groupId !== 
violationInfo.subjectId);
+                        console.log(violationInfo);
+                        if (violationInfo.groupId !== violationInfo.subjectId) 
{
+                            $('#violation-menu-go-to').removeClass('disabled');
+                            $('#violation-menu-go-to 
.violation-menu-option-icon').removeClass('disabled');
+                            $('#violation-menu-go-to').on('click', 
goToComponent);
+                        } else {
+                            $('#violation-menu-go-to').addClass('disabled');
+                            $('#violation-menu-go-to 
.violation-menu-option-icon').addClass('disabled');
+                        }
+                        $(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();
+                        }

Review Comment:
   This doesn't appear to work for non Processor components.



##########
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:
##########
@@ -400,6 +406,556 @@
                 }
             }
 
+            /**
+             * 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();
+                    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);
+                        
$(violationListItemNameEl).text(violation.subjectDisplayName);

Review Comment:
   When I was verifying things I noticed that the flow analysis results may 
leak component details. If I don't have permission for a component that 
violates a rule, the component name is still rendered. Then the user can simply 
navigate to the component in question. In past features, referencing components 
where the user lacked permissions were rendered as `unauthorized`. 



##########
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,85 @@
+<%--
+ 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 class="flow-analysis-refresh-container">
+            <button id="flow-analysis-check-now-btn" 
class="flow-analysis-check-now-btn">Start a new analysis</button>

Review Comment:
   When the user clicks this button there is no feedback. With no feedback, I 
was questioning whether it worked or not. As I repeatedly click on the link, 
new flow analysis reports seem to be generated. At a minimum, we should 
probably limit a client from having more than one outstanding request.
   
   Alternatively, should be feature be simplified and the UI only show whatever 
the current results are? If so, we could just remove this link.



##########
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>

Review Comment:
   Is there a reason the background color of this button is different? It 
appears to match the hover state of the search button.
   
   ![Screenshot 2024-02-29 at 11 16 04 
AM](https://github.com/apache/nifi/assets/123395/8fae8054-5a89-4e55-94c2-d7e53cba3050)
   



##########
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:
##########
@@ -400,6 +406,556 @@
                 }
             }
 
+            /**
+             * 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();
+                    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);
+                        
$(violationListItemNameEl).text(violation.subjectDisplayName);
+                        $(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);
+                        
$(warningListItemNameEl).text(warning.subjectDisplayName);
+                        $(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
+                            violationNameEl.text(violation.subjectDisplayName);
+                            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 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);
+
+                                // 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
+                        $('#rule-menu-edit-rule').on('click', 
openRuleDetailsDialog);
+                        $('#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();
+                        }

Review Comment:
   Users that lack permission to the controller won't be able to view the 
rules. Currently, the application allows all users to attempt this. If the user 
lacks permission they will be shown a dialog with a corresponding error 
message. But I think could we should be able to check this ahead of time and 
hide this menu item accordingly.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to