You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by "mcgilman (via GitHub)" <gi...@apache.org> on 2023/06/08 21:12:41 UTC

[GitHub] [nifi] mcgilman commented on a diff in pull request #7191: NIFI-8650 Flow Analysis

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


##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js:
##########
@@ -1925,6 +1931,885 @@
         });
     };
 
+    /**
+     * Get the text out of the filter field. If the filter field doesn't
+     * have any text it will contain the text 'filter list' so this method
+     * accounts for that.
+     */
+    var getFlowAnalysisRuleTypeFilterText = function () {
+        return $('#flow-analysis-rule-type-filter').val();
+    };
+
+    /**
+     * Filters the flow analysis rule type table.
+     */
+    var applyFlowAnalysisRuleTypeFilter = function () {
+        // get the dataview
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+
+        // ensure the grid has been initialized
+        if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleTypesGrid)) {
+            var flowAnalysisRuleTypesData = flowAnalysisRuleTypesGrid.getData();
+
+            // update the search criteria
+            flowAnalysisRuleTypesData.setFilterArgs({
+                searchString: getFlowAnalysisRuleTypeFilterText()
+            });
+            flowAnalysisRuleTypesData.refresh();
+
+            // update the buttons to possibly trigger the disabled state
+            $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+
+            // update the selection if possible
+            if (flowAnalysisRuleTypesData.getLength() > 0) {
+                nfFilteredDialogCommon.choseFirstRow(flowAnalysisRuleTypesGrid);
+            }
+        }
+    };
+
+    /**
+     * Hides the selected flow analysis rule.
+     */
+    var clearSelectedFlowAnalysisRule = function () {
+        $('#flow-analysis-rule-type-description').attr('title', '').text('');
+        $('#flow-analysis-rule-type-name').attr('title', '').text('');
+        $('#flow-analysis-rule-type-bundle').attr('title', '').text('');
+        $('#selected-flow-analysis-rule-name').text('');
+        $('#selected-flow-analysis-rule-type').text('').removeData('bundle');
+        $('#flow-analysis-rule-description-container').hide();
+    };
+
+    /**
+     * Clears the selected flow analysis rule type.
+     */
+    var clearFlowAnalysisRuleSelection = function () {
+        // clear the selected row
+        clearSelectedFlowAnalysisRule();
+
+        // clear the active cell the it can be reselected when its included
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+        flowAnalysisRuleTypesGrid.resetActiveCell();
+    };
+
+    /**
+     * Performs the filtering.
+     *
+     * @param {object} item     The item subject to filtering
+     * @param {object} args     Filter arguments
+     * @returns {Boolean}       Whether or not to include the item
+     */
+    var filterFlowAnalysisRuleTypes = function (item, args) {
+        // determine if the item matches the filter
+        var matchesFilter = matchesRegex(item, args);
+
+        // determine if the row matches the selected tags
+        var matchesTags = true;
+        if (matchesFilter) {
+            var tagFilters = $('#flow-analysis-rule-tag-cloud').tagcloud('getSelectedTags');
+            var hasSelectedTags = tagFilters.length > 0;
+            if (hasSelectedTags) {
+                matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+            }
+        }
+
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#flow-analysis-rule-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
+        // determine if this row should be visible
+        var matches = matchesFilter && matchesTags && matchesGroup;
+
+        // if this row is currently selected and its being filtered
+        if (matches === false && $('#selected-flow-analysis-rule-type').text() === item['type']) {
+            clearFlowAnalysisRuleSelection();
+        }
+
+        return matches;
+    };
+
+    /**
+     * Adds the currently selected flow analysis rule.
+     */
+    var addSelectedFlowAnalysisRule = function () {
+        var selectedTaskType = $('#selected-flow-analysis-rule-type').text();
+        var selectedTaskBundle = $('#selected-flow-analysis-rule-type').data('bundle');
+
+        // ensure something was selected
+        if (selectedTaskType === '') {
+            nfDialog.showOkDialog({
+                headerText: 'Settings',
+                dialogContent: 'The type of flow analysis rule to create must be selected.'
+            });
+        } else {
+            addFlowAnalysisRule(selectedTaskType, selectedTaskBundle);
+        }
+    };
+
+    /**
+     * Adds a new flow analysis rule of the specified type.
+     *
+     * @param {string} flowAnalysisRuleType
+     * @param {object} flowAnalysisRuleBundle
+     */
+    var addFlowAnalysisRule = function (flowAnalysisRuleType, flowAnalysisRuleBundle) {
+        // build the flow analysis rule entity
+        var flowAnalysisRuleEntity = {
+            'revision': nfClient.getRevision({
+                'revision': {
+                    'version': 0
+                }
+            }),
+            'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+            'component': {
+                'type': flowAnalysisRuleType,
+                'bundle': flowAnalysisRuleBundle
+            }
+        };
+
+        // add the new flow analysis rule
+        var addRule = $.ajax({
+            type: 'POST',
+            url: config.urls.createFlowAnalysisRule,
+            data: JSON.stringify(flowAnalysisRuleEntity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (flowAnalysisRuleEntity) {
+            // add the item
+            var flowAnalysisRuleGrid = $('#flow-analysis-rules-table').data('gridInstance');
+            var flowAnalysisRuleData = flowAnalysisRuleGrid.getData();
+            flowAnalysisRuleData.addItem($.extend({
+                type: 'FlowAnalysisRule',
+                bulletins: []
+            }, flowAnalysisRuleEntity));
+
+            // resort
+            flowAnalysisRuleData.reSort();
+            flowAnalysisRuleGrid.invalidate();
+
+            // select the new flow analysis rule
+            var row = flowAnalysisRuleData.getRowById(flowAnalysisRuleEntity.id);
+            nfFilteredDialogCommon.choseRow(flowAnalysisRuleGrid, row);
+            flowAnalysisRuleGrid.scrollRowIntoView(row);
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        // hide the dialog
+        $('#new-flow-analysis-rule-dialog').modal('hide');
+
+        return addRule;
+    };
+
+    /**
+     * Initializes the new flow analysis rule dialog.
+     */
+    var initNewFlowAnalysisRuleDialog = function () {
+        // initialize the flow analysis rule type table
+        var flowAnalysisRuleTypesColumns = [
+            {
+                id: 'type',
+                name: 'Type',
+                field: 'label',
+                formatter: nfCommon.typeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'tags',
+                name: 'Tags',
+                field: 'tags',
+                sortable: true,
+                resizable: true,
+                formatter: nfCommon.genericValueFormatter
+            }
+        ];
+
+        // initialize the dataview
+        var flowAnalysisRuleTypesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        flowAnalysisRuleTypesData.setItems([]);
+        flowAnalysisRuleTypesData.setFilterArgs({
+            searchString: getFlowAnalysisRuleTypeFilterText()
+        });
+        flowAnalysisRuleTypesData.setFilter(filterFlowAnalysisRuleTypes);
+
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, flowAnalysisRuleTypesData);
+
+        // initialize the grid
+        var flowAnalysisRuleTypesGrid = new Slick.Grid('#flow-analysis-rule-types-table', flowAnalysisRuleTypesData, flowAnalysisRuleTypesColumns, gridOptions);
+        flowAnalysisRuleTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+        flowAnalysisRuleTypesGrid.registerPlugin(new Slick.AutoTooltips());
+        flowAnalysisRuleTypesGrid.setSortColumn('type', true);
+        flowAnalysisRuleTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, flowAnalysisRuleTypesData);
+        });
+        flowAnalysisRuleTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+            if ($.isArray(args.rows) && args.rows.length === 1) {
+                var flowAnalysisRuleTypeIndex = args.rows[0];
+                var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(flowAnalysisRuleTypeIndex);
+
+                // set the flow analysis rule type description
+                if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleType)) {
+                    // show the selected flow analysis rule
+                    $('#flow-analysis-rule-description-container').show();
+
+                    if (nfCommon.isBlank(flowAnalysisRuleType.description)) {
+                        $('#flow-analysis-rule-type-description')
+                            .attr('title', '')
+                            .html('<span class="unset">No description specified</span>');
+                    } else {
+                        $('#flow-analysis-rule-type-description')
+                            .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                            .html(flowAnalysisRuleType.description)
+                            .ellipsis();
+                    }
+
+                    var bundle = nfCommon.formatBundle(flowAnalysisRuleType.bundle);
+                    var type = nfCommon.formatType(flowAnalysisRuleType);
+
+                    // populate the dom
+                    $('#flow-analysis-rule-type-name').text(type).attr('title', type);
+                    $('#flow-analysis-rule-type-bundle').text(bundle).attr('title', bundle);
+                    $('#selected-flow-analysis-rule-name').text(flowAnalysisRuleType.label);
+                    $('#selected-flow-analysis-rule-type').text(flowAnalysisRuleType.type).data('bundle', flowAnalysisRuleType.bundle);
+
+                    // refresh the buttons based on the current selection
+                    $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+                }
+            }
+        });
+        flowAnalysisRuleTypesGrid.onDblClick.subscribe(function (e, args) {
+            var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(args.row);
+
+            if (isSelectable(flowAnalysisRuleType)) {
+                addFlowAnalysisRule(flowAnalysisRuleType.type, flowAnalysisRuleType.bundle);
+            }
+        });
+        flowAnalysisRuleTypesGrid.onViewportChanged.subscribe(function (e, args) {
+            nfCommon.cleanUpTooltips($('#flow-analysis-rule-types-table'), 'div.view-usage-restriction');
+        });
+
+        // wire up the dataview to the grid
+        flowAnalysisRuleTypesData.onRowCountChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.updateRowCount();
+            flowAnalysisRuleTypesGrid.render();
+
+            // update the total number of displayed processors
+            $('#displayed-flow-analysis-rule-types').text(args.current);
+        });
+        flowAnalysisRuleTypesData.onRowsChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.invalidateRows(args.rows);
+            flowAnalysisRuleTypesGrid.render();
+        });
+        flowAnalysisRuleTypesData.syncGridSelection(flowAnalysisRuleTypesGrid, true);
+
+        // hold onto an instance of the grid
+        $('#flow-analysis-rule-types-table').data('gridInstance', flowAnalysisRuleTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var usageRestriction = $(this).find('div.view-usage-restriction');
+            if (usageRestriction.length && !usageRestriction.data('qtip')) {
+                var rowId = $(this).find('span.row-id').text();
+
+                // get the status item
+                var item = flowAnalysisRuleTypesData.getItemById(rowId);
+
+                // show the tooltip
+                if (item.restricted === true) {
+                    var restrictionTip = $('<div></div>');
+
+                    if (nfCommon.isBlank(item.usageRestriction)) {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+                    } else {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+                    }
+
+                    var restrictions = [];
+                    if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+                        $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+                            restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+                        });
+                    } else {
+                        restrictions.push('Access to restricted components regardless of restrictions.');
+                    }
+                    restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+                    usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+                        content: restrictionTip,
+                        position: {
+                            container: $('#summary'),
+                            at: 'bottom right',
+                            my: 'top left',
+                            adjust: {
+                                x: 4,
+                                y: 4
+                            }
+                        }
+                    }));
+                }
+            }
+        });
+
+        var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+        // load the available flow analysis rules
+        $.ajax({
+            type: 'GET',
+            url: config.urls.flowAnalysisRuleTypes,
+            dataType: 'json'
+        }).done(function (response) {
+            var id = 0;
+            var tags = [];
+            var groups = d3.set();
+            var restrictedUsage = d3.map();
+            var requiredPermissions = d3.map();
+
+            // begin the update
+            flowAnalysisRuleTypesData.beginUpdate();
+
+            // go through each flow analysis rule type
+            $.each(response.flowAnalysisRuleTypes, function (i, documentedType) {
+                if (documentedType.restricted === true) {
+                    if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+                        $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+
+                            // update required permissions
+                            if (!requiredPermissions.has(requiredPermission.id)) {
+                                requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+                            }
+
+                            // update component restrictions
+                            if (!restrictedUsage.has(requiredPermission.id)) {
+                                restrictedUsage.set(requiredPermission.id, []);
+                            }
+
+                            restrictedUsage.get(requiredPermission.id).push({
+                                type: nfCommon.formatType(documentedType),
+                                bundle: nfCommon.formatBundle(documentedType.bundle),
+                                explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+                            })
+                        });
+                    } else {
+                        // update required permissions
+                        if (!requiredPermissions.has(generalRestriction.value)) {
+                            requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+                        }
+
+                        // update component restrictions
+                        if (!restrictedUsage.has(generalRestriction.value)) {
+                            restrictedUsage.set(generalRestriction.value, []);
+                        }
+
+                        restrictedUsage.get(generalRestriction.value).push({
+                            type: nfCommon.formatType(documentedType),
+                            bundle: nfCommon.formatBundle(documentedType.bundle),
+                            explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+                        });
+                    }
+                }
+
+                // record the group
+                groups.add(documentedType.bundle.group);
+
+                // add the documented type
+                flowAnalysisRuleTypesData.addItem({
+                    id: id++,
+                    label: nfCommon.substringAfterLast(documentedType.type, '.'),
+                    type: documentedType.type,
+                    bundle: documentedType.bundle,
+                    description: nfCommon.escapeHtml(documentedType.description),
+                    restricted:  documentedType.restricted,
+                    usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+                    explicitRestrictions: documentedType.explicitRestrictions,
+                    tags: documentedType.tags.join(', ')
+                });
+
+                // count the frequency of each tag for this type
+                $.each(documentedType.tags, function (i, tag) {
+                    tags.push(tag.toLowerCase());
+                });
+            });
+
+            // end the update
+            flowAnalysisRuleTypesData.endUpdate();
+
+            // resort
+            flowAnalysisRuleTypesData.reSort();
+            flowAnalysisRuleTypesGrid.invalidate();
+
+            // set the component restrictions and the corresponding required permissions
+            nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+            // set the total number of processors
+            $('#total-flow-analysis-rule-types, #displayed-flow-analysis-rule-types').text(response.flowAnalysisRuleTypes.length);
+
+            // create the tag cloud
+            $('#flow-analysis-rule-tag-cloud').tagcloud({
+                tags: tags,
+                select: applyFlowAnalysisRuleTypeFilter,
+                remove: applyFlowAnalysisRuleTypeFilter
+            });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.each(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#flow-analysis-rule-bundle-group-combo').combo({
+                options: options,
+                select: applyFlowAnalysisRuleTypeFilter
+            });
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+        // define the function for filtering the list
+        $('#flow-analysis-rule-type-filter').off('keyup').on('keyup', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+
+            // ignore navigation keys
+            if ($.inArray(code, navigationKeys) !== -1) {
+                return;
+            }
+
+            if (code === $.ui.keyCode.ENTER) {
+                var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                if (selected.length > 0) {
+                    // grid configured with multi-select = false
+                    var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                    if (isSelectable(item)) {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            } else {
+                applyFlowAnalysisRuleTypeFilter();
+            }
+        });
+
+        // setup row navigation
+        nfFilteredDialogCommon.addKeydownListener('#flow-analysis-rule-type-filter', flowAnalysisRuleTypesGrid, flowAnalysisRuleTypesGrid.getData());
+
+        // initialize the flow analysis rule dialog
+        $('#new-flow-analysis-rule-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Add Flow Analysis Rule',
+            buttons: [{
+                buttonText: 'Add',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                    if (selected.length > 0) {
+                        // grid configured with multi-select = false
+                        var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                        return isSelectable(item) === false;
+                    } else {
+                        return flowAnalysisRuleTypesGrid.getData().getLength() === 0;
+                    }
+                },
+                handler: {
+                    click: function () {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            },
+                {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+            handler: {
+                close: function () {
+                    // clear the selected row
+                    clearSelectedFlowAnalysisRule();
+
+                    // clear any filter strings
+                    $('#flow-analysis-rule-type-filter').val('');
+
+                    // clear the tagcloud
+                    $('#flow-analysis-rule-tag-cloud').tagcloud('clearSelectedTags');
+
+                    // reset the group combo
+                    $('#flow-analysis-rule-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
+                    // reset the filter
+                    applyFlowAnalysisRuleTypeFilter();
+
+                    // unselect any current selection
+                    var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+                    flowAnalysisRuleTypesGrid.setSelectedRows([]);
+                    flowAnalysisRuleTypesGrid.resetActiveCell();
+                },
+                resize: function () {
+                    $('#flow-analysis-rule-type-description')
+                        .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                        .text($('#flow-analysis-rule-type-description').attr('title'))
+                        .ellipsis();
+                }
+            }
+        });
+
+        // initialize the registry configuration dialog
+        $('#registry-configuration-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            handler: {
+                close: function () {
+                    $('#registry-id').text('');
+                    $('#registry-name').val('');
+                    $('#registry-location').val('');
+                    $('#registry-description').val('');
+                }
+            }
+        });
+    };
+
+    /**
+     * Initializes the flow analysis rules tab.
+     */
+    var initFlowAnalysisRules = function () {
+        // initialize the new flow analysis rule dialog
+        initNewFlowAnalysisRuleDialog();
+
+        var moreFlowAnalysisRuleDetails = function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            var markup = '<div title="View Details" class="pointer view-flow-analysis-rule fa fa-info-circle"></div>';
+
+            // always include a button to view the usage
+            markup += '<div title="Usage" class="pointer flow-analysis-rule-usage fa fa-book"></div>';
+
+            var hasErrors = !nfCommon.isEmpty(dataContext.component.validationErrors);
+            var hasBulletins = !nfCommon.isEmpty(dataContext.bulletins);
+
+            if (hasErrors) {
+                markup += '<div class="pointer has-errors fa fa-warning" ></div>';
+            }
+
+            if (hasBulletins) {
+                markup += '<div class="has-bulletins fa fa-sticky-note-o"></div>';
+            }
+
+            if (hasErrors || hasBulletins) {
+                markup += '<span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.component.id) + '</span>';
+            }
+
+            return markup;
+        };
+
+        var flowAnalysisRuleRunStatusFormatter = function (row, cell, value, columnDef, dataContext) {
+            var icon = '', label = '';
+            if (dataContext.status.validationStatus === 'VALIDATING') {
+                icon = 'validating fa fa-spin fa-circle-notch';
+                label = 'Validating';
+            } else if (dataContext.status.validationStatus === 'INVALID') {
+                icon = 'invalid fa fa-warning';
+                label = 'Invalid';
+            } else {
+                if (dataContext.status.runStatus === 'DISABLED') {
+                    icon = 'disabled icon icon-enable-false"';
+                    label = 'Disabled';
+                } else if (dataContext.status.runStatus === 'ENABLED') {
+                    icon = 'enabled fa fa-flash';
+                    label = 'Enabled';
+                }
+            }
+
+            // format the markup
+            var formattedValue = '<div layout="row"><div class="' + icon + '"></div>';
+            return formattedValue + '<div class="status-text">' + label + '</div></div>';
+        };
+
+        var flowAnalysisRuleActionFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            var canWrite = dataContext.permissions.canWrite;
+            var canRead = dataContext.permissions.canRead;
+            var canOperate = canWrite || (dataContext.operatePermissions && dataContext.operatePermissions.canWrite);
+
+            var isDisabled = dataContext.status.runStatus === 'DISABLED';
+
+            if (canRead) {
+                if (canWrite && isDisabled) {
+                    markup += '<div class="pointer edit-flow-analysis-rule fa fa-gear" title="Configure"></div>';
+                } else {
+                    markup += '<div class="pointer view-flow-analysis-rule fa fa-gear" title="View Configuration"></div>';
+                }
+            }
+
+            if (canOperate) {
+                if (dataContext.status.runStatus === 'ENABLED') {
+                    markup += '<div class="pointer disable-flow-analysis-rule icon icon-enable-false" title="Disable"></div>';
+                } else if (isDisabled && dataContext.status.validationStatus === 'VALID') {
+                    markup += '<div class="pointer enable-flow-analysis-rule fa fa-flash" title="Enable"></div>';
+                }
+            }
+
+            if (isDisabled && canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) {
+                markup += '<div title="Change Version" class="pointer change-version-flow-analysis-rule fa fa-exchange"></div>';

Review Comment:
   Are Rules an extension point that would need to support Changing Versions?



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-analysis-rule.js:
##########
@@ -0,0 +1,788 @@
+/*
+ * 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.
+ */
+
+/* global define, module, require, exports */
+
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        define(['jquery',
+                'nf.ErrorHandler',
+                'nf.Common',
+                'nf.Dialog',
+                'nf.Storage',
+                'nf.Client',
+                'nf.ControllerService',
+                'nf.ControllerServices',
+                'nf.UniversalCapture',
+                'nf.CustomUi',
+                'nf.Verify'],
+            function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify) {
+                return (nf.FlowAnalysisRule = factory($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify));
+            });
+    } else if (typeof exports === 'object' && typeof module === 'object') {
+        module.exports = (nf.FlowAnalysisRule =
+            factory(require('jquery'),
+                require('nf.ErrorHandler'),
+                require('nf.Common'),
+                require('nf.Dialog'),
+                require('nf.Storage'),
+                require('nf.Client'),
+                require('nf.ControllerService'),
+                require('nf.ControllerServices'),
+                require('nf.UniversalCapture'),
+                require('nf.CustomUi'),
+                require('nf.Verify')));
+    } else {
+        nf.FlowAnalysisRule = factory(root.$,
+            root.nf.ErrorHandler,
+            root.nf.Common,
+            root.nf.Dialog,
+            root.nf.Storage,
+            root.nf.Client,
+            root.nf.ControllerService,
+            root.nf.ControllerServices,
+            root.nf.UniversalCapture,
+            root.nf.CustomUi,
+            root.nf.Verify);
+    }
+}(this, function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify) {
+    'use strict';
+
+    var nfSettings;
+
+    var config = {
+        edit: 'edit',
+        readOnly: 'read-only',
+        urls: {
+            api: '../nifi-api'
+        }
+    };
+
+    // the last submitted referenced attributes
+    var referencedAttributes = null;
+
+    // load the controller services
+    var controllerServicesUri = config.urls.api + '/flow/controller/controller-services';
+
+    /**
+     * Gets the controller services table.
+     *
+     * @returns {*|jQuery|HTMLElement}
+     */
+    var getControllerServicesTable = function () {
+        return $('#controller-services-table');
+    };
+
+    /**
+     * Determines whether the user has made any changes to the flow analysis rule configuration
+     * that needs to be saved.
+     */
+    var isSaveRequired = function () {
+        var entity = $('#flow-analysis-rule-configuration').data('flowAnalysisRuleDetails');
+
+        // determine if any flow analysis rule settings have changed
+
+        if ($('#flow-analysis-rule-name').val() !== entity.component['name']) {
+            return true;
+        }
+        if ($('#flow-analysis-rule-comments').val() !== entity.component['comments']) {
+            return true;
+        }
+
+        var enforcementPolicy = $('#flow-analysis-rule-enforcement-policy-combo').combo('getSelectedOption').value;
+        if (enforcementPolicy !== (entity.component['enforcementPolicy'] + '')) {
+            return true;
+        }
+
+        // defer to the properties
+        return $('#flow-analysis-rule-properties').propertytable('isSaveRequired');
+    };
+
+    /**
+     * Marshals the data that will be used to update the flow analysis rule's configuration.
+     */
+    var marshalDetails = function () {
+        // properties
+        var properties = $('#flow-analysis-rule-properties').propertytable('marshalProperties');
+
+        var enforcementPolicy = $('#flow-analysis-rule-enforcement-policy-combo').combo('getSelectedOption').value;
+
+        // create the flow analysis rule dto
+        var flowAnalysisRuleDto = {};
+        flowAnalysisRuleDto['id'] = $('#flow-analysis-rule-id').text();
+        flowAnalysisRuleDto['name'] = $('#flow-analysis-rule-name').val();
+        flowAnalysisRuleDto['comments'] = $('#flow-analysis-rule-comments').val();
+        flowAnalysisRuleDto['enforcementPolicy'] = enforcementPolicy;
+
+        // set the properties
+        if ($.isEmptyObject(properties) === false) {
+            flowAnalysisRuleDto['properties'] = properties;
+        }
+        flowAnalysisRuleDto['sensitiveDynamicPropertyNames'] = $('#flow-analysis-rule-properties').propertytable('getSensitiveDynamicPropertyNames');
+
+        // create the flow analysis rule entity
+        var flowAnalysisRuleEntity = {};
+        flowAnalysisRuleEntity['component'] = flowAnalysisRuleDto;
+
+        // return the marshaled details
+        return flowAnalysisRuleEntity;
+    };
+
+    /**
+     * Validates the specified details.
+     *
+     * @argument {object} details       The details to validate
+     */
+    var validateDetails = function (details) {
+        var errors = [];
+        var flowAnalysisRule = details['component'];
+
+        if (errors.length > 0) {
+            nfDialog.showOkDialog({
+                dialogContent: nfCommon.formatUnorderedList(errors),
+                headerText: 'Flow Analysis Rule'
+            });
+            return false;
+        } else {
+            return true;
+        }
+    };
+
+    /**
+     * Renders the specified flow analysis rule.
+     *
+     * @param {object} flowAnalysisRule
+     */
+    var renderFlowAnalysisRule = function (flowAnalysisRuleEntity) {
+        // get the table and update the row accordingly
+        var flowAnalysisRuleGrid = $('#flow-analysis-rules-table').data('gridInstance');
+        var flowAnalysisRuleData = flowAnalysisRuleGrid.getData();
+        var currentFlowAnalysisRule = flowAnalysisRuleData.getItemById(flowAnalysisRuleEntity.id);
+        flowAnalysisRuleData.updateItem(flowAnalysisRuleEntity.id, $.extend({
+            type: 'flowAnalysisRule',
+            bulletins: currentFlowAnalysisRule.bulletins
+        }, flowAnalysisRuleEntity));
+    };
+
+    /**
+     *
+     * @param {object} flowAnalysisRuleEntity
+     * @param {boolean} enabled
+     */
+    var setEnabled = function (flowAnalysisRuleEntity, enabled) {
+        var entity = {
+            'revision': nfClient.getRevision(flowAnalysisRuleEntity),
+            'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+            'state': enabled === true ? 'ENABLED' : 'DISABLED'
+        };
+
+        return $.ajax({
+            type: 'PUT',
+            url: flowAnalysisRuleEntity.uri + '/run-status',
+            data: JSON.stringify(entity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (response) {
+            // update the task
+            renderFlowAnalysisRule(response);
+            // component can be null if the user only has 'operate' permission without 'read'.
+            if (nfCommon.isDefinedAndNotNull(response.component)) {
+                nfControllerService.reloadReferencedServices(getControllerServicesTable(), response.component);
+            }
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Goes to a service configuration from the property table.
+     */
+    var goToServiceFromProperty = function () {
+        return $.Deferred(function (deferred) {
+            // close all fields currently being edited
+            $('#flow-analysis-rule-properties').propertytable('saveRow');
+
+            // determine if changes have been made
+            if (isSaveRequired()) {
+                // see if those changes should be saved
+                nfDialog.showYesNoDialog({
+                    headerText: 'Save',
+                    dialogContent: 'Save changes before going to this Controller Service?',
+                    noHandler: function () {
+                        deferred.resolve();
+                    },
+                    yesHandler: function () {
+                        var flowAnalysisRule = $('#flow-analysis-rule-configuration').data('flowAnalysisRuleDetails');
+                        saveFlowAnalysisRule(flowAnalysisRule).done(function () {
+                            deferred.resolve();
+                        }).fail(function () {
+                            deferred.reject();
+                        });
+                    }
+                });
+            } else {
+                deferred.resolve();
+            }
+        }).promise();
+    };
+
+    /**
+     * Saves the specified flow analysis rule.
+     *
+     * @param {type} flowAnalysisRule
+     */
+    var saveFlowAnalysisRule = function (flowAnalysisRuleEntity) {
+        // marshal the settings and properties and update the flow analysis rule
+        var updatedFlowAnalysisRule = marshalDetails();
+
+        // ensure details are valid as far as we can tell
+        if (validateDetails(updatedFlowAnalysisRule)) {
+            updatedFlowAnalysisRule['revision'] = nfClient.getRevision(flowAnalysisRuleEntity);
+            updatedFlowAnalysisRule['disconnectedNodeAcknowledged'] = nfStorage.isDisconnectionAcknowledged();
+
+            // update the selected component
+            return $.ajax({
+                type: 'PUT',
+                data: JSON.stringify(updatedFlowAnalysisRule),
+                url: flowAnalysisRuleEntity.uri,
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                // update the flow analysis rule
+                renderFlowAnalysisRule(response);
+            }).fail(nfErrorHandler.handleConfigurationUpdateAjaxError);
+        } else {
+            return $.Deferred(function (deferred) {
+                deferred.reject();
+            }).promise();
+        }
+    };
+
+    /**
+     * Gets a property descriptor for the flow analysis rule currently being configured.
+     *
+     * @param {type} propertyName
+     * @param {type} sensitive Requested sensitive status
+     */
+    var getFlowAnalysisRulePropertyDescriptor = function (propertyName, sensitive) {
+        var details = $('#flow-analysis-rule-configuration').data('flowAnalysisRuleDetails');
+        return $.ajax({
+            type: 'GET',
+            url: details.uri + '/descriptors',
+            data: {
+                propertyName: propertyName,
+                sensitive: sensitive
+            },
+            dataType: 'json'
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Handles verification results.
+     */
+    var handleVerificationResults = function (verificationResults, referencedAttributeMap) {
+        // record the most recently submitted referenced attributes
+        referencedAttributes = referencedAttributeMap;
+
+        var verificationResultsContainer = $('#flow-analysis-rule-properties-verification-results');
+
+        // expand the dialog to make room for the verification result
+        if (verificationResultsContainer.is(':visible') === false) {
+            // show the verification results
+            $('#flow-analysis-rule-properties').css('bottom', '40%').propertytable('resetTableSize')
+            verificationResultsContainer.show();
+        }
+
+        // show borders if appropriate
+        var verificationResultsListing = $('#flow-analysis-rule-properties-verification-results-listing');
+        if (verificationResultsListing.get(0).scrollHeight > Math.round(verificationResultsListing.innerHeight())) {
+            verificationResultsListing.css('border-width', '1px');
+        }
+    };
+
+    var nfFlowAnalysisRule = {
+        /**
+         * Initializes the flow analysis rule configuration dialog.
+         *
+         * @param nfSettingsRef   The nfSettings module.
+         */
+        init: function (nfSettingsRef) {
+            nfSettings = nfSettingsRef;
+
+            // initialize the configuration dialog tabs
+            $('#flow-analysis-rule-configuration-tabs').tabbs({
+                tabStyle: 'tab',
+                selectedTabStyle: 'selected-tab',
+                scrollableTabContentStyle: 'scrollable',
+                tabs: [{
+                    name: 'Settings',
+                    tabContentId: 'flow-analysis-rule-standard-settings-tab-content'
+                }, {
+                    name: 'Properties',
+                    tabContentId: 'flow-analysis-rule-properties-tab-content'
+                }, {
+                    name: 'Comments',
+                    tabContentId: 'flow-analysis-rule-comments-tab-content'
+                }],
+                select: function () {
+                    // remove all property detail dialogs
+                    nfUniversalCapture.removeAllPropertyDetailDialogs();
+
+                    // update the property table size in case this is the first time its rendered
+                    if ($(this).text() === 'Properties') {
+                        $('#flow-analysis-rule-properties').propertytable('resetTableSize');
+                    }
+
+                    // close all fields currently being edited
+                    $('#flow-analysis-rule-properties').propertytable('saveRow');
+                }
+            });
+
+            // initialize the flow analysis rule configuration dialog
+            $('#flow-analysis-rule-configuration').data('mode', config.edit).modal({
+                scrollableContentStyle: 'scrollable',
+                headerText: 'Configure Flow Analysis Rule',
+                handler: {
+                    close: function () {
+                        // cancel any active edits
+                        $('#flow-analysis-rule-properties').propertytable('cancelEdit');
+
+                        // clear the tables
+                        $('#flow-analysis-rule-properties').propertytable('clear');
+
+                        // clear the comments
+                        nfCommon.clearField('read-only-flow-analysis-rule-comments');
+
+                        // removed the cached flow analysis rule details
+                        $('#flow-analysis-rule-configuration').removeData('flowAnalysisRuleDetails');
+
+                        // clean up an shown verification errors
+                        $('#flow-analysis-rule-properties-verification-results').hide();
+                        $('#flow-analysis-rule-properties-verification-results-listing').css('border-width', '0').empty();
+                        $('#flow-analysis-rule-properties').css('bottom', '0');
+
+                        // clear most recently submitted referenced attributes
+                        referencedAttributes = null;
+                    },
+                    open: function () {
+                        nfCommon.toggleScrollable($('#' + this.find('.tab-container').attr('id') + '-content').get(0));
+                    }
+                }
+            });
+
+            // initialize the property table
+            $('#flow-analysis-rule-properties').propertytable({
+                readOnly: false,
+                supportsGoTo: true,
+                dialogContainer: '#new-flow-analysis-rule-property-container',
+                descriptorDeferred: getFlowAnalysisRulePropertyDescriptor,
+                controllerServiceCreatedDeferred: function (response) {
+                    return nfControllerServices.loadControllerServices(controllerServicesUri, $('#controller-services-table'));
+                },
+                goToServiceDeferred: goToServiceFromProperty
+            });
+        },
+
+        /**
+         * Shows the configuration dialog for the specified flow analysis rule.
+         *
+         * @argument {flowAnalysisRule} flowAnalysisRuleEntity      The flow analysis rule
+         */
+        showConfiguration: function (flowAnalysisRuleEntity) {
+            var flowAnalysisRuleDialog = $('#flow-analysis-rule-configuration');
+
+            flowAnalysisRuleDialog.find('.dialog-header .dialog-header-text').text('Configure Flow Analysis Rule');
+            if (flowAnalysisRuleDialog.data('mode') === config.readOnly) {
+                // update the visibility
+                $('#flow-analysis-rule-configuration .flow-analysis-rule-read-only').hide();
+                $('#flow-analysis-rule-configuration .flow-analysis-rule-editable').show();
+
+                // initialize the property table
+                $('#flow-analysis-rule-properties').propertytable('destroy').propertytable({
+                    readOnly: false,
+                    supportsGoTo: true,
+                    dialogContainer: '#new-flow-analysis-rule-property-container',
+                    descriptorDeferred: getFlowAnalysisRulePropertyDescriptor,
+                    controllerServiceCreatedDeferred: function (response) {
+                        return nfControllerServices.loadControllerServices(controllerServicesUri, $('#controller-services-table'));
+                    },
+                    goToServiceDeferred: goToServiceFromProperty
+                });
+
+                // update the mode
+                flowAnalysisRuleDialog.data('mode', config.edit);
+            }
+
+            // reload the task in case the property descriptors have changed
+            var reloadTask = $.ajax({
+                type: 'GET',
+                url: flowAnalysisRuleEntity.uri,
+                dataType: 'json'
+            });
+
+            // get the flow analysis rule history
+            var loadHistory = $.ajax({
+                type: 'GET',
+                url: '../nifi-api/flow/history/components/' + encodeURIComponent(flowAnalysisRuleEntity.id),
+                dataType: 'json'
+            });
+
+            // once everything is loaded, show the dialog
+            $.when(reloadTask, loadHistory).done(function (taskResponse, historyResponse) {
+                // get the updated flow analysis rule
+                flowAnalysisRuleEntity = taskResponse[0];
+                var flowAnalysisRule = flowAnalysisRuleEntity.component;
+
+                // get the flow analysis rule history
+                var flowAnalysisRuleHistory = historyResponse[0].componentHistory;
+
+                // record the flow analysis rule details
+                $('#flow-analysis-rule-configuration').data('flowAnalysisRuleDetails', flowAnalysisRuleEntity);
+
+                // populate the flow analysis rule settings
+                nfCommon.populateField('flow-analysis-rule-id', flowAnalysisRule['id']);
+                nfCommon.populateField('flow-analysis-rule-type', nfCommon.formatType(flowAnalysisRule));
+                nfCommon.populateField('flow-analysis-rule-bundle', nfCommon.formatBundle(flowAnalysisRule['bundle']));
+                $('#flow-analysis-rule-name').val(flowAnalysisRule['name']);
+                $('#flow-analysis-rule-comments').val(flowAnalysisRule['comments']);
+
+                $('#flow-analysis-rule-enforcement-policy-combo').combo({
+                    options: [{
+                        text: 'Enforce',
+                        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.'
+                    }],
+                    selectedOption: {
+                        value: flowAnalysisRule['enforcementPolicy']
+                    }
+                });
+
+                var buttons = [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            // close all fields currently being edited
+                            $('#flow-analysis-rule-properties').propertytable('saveRow');
+
+                            // save the flow analysis rule
+                            saveFlowAnalysisRule(flowAnalysisRuleEntity).done(function (response) {
+                                // reload the flow analysis rule
+                                nfControllerService.reloadReferencedServices(getControllerServicesTable(), response.component);
+
+                                // close the details panel
+                                $('#flow-analysis-rule-configuration').modal('hide');
+                            });
+                        }
+                    }
+                },
+                    {
+                        buttonText: 'Cancel',
+                        color: {
+                            base: '#E3E8EB',
+                            hover: '#C7D2D7',
+                            text: '#004849'
+                        },
+                        handler: {
+                            click: function () {
+                                $('#flow-analysis-rule-configuration').modal('hide');
+                            }
+                        }
+                    }];
+
+                // determine if we should show the advanced button
+                if (nfCommon.isDefinedAndNotNull(flowAnalysisRule.customUiUrl) && flowAnalysisRule.customUiUrl !== '') {
+                    buttons.push({
+                        buttonText: 'Advanced',
+                        clazz: 'fa fa-cog button-icon',
+                        color: {
+                            base: '#E3E8EB',
+                            hover: '#C7D2D7',
+                            text: '#004849'
+                        },
+                        handler: {
+                            click: function () {
+                                var openCustomUi = function () {
+                                    // reset state and close the dialog manually to avoid hiding the faded background
+                                    $('#flow-analysis-rule-configuration').modal('hide');
+
+                                    // close the settings dialog since the custom ui is also opened in the shell
+                                    $('#shell-close-button').click();
+
+                                    // show the custom ui
+                                    nfCustomUi.showCustomUi(flowAnalysisRuleEntity, flowAnalysisRule.customUiUrl, true).done(function () {
+                                        // once the custom ui is closed, reload the flow analysis rule
+                                        nfFlowAnalysisRule.reload(flowAnalysisRuleEntity.id).done(function (response) {
+                                            nfControllerService.reloadReferencedServices(getControllerServicesTable(), response.flowAnalysisRule);
+                                        });
+
+                                        // show the settings
+                                        nfSettings.showSettings();
+                                    });
+                                };
+
+                                // close all fields currently being edited
+                                $('#flow-analysis-rule-properties').propertytable('saveRow');
+
+                                // determine if changes have been made
+                                if (isSaveRequired()) {
+                                    // see if those changes should be saved
+                                    nfDialog.showYesNoDialog({
+                                        headerText: 'Save',
+                                        dialogContent: 'Save changes before opening the advanced configuration?',
+                                        noHandler: openCustomUi,
+                                        yesHandler: function () {
+                                            saveFlowAnalysisRule(flowAnalysisRuleEntity).done(function () {
+                                                // open the custom ui
+                                                openCustomUi();
+                                            });
+                                        }
+                                    });
+                                } else {
+                                    // if there were no changes, simply open the custom ui
+                                    openCustomUi();
+                                }
+                            }
+                        }
+                    });
+                }
+
+                // set the button model
+                $('#flow-analysis-rule-configuration').modal('setButtonModel', buttons);
+
+                // load the property table
+                $('#flow-analysis-rule-properties')
+                    .propertytable('setGroupId', null)
+                    .propertytable('setSupportsSensitiveDynamicProperties', flowAnalysisRule.supportsSensitiveDynamicProperties)
+                    .propertytable('loadProperties', flowAnalysisRule.properties, flowAnalysisRule.descriptors, flowAnalysisRuleHistory.propertyHistory)
+                    .propertytable('setPropertyVerificationCallback', function (proposedProperties) {
+                        nfVerify.verify(flowAnalysisRule['id'], flowAnalysisRuleEntity['uri'], proposedProperties, referencedAttributes, handleVerificationResults, $('#flow-analysis-rule-properties-verification-results-listing'));

Review Comment:
   Do Rules support Verification?



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js:
##########
@@ -1925,6 +1931,885 @@
         });
     };
 
+    /**
+     * Get the text out of the filter field. If the filter field doesn't
+     * have any text it will contain the text 'filter list' so this method
+     * accounts for that.
+     */
+    var getFlowAnalysisRuleTypeFilterText = function () {
+        return $('#flow-analysis-rule-type-filter').val();
+    };
+
+    /**
+     * Filters the flow analysis rule type table.
+     */
+    var applyFlowAnalysisRuleTypeFilter = function () {
+        // get the dataview
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+
+        // ensure the grid has been initialized
+        if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleTypesGrid)) {
+            var flowAnalysisRuleTypesData = flowAnalysisRuleTypesGrid.getData();
+
+            // update the search criteria
+            flowAnalysisRuleTypesData.setFilterArgs({
+                searchString: getFlowAnalysisRuleTypeFilterText()
+            });
+            flowAnalysisRuleTypesData.refresh();
+
+            // update the buttons to possibly trigger the disabled state
+            $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+
+            // update the selection if possible
+            if (flowAnalysisRuleTypesData.getLength() > 0) {
+                nfFilteredDialogCommon.choseFirstRow(flowAnalysisRuleTypesGrid);
+            }
+        }
+    };
+
+    /**
+     * Hides the selected flow analysis rule.
+     */
+    var clearSelectedFlowAnalysisRule = function () {
+        $('#flow-analysis-rule-type-description').attr('title', '').text('');
+        $('#flow-analysis-rule-type-name').attr('title', '').text('');
+        $('#flow-analysis-rule-type-bundle').attr('title', '').text('');
+        $('#selected-flow-analysis-rule-name').text('');
+        $('#selected-flow-analysis-rule-type').text('').removeData('bundle');
+        $('#flow-analysis-rule-description-container').hide();
+    };
+
+    /**
+     * Clears the selected flow analysis rule type.
+     */
+    var clearFlowAnalysisRuleSelection = function () {
+        // clear the selected row
+        clearSelectedFlowAnalysisRule();
+
+        // clear the active cell the it can be reselected when its included
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+        flowAnalysisRuleTypesGrid.resetActiveCell();
+    };
+
+    /**
+     * Performs the filtering.
+     *
+     * @param {object} item     The item subject to filtering
+     * @param {object} args     Filter arguments
+     * @returns {Boolean}       Whether or not to include the item
+     */
+    var filterFlowAnalysisRuleTypes = function (item, args) {
+        // determine if the item matches the filter
+        var matchesFilter = matchesRegex(item, args);
+
+        // determine if the row matches the selected tags
+        var matchesTags = true;
+        if (matchesFilter) {
+            var tagFilters = $('#flow-analysis-rule-tag-cloud').tagcloud('getSelectedTags');
+            var hasSelectedTags = tagFilters.length > 0;
+            if (hasSelectedTags) {
+                matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+            }
+        }
+
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#flow-analysis-rule-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
+        // determine if this row should be visible
+        var matches = matchesFilter && matchesTags && matchesGroup;
+
+        // if this row is currently selected and its being filtered
+        if (matches === false && $('#selected-flow-analysis-rule-type').text() === item['type']) {
+            clearFlowAnalysisRuleSelection();
+        }
+
+        return matches;
+    };
+
+    /**
+     * Adds the currently selected flow analysis rule.
+     */
+    var addSelectedFlowAnalysisRule = function () {
+        var selectedTaskType = $('#selected-flow-analysis-rule-type').text();
+        var selectedTaskBundle = $('#selected-flow-analysis-rule-type').data('bundle');
+
+        // ensure something was selected
+        if (selectedTaskType === '') {
+            nfDialog.showOkDialog({
+                headerText: 'Settings',
+                dialogContent: 'The type of flow analysis rule to create must be selected.'
+            });
+        } else {
+            addFlowAnalysisRule(selectedTaskType, selectedTaskBundle);
+        }
+    };
+
+    /**
+     * Adds a new flow analysis rule of the specified type.
+     *
+     * @param {string} flowAnalysisRuleType
+     * @param {object} flowAnalysisRuleBundle
+     */
+    var addFlowAnalysisRule = function (flowAnalysisRuleType, flowAnalysisRuleBundle) {
+        // build the flow analysis rule entity
+        var flowAnalysisRuleEntity = {
+            'revision': nfClient.getRevision({
+                'revision': {
+                    'version': 0
+                }
+            }),
+            'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+            'component': {
+                'type': flowAnalysisRuleType,
+                'bundle': flowAnalysisRuleBundle
+            }
+        };
+
+        // add the new flow analysis rule
+        var addRule = $.ajax({
+            type: 'POST',
+            url: config.urls.createFlowAnalysisRule,
+            data: JSON.stringify(flowAnalysisRuleEntity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (flowAnalysisRuleEntity) {
+            // add the item
+            var flowAnalysisRuleGrid = $('#flow-analysis-rules-table').data('gridInstance');
+            var flowAnalysisRuleData = flowAnalysisRuleGrid.getData();
+            flowAnalysisRuleData.addItem($.extend({
+                type: 'FlowAnalysisRule',
+                bulletins: []
+            }, flowAnalysisRuleEntity));
+
+            // resort
+            flowAnalysisRuleData.reSort();
+            flowAnalysisRuleGrid.invalidate();
+
+            // select the new flow analysis rule
+            var row = flowAnalysisRuleData.getRowById(flowAnalysisRuleEntity.id);
+            nfFilteredDialogCommon.choseRow(flowAnalysisRuleGrid, row);
+            flowAnalysisRuleGrid.scrollRowIntoView(row);
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        // hide the dialog
+        $('#new-flow-analysis-rule-dialog').modal('hide');
+
+        return addRule;
+    };
+
+    /**
+     * Initializes the new flow analysis rule dialog.
+     */
+    var initNewFlowAnalysisRuleDialog = function () {
+        // initialize the flow analysis rule type table
+        var flowAnalysisRuleTypesColumns = [
+            {
+                id: 'type',
+                name: 'Type',
+                field: 'label',
+                formatter: nfCommon.typeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'tags',
+                name: 'Tags',
+                field: 'tags',
+                sortable: true,
+                resizable: true,
+                formatter: nfCommon.genericValueFormatter
+            }
+        ];
+
+        // initialize the dataview
+        var flowAnalysisRuleTypesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        flowAnalysisRuleTypesData.setItems([]);
+        flowAnalysisRuleTypesData.setFilterArgs({
+            searchString: getFlowAnalysisRuleTypeFilterText()
+        });
+        flowAnalysisRuleTypesData.setFilter(filterFlowAnalysisRuleTypes);
+
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, flowAnalysisRuleTypesData);
+
+        // initialize the grid
+        var flowAnalysisRuleTypesGrid = new Slick.Grid('#flow-analysis-rule-types-table', flowAnalysisRuleTypesData, flowAnalysisRuleTypesColumns, gridOptions);
+        flowAnalysisRuleTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+        flowAnalysisRuleTypesGrid.registerPlugin(new Slick.AutoTooltips());
+        flowAnalysisRuleTypesGrid.setSortColumn('type', true);
+        flowAnalysisRuleTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, flowAnalysisRuleTypesData);
+        });
+        flowAnalysisRuleTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+            if ($.isArray(args.rows) && args.rows.length === 1) {
+                var flowAnalysisRuleTypeIndex = args.rows[0];
+                var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(flowAnalysisRuleTypeIndex);
+
+                // set the flow analysis rule type description
+                if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleType)) {
+                    // show the selected flow analysis rule
+                    $('#flow-analysis-rule-description-container').show();
+
+                    if (nfCommon.isBlank(flowAnalysisRuleType.description)) {
+                        $('#flow-analysis-rule-type-description')
+                            .attr('title', '')
+                            .html('<span class="unset">No description specified</span>');
+                    } else {
+                        $('#flow-analysis-rule-type-description')
+                            .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                            .html(flowAnalysisRuleType.description)
+                            .ellipsis();
+                    }
+
+                    var bundle = nfCommon.formatBundle(flowAnalysisRuleType.bundle);
+                    var type = nfCommon.formatType(flowAnalysisRuleType);
+
+                    // populate the dom
+                    $('#flow-analysis-rule-type-name').text(type).attr('title', type);
+                    $('#flow-analysis-rule-type-bundle').text(bundle).attr('title', bundle);
+                    $('#selected-flow-analysis-rule-name').text(flowAnalysisRuleType.label);
+                    $('#selected-flow-analysis-rule-type').text(flowAnalysisRuleType.type).data('bundle', flowAnalysisRuleType.bundle);
+
+                    // refresh the buttons based on the current selection
+                    $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+                }
+            }
+        });
+        flowAnalysisRuleTypesGrid.onDblClick.subscribe(function (e, args) {
+            var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(args.row);
+
+            if (isSelectable(flowAnalysisRuleType)) {
+                addFlowAnalysisRule(flowAnalysisRuleType.type, flowAnalysisRuleType.bundle);
+            }
+        });
+        flowAnalysisRuleTypesGrid.onViewportChanged.subscribe(function (e, args) {
+            nfCommon.cleanUpTooltips($('#flow-analysis-rule-types-table'), 'div.view-usage-restriction');
+        });
+
+        // wire up the dataview to the grid
+        flowAnalysisRuleTypesData.onRowCountChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.updateRowCount();
+            flowAnalysisRuleTypesGrid.render();
+
+            // update the total number of displayed processors
+            $('#displayed-flow-analysis-rule-types').text(args.current);
+        });
+        flowAnalysisRuleTypesData.onRowsChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.invalidateRows(args.rows);
+            flowAnalysisRuleTypesGrid.render();
+        });
+        flowAnalysisRuleTypesData.syncGridSelection(flowAnalysisRuleTypesGrid, true);
+
+        // hold onto an instance of the grid
+        $('#flow-analysis-rule-types-table').data('gridInstance', flowAnalysisRuleTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var usageRestriction = $(this).find('div.view-usage-restriction');
+            if (usageRestriction.length && !usageRestriction.data('qtip')) {
+                var rowId = $(this).find('span.row-id').text();
+
+                // get the status item
+                var item = flowAnalysisRuleTypesData.getItemById(rowId);
+
+                // show the tooltip
+                if (item.restricted === true) {
+                    var restrictionTip = $('<div></div>');
+
+                    if (nfCommon.isBlank(item.usageRestriction)) {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+                    } else {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+                    }
+
+                    var restrictions = [];
+                    if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+                        $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+                            restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+                        });
+                    } else {
+                        restrictions.push('Access to restricted components regardless of restrictions.');
+                    }
+                    restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+                    usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+                        content: restrictionTip,
+                        position: {
+                            container: $('#summary'),
+                            at: 'bottom right',
+                            my: 'top left',
+                            adjust: {
+                                x: 4,
+                                y: 4
+                            }
+                        }
+                    }));
+                }
+            }
+        });
+
+        var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+        // load the available flow analysis rules
+        $.ajax({
+            type: 'GET',
+            url: config.urls.flowAnalysisRuleTypes,
+            dataType: 'json'
+        }).done(function (response) {
+            var id = 0;
+            var tags = [];
+            var groups = d3.set();
+            var restrictedUsage = d3.map();
+            var requiredPermissions = d3.map();
+
+            // begin the update
+            flowAnalysisRuleTypesData.beginUpdate();
+
+            // go through each flow analysis rule type
+            $.each(response.flowAnalysisRuleTypes, function (i, documentedType) {
+                if (documentedType.restricted === true) {
+                    if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+                        $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+
+                            // update required permissions
+                            if (!requiredPermissions.has(requiredPermission.id)) {
+                                requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+                            }
+
+                            // update component restrictions
+                            if (!restrictedUsage.has(requiredPermission.id)) {
+                                restrictedUsage.set(requiredPermission.id, []);
+                            }
+
+                            restrictedUsage.get(requiredPermission.id).push({
+                                type: nfCommon.formatType(documentedType),
+                                bundle: nfCommon.formatBundle(documentedType.bundle),
+                                explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+                            })
+                        });
+                    } else {
+                        // update required permissions
+                        if (!requiredPermissions.has(generalRestriction.value)) {
+                            requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+                        }
+
+                        // update component restrictions
+                        if (!restrictedUsage.has(generalRestriction.value)) {
+                            restrictedUsage.set(generalRestriction.value, []);
+                        }
+
+                        restrictedUsage.get(generalRestriction.value).push({
+                            type: nfCommon.formatType(documentedType),
+                            bundle: nfCommon.formatBundle(documentedType.bundle),
+                            explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+                        });
+                    }
+                }
+
+                // record the group
+                groups.add(documentedType.bundle.group);
+
+                // add the documented type
+                flowAnalysisRuleTypesData.addItem({
+                    id: id++,
+                    label: nfCommon.substringAfterLast(documentedType.type, '.'),
+                    type: documentedType.type,
+                    bundle: documentedType.bundle,
+                    description: nfCommon.escapeHtml(documentedType.description),
+                    restricted:  documentedType.restricted,
+                    usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+                    explicitRestrictions: documentedType.explicitRestrictions,
+                    tags: documentedType.tags.join(', ')
+                });
+
+                // count the frequency of each tag for this type
+                $.each(documentedType.tags, function (i, tag) {
+                    tags.push(tag.toLowerCase());
+                });
+            });
+
+            // end the update
+            flowAnalysisRuleTypesData.endUpdate();
+
+            // resort
+            flowAnalysisRuleTypesData.reSort();
+            flowAnalysisRuleTypesGrid.invalidate();
+
+            // set the component restrictions and the corresponding required permissions
+            nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+            // set the total number of processors
+            $('#total-flow-analysis-rule-types, #displayed-flow-analysis-rule-types').text(response.flowAnalysisRuleTypes.length);
+
+            // create the tag cloud
+            $('#flow-analysis-rule-tag-cloud').tagcloud({
+                tags: tags,
+                select: applyFlowAnalysisRuleTypeFilter,
+                remove: applyFlowAnalysisRuleTypeFilter
+            });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.each(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#flow-analysis-rule-bundle-group-combo').combo({
+                options: options,
+                select: applyFlowAnalysisRuleTypeFilter
+            });
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+        // define the function for filtering the list
+        $('#flow-analysis-rule-type-filter').off('keyup').on('keyup', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+
+            // ignore navigation keys
+            if ($.inArray(code, navigationKeys) !== -1) {
+                return;
+            }
+
+            if (code === $.ui.keyCode.ENTER) {
+                var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                if (selected.length > 0) {
+                    // grid configured with multi-select = false
+                    var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                    if (isSelectable(item)) {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            } else {
+                applyFlowAnalysisRuleTypeFilter();
+            }
+        });
+
+        // setup row navigation
+        nfFilteredDialogCommon.addKeydownListener('#flow-analysis-rule-type-filter', flowAnalysisRuleTypesGrid, flowAnalysisRuleTypesGrid.getData());
+
+        // initialize the flow analysis rule dialog
+        $('#new-flow-analysis-rule-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Add Flow Analysis Rule',
+            buttons: [{
+                buttonText: 'Add',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                    if (selected.length > 0) {
+                        // grid configured with multi-select = false
+                        var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                        return isSelectable(item) === false;
+                    } else {
+                        return flowAnalysisRuleTypesGrid.getData().getLength() === 0;
+                    }
+                },
+                handler: {
+                    click: function () {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            },
+                {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+            handler: {
+                close: function () {
+                    // clear the selected row
+                    clearSelectedFlowAnalysisRule();
+
+                    // clear any filter strings
+                    $('#flow-analysis-rule-type-filter').val('');
+
+                    // clear the tagcloud
+                    $('#flow-analysis-rule-tag-cloud').tagcloud('clearSelectedTags');
+
+                    // reset the group combo
+                    $('#flow-analysis-rule-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
+                    // reset the filter
+                    applyFlowAnalysisRuleTypeFilter();
+
+                    // unselect any current selection
+                    var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+                    flowAnalysisRuleTypesGrid.setSelectedRows([]);
+                    flowAnalysisRuleTypesGrid.resetActiveCell();
+                },
+                resize: function () {
+                    $('#flow-analysis-rule-type-description')
+                        .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                        .text($('#flow-analysis-rule-type-description').attr('title'))
+                        .ellipsis();
+                }
+            }
+        });
+
+        // initialize the registry configuration dialog
+        $('#registry-configuration-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            handler: {
+                close: function () {
+                    $('#registry-id').text('');
+                    $('#registry-name').val('');
+                    $('#registry-location').val('');
+                    $('#registry-description').val('');
+                }
+            }
+        });
+    };
+
+    /**
+     * Initializes the flow analysis rules tab.
+     */
+    var initFlowAnalysisRules = function () {
+        // initialize the new flow analysis rule dialog
+        initNewFlowAnalysisRuleDialog();
+
+        var moreFlowAnalysisRuleDetails = function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            var markup = '<div title="View Details" class="pointer view-flow-analysis-rule fa fa-info-circle"></div>';
+
+            // always include a button to view the usage
+            markup += '<div title="Usage" class="pointer flow-analysis-rule-usage fa fa-book"></div>';

Review Comment:
   Do Rules generate docs that we'd link to in Help?



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js:
##########
@@ -1925,6 +1931,885 @@
         });
     };
 
+    /**
+     * Get the text out of the filter field. If the filter field doesn't
+     * have any text it will contain the text 'filter list' so this method
+     * accounts for that.
+     */
+    var getFlowAnalysisRuleTypeFilterText = function () {
+        return $('#flow-analysis-rule-type-filter').val();
+    };
+
+    /**
+     * Filters the flow analysis rule type table.
+     */
+    var applyFlowAnalysisRuleTypeFilter = function () {
+        // get the dataview
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+
+        // ensure the grid has been initialized
+        if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleTypesGrid)) {
+            var flowAnalysisRuleTypesData = flowAnalysisRuleTypesGrid.getData();
+
+            // update the search criteria
+            flowAnalysisRuleTypesData.setFilterArgs({
+                searchString: getFlowAnalysisRuleTypeFilterText()
+            });
+            flowAnalysisRuleTypesData.refresh();
+
+            // update the buttons to possibly trigger the disabled state
+            $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+
+            // update the selection if possible
+            if (flowAnalysisRuleTypesData.getLength() > 0) {
+                nfFilteredDialogCommon.choseFirstRow(flowAnalysisRuleTypesGrid);
+            }
+        }
+    };
+
+    /**
+     * Hides the selected flow analysis rule.
+     */
+    var clearSelectedFlowAnalysisRule = function () {
+        $('#flow-analysis-rule-type-description').attr('title', '').text('');
+        $('#flow-analysis-rule-type-name').attr('title', '').text('');
+        $('#flow-analysis-rule-type-bundle').attr('title', '').text('');
+        $('#selected-flow-analysis-rule-name').text('');
+        $('#selected-flow-analysis-rule-type').text('').removeData('bundle');
+        $('#flow-analysis-rule-description-container').hide();
+    };
+
+    /**
+     * Clears the selected flow analysis rule type.
+     */
+    var clearFlowAnalysisRuleSelection = function () {
+        // clear the selected row
+        clearSelectedFlowAnalysisRule();
+
+        // clear the active cell the it can be reselected when its included
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+        flowAnalysisRuleTypesGrid.resetActiveCell();
+    };
+
+    /**
+     * Performs the filtering.
+     *
+     * @param {object} item     The item subject to filtering
+     * @param {object} args     Filter arguments
+     * @returns {Boolean}       Whether or not to include the item
+     */
+    var filterFlowAnalysisRuleTypes = function (item, args) {
+        // determine if the item matches the filter
+        var matchesFilter = matchesRegex(item, args);
+
+        // determine if the row matches the selected tags
+        var matchesTags = true;
+        if (matchesFilter) {
+            var tagFilters = $('#flow-analysis-rule-tag-cloud').tagcloud('getSelectedTags');
+            var hasSelectedTags = tagFilters.length > 0;
+            if (hasSelectedTags) {
+                matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+            }
+        }
+
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#flow-analysis-rule-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
+        // determine if this row should be visible
+        var matches = matchesFilter && matchesTags && matchesGroup;
+
+        // if this row is currently selected and its being filtered
+        if (matches === false && $('#selected-flow-analysis-rule-type').text() === item['type']) {
+            clearFlowAnalysisRuleSelection();
+        }
+
+        return matches;
+    };
+
+    /**
+     * Adds the currently selected flow analysis rule.
+     */
+    var addSelectedFlowAnalysisRule = function () {
+        var selectedTaskType = $('#selected-flow-analysis-rule-type').text();
+        var selectedTaskBundle = $('#selected-flow-analysis-rule-type').data('bundle');
+
+        // ensure something was selected
+        if (selectedTaskType === '') {
+            nfDialog.showOkDialog({
+                headerText: 'Settings',
+                dialogContent: 'The type of flow analysis rule to create must be selected.'
+            });
+        } else {
+            addFlowAnalysisRule(selectedTaskType, selectedTaskBundle);
+        }
+    };
+
+    /**
+     * Adds a new flow analysis rule of the specified type.
+     *
+     * @param {string} flowAnalysisRuleType
+     * @param {object} flowAnalysisRuleBundle
+     */
+    var addFlowAnalysisRule = function (flowAnalysisRuleType, flowAnalysisRuleBundle) {
+        // build the flow analysis rule entity
+        var flowAnalysisRuleEntity = {
+            'revision': nfClient.getRevision({
+                'revision': {
+                    'version': 0
+                }
+            }),
+            'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+            'component': {
+                'type': flowAnalysisRuleType,
+                'bundle': flowAnalysisRuleBundle
+            }
+        };
+
+        // add the new flow analysis rule
+        var addRule = $.ajax({
+            type: 'POST',
+            url: config.urls.createFlowAnalysisRule,
+            data: JSON.stringify(flowAnalysisRuleEntity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (flowAnalysisRuleEntity) {
+            // add the item
+            var flowAnalysisRuleGrid = $('#flow-analysis-rules-table').data('gridInstance');
+            var flowAnalysisRuleData = flowAnalysisRuleGrid.getData();
+            flowAnalysisRuleData.addItem($.extend({
+                type: 'FlowAnalysisRule',
+                bulletins: []
+            }, flowAnalysisRuleEntity));
+
+            // resort
+            flowAnalysisRuleData.reSort();
+            flowAnalysisRuleGrid.invalidate();
+
+            // select the new flow analysis rule
+            var row = flowAnalysisRuleData.getRowById(flowAnalysisRuleEntity.id);
+            nfFilteredDialogCommon.choseRow(flowAnalysisRuleGrid, row);
+            flowAnalysisRuleGrid.scrollRowIntoView(row);
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        // hide the dialog
+        $('#new-flow-analysis-rule-dialog').modal('hide');
+
+        return addRule;
+    };
+
+    /**
+     * Initializes the new flow analysis rule dialog.
+     */
+    var initNewFlowAnalysisRuleDialog = function () {
+        // initialize the flow analysis rule type table
+        var flowAnalysisRuleTypesColumns = [
+            {
+                id: 'type',
+                name: 'Type',
+                field: 'label',
+                formatter: nfCommon.typeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'tags',
+                name: 'Tags',
+                field: 'tags',
+                sortable: true,
+                resizable: true,
+                formatter: nfCommon.genericValueFormatter
+            }
+        ];
+
+        // initialize the dataview
+        var flowAnalysisRuleTypesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        flowAnalysisRuleTypesData.setItems([]);
+        flowAnalysisRuleTypesData.setFilterArgs({
+            searchString: getFlowAnalysisRuleTypeFilterText()
+        });
+        flowAnalysisRuleTypesData.setFilter(filterFlowAnalysisRuleTypes);
+
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, flowAnalysisRuleTypesData);
+
+        // initialize the grid
+        var flowAnalysisRuleTypesGrid = new Slick.Grid('#flow-analysis-rule-types-table', flowAnalysisRuleTypesData, flowAnalysisRuleTypesColumns, gridOptions);
+        flowAnalysisRuleTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+        flowAnalysisRuleTypesGrid.registerPlugin(new Slick.AutoTooltips());
+        flowAnalysisRuleTypesGrid.setSortColumn('type', true);
+        flowAnalysisRuleTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, flowAnalysisRuleTypesData);
+        });
+        flowAnalysisRuleTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+            if ($.isArray(args.rows) && args.rows.length === 1) {
+                var flowAnalysisRuleTypeIndex = args.rows[0];
+                var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(flowAnalysisRuleTypeIndex);
+
+                // set the flow analysis rule type description
+                if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleType)) {
+                    // show the selected flow analysis rule
+                    $('#flow-analysis-rule-description-container').show();
+
+                    if (nfCommon.isBlank(flowAnalysisRuleType.description)) {
+                        $('#flow-analysis-rule-type-description')
+                            .attr('title', '')
+                            .html('<span class="unset">No description specified</span>');
+                    } else {
+                        $('#flow-analysis-rule-type-description')
+                            .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                            .html(flowAnalysisRuleType.description)
+                            .ellipsis();
+                    }
+
+                    var bundle = nfCommon.formatBundle(flowAnalysisRuleType.bundle);
+                    var type = nfCommon.formatType(flowAnalysisRuleType);
+
+                    // populate the dom
+                    $('#flow-analysis-rule-type-name').text(type).attr('title', type);
+                    $('#flow-analysis-rule-type-bundle').text(bundle).attr('title', bundle);
+                    $('#selected-flow-analysis-rule-name').text(flowAnalysisRuleType.label);
+                    $('#selected-flow-analysis-rule-type').text(flowAnalysisRuleType.type).data('bundle', flowAnalysisRuleType.bundle);
+
+                    // refresh the buttons based on the current selection
+                    $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+                }
+            }
+        });
+        flowAnalysisRuleTypesGrid.onDblClick.subscribe(function (e, args) {
+            var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(args.row);
+
+            if (isSelectable(flowAnalysisRuleType)) {
+                addFlowAnalysisRule(flowAnalysisRuleType.type, flowAnalysisRuleType.bundle);
+            }
+        });
+        flowAnalysisRuleTypesGrid.onViewportChanged.subscribe(function (e, args) {
+            nfCommon.cleanUpTooltips($('#flow-analysis-rule-types-table'), 'div.view-usage-restriction');
+        });
+
+        // wire up the dataview to the grid
+        flowAnalysisRuleTypesData.onRowCountChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.updateRowCount();
+            flowAnalysisRuleTypesGrid.render();
+
+            // update the total number of displayed processors
+            $('#displayed-flow-analysis-rule-types').text(args.current);
+        });
+        flowAnalysisRuleTypesData.onRowsChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.invalidateRows(args.rows);
+            flowAnalysisRuleTypesGrid.render();
+        });
+        flowAnalysisRuleTypesData.syncGridSelection(flowAnalysisRuleTypesGrid, true);
+
+        // hold onto an instance of the grid
+        $('#flow-analysis-rule-types-table').data('gridInstance', flowAnalysisRuleTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var usageRestriction = $(this).find('div.view-usage-restriction');
+            if (usageRestriction.length && !usageRestriction.data('qtip')) {
+                var rowId = $(this).find('span.row-id').text();
+
+                // get the status item
+                var item = flowAnalysisRuleTypesData.getItemById(rowId);
+
+                // show the tooltip
+                if (item.restricted === true) {
+                    var restrictionTip = $('<div></div>');
+
+                    if (nfCommon.isBlank(item.usageRestriction)) {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+                    } else {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+                    }
+
+                    var restrictions = [];
+                    if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+                        $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+                            restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+                        });
+                    } else {
+                        restrictions.push('Access to restricted components regardless of restrictions.');
+                    }
+                    restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+                    usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+                        content: restrictionTip,
+                        position: {
+                            container: $('#summary'),
+                            at: 'bottom right',
+                            my: 'top left',
+                            adjust: {
+                                x: 4,
+                                y: 4
+                            }
+                        }
+                    }));
+                }
+            }
+        });
+
+        var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+        // load the available flow analysis rules
+        $.ajax({
+            type: 'GET',
+            url: config.urls.flowAnalysisRuleTypes,
+            dataType: 'json'
+        }).done(function (response) {
+            var id = 0;
+            var tags = [];
+            var groups = d3.set();
+            var restrictedUsage = d3.map();
+            var requiredPermissions = d3.map();
+
+            // begin the update
+            flowAnalysisRuleTypesData.beginUpdate();
+
+            // go through each flow analysis rule type
+            $.each(response.flowAnalysisRuleTypes, function (i, documentedType) {
+                if (documentedType.restricted === true) {
+                    if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+                        $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+
+                            // update required permissions
+                            if (!requiredPermissions.has(requiredPermission.id)) {
+                                requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+                            }
+
+                            // update component restrictions
+                            if (!restrictedUsage.has(requiredPermission.id)) {
+                                restrictedUsage.set(requiredPermission.id, []);
+                            }
+
+                            restrictedUsage.get(requiredPermission.id).push({
+                                type: nfCommon.formatType(documentedType),
+                                bundle: nfCommon.formatBundle(documentedType.bundle),
+                                explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+                            })
+                        });
+                    } else {
+                        // update required permissions
+                        if (!requiredPermissions.has(generalRestriction.value)) {
+                            requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+                        }
+
+                        // update component restrictions
+                        if (!restrictedUsage.has(generalRestriction.value)) {
+                            restrictedUsage.set(generalRestriction.value, []);
+                        }
+
+                        restrictedUsage.get(generalRestriction.value).push({
+                            type: nfCommon.formatType(documentedType),
+                            bundle: nfCommon.formatBundle(documentedType.bundle),
+                            explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+                        });
+                    }
+                }
+
+                // record the group
+                groups.add(documentedType.bundle.group);
+
+                // add the documented type
+                flowAnalysisRuleTypesData.addItem({
+                    id: id++,
+                    label: nfCommon.substringAfterLast(documentedType.type, '.'),
+                    type: documentedType.type,
+                    bundle: documentedType.bundle,
+                    description: nfCommon.escapeHtml(documentedType.description),
+                    restricted:  documentedType.restricted,
+                    usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+                    explicitRestrictions: documentedType.explicitRestrictions,
+                    tags: documentedType.tags.join(', ')
+                });
+
+                // count the frequency of each tag for this type
+                $.each(documentedType.tags, function (i, tag) {
+                    tags.push(tag.toLowerCase());
+                });
+            });
+
+            // end the update
+            flowAnalysisRuleTypesData.endUpdate();
+
+            // resort
+            flowAnalysisRuleTypesData.reSort();
+            flowAnalysisRuleTypesGrid.invalidate();
+
+            // set the component restrictions and the corresponding required permissions
+            nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+            // set the total number of processors
+            $('#total-flow-analysis-rule-types, #displayed-flow-analysis-rule-types').text(response.flowAnalysisRuleTypes.length);
+
+            // create the tag cloud
+            $('#flow-analysis-rule-tag-cloud').tagcloud({
+                tags: tags,
+                select: applyFlowAnalysisRuleTypeFilter,
+                remove: applyFlowAnalysisRuleTypeFilter
+            });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.each(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#flow-analysis-rule-bundle-group-combo').combo({
+                options: options,
+                select: applyFlowAnalysisRuleTypeFilter
+            });
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+        // define the function for filtering the list
+        $('#flow-analysis-rule-type-filter').off('keyup').on('keyup', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+
+            // ignore navigation keys
+            if ($.inArray(code, navigationKeys) !== -1) {
+                return;
+            }
+
+            if (code === $.ui.keyCode.ENTER) {
+                var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                if (selected.length > 0) {
+                    // grid configured with multi-select = false
+                    var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                    if (isSelectable(item)) {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            } else {
+                applyFlowAnalysisRuleTypeFilter();
+            }
+        });
+
+        // setup row navigation
+        nfFilteredDialogCommon.addKeydownListener('#flow-analysis-rule-type-filter', flowAnalysisRuleTypesGrid, flowAnalysisRuleTypesGrid.getData());
+
+        // initialize the flow analysis rule dialog
+        $('#new-flow-analysis-rule-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Add Flow Analysis Rule',
+            buttons: [{
+                buttonText: 'Add',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                    if (selected.length > 0) {
+                        // grid configured with multi-select = false
+                        var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                        return isSelectable(item) === false;
+                    } else {
+                        return flowAnalysisRuleTypesGrid.getData().getLength() === 0;
+                    }
+                },
+                handler: {
+                    click: function () {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            },
+                {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+            handler: {
+                close: function () {
+                    // clear the selected row
+                    clearSelectedFlowAnalysisRule();
+
+                    // clear any filter strings
+                    $('#flow-analysis-rule-type-filter').val('');
+
+                    // clear the tagcloud
+                    $('#flow-analysis-rule-tag-cloud').tagcloud('clearSelectedTags');
+
+                    // reset the group combo
+                    $('#flow-analysis-rule-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
+                    // reset the filter
+                    applyFlowAnalysisRuleTypeFilter();
+
+                    // unselect any current selection
+                    var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+                    flowAnalysisRuleTypesGrid.setSelectedRows([]);
+                    flowAnalysisRuleTypesGrid.resetActiveCell();
+                },
+                resize: function () {
+                    $('#flow-analysis-rule-type-description')
+                        .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                        .text($('#flow-analysis-rule-type-description').attr('title'))
+                        .ellipsis();
+                }
+            }
+        });
+
+        // initialize the registry configuration dialog
+        $('#registry-configuration-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            handler: {
+                close: function () {
+                    $('#registry-id').text('');
+                    $('#registry-name').val('');
+                    $('#registry-location').val('');
+                    $('#registry-description').val('');
+                }
+            }
+        });
+    };
+
+    /**
+     * Initializes the flow analysis rules tab.
+     */
+    var initFlowAnalysisRules = function () {
+        // initialize the new flow analysis rule dialog
+        initNewFlowAnalysisRuleDialog();
+
+        var moreFlowAnalysisRuleDetails = function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            var markup = '<div title="View Details" class="pointer view-flow-analysis-rule fa fa-info-circle"></div>';
+
+            // always include a button to view the usage
+            markup += '<div title="Usage" class="pointer flow-analysis-rule-usage fa fa-book"></div>';
+
+            var hasErrors = !nfCommon.isEmpty(dataContext.component.validationErrors);
+            var hasBulletins = !nfCommon.isEmpty(dataContext.bulletins);

Review Comment:
   Do Flow Analysis Rules have Bulletins?



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js:
##########
@@ -1925,6 +1931,885 @@
         });
     };
 
+    /**
+     * Get the text out of the filter field. If the filter field doesn't
+     * have any text it will contain the text 'filter list' so this method
+     * accounts for that.
+     */
+    var getFlowAnalysisRuleTypeFilterText = function () {
+        return $('#flow-analysis-rule-type-filter').val();
+    };
+
+    /**
+     * Filters the flow analysis rule type table.
+     */
+    var applyFlowAnalysisRuleTypeFilter = function () {
+        // get the dataview
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+
+        // ensure the grid has been initialized
+        if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleTypesGrid)) {
+            var flowAnalysisRuleTypesData = flowAnalysisRuleTypesGrid.getData();
+
+            // update the search criteria
+            flowAnalysisRuleTypesData.setFilterArgs({
+                searchString: getFlowAnalysisRuleTypeFilterText()
+            });
+            flowAnalysisRuleTypesData.refresh();
+
+            // update the buttons to possibly trigger the disabled state
+            $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+
+            // update the selection if possible
+            if (flowAnalysisRuleTypesData.getLength() > 0) {
+                nfFilteredDialogCommon.choseFirstRow(flowAnalysisRuleTypesGrid);
+            }
+        }
+    };
+
+    /**
+     * Hides the selected flow analysis rule.
+     */
+    var clearSelectedFlowAnalysisRule = function () {
+        $('#flow-analysis-rule-type-description').attr('title', '').text('');
+        $('#flow-analysis-rule-type-name').attr('title', '').text('');
+        $('#flow-analysis-rule-type-bundle').attr('title', '').text('');
+        $('#selected-flow-analysis-rule-name').text('');
+        $('#selected-flow-analysis-rule-type').text('').removeData('bundle');
+        $('#flow-analysis-rule-description-container').hide();
+    };
+
+    /**
+     * Clears the selected flow analysis rule type.
+     */
+    var clearFlowAnalysisRuleSelection = function () {
+        // clear the selected row
+        clearSelectedFlowAnalysisRule();
+
+        // clear the active cell the it can be reselected when its included
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+        flowAnalysisRuleTypesGrid.resetActiveCell();
+    };
+
+    /**
+     * Performs the filtering.
+     *
+     * @param {object} item     The item subject to filtering
+     * @param {object} args     Filter arguments
+     * @returns {Boolean}       Whether or not to include the item
+     */
+    var filterFlowAnalysisRuleTypes = function (item, args) {
+        // determine if the item matches the filter
+        var matchesFilter = matchesRegex(item, args);
+
+        // determine if the row matches the selected tags
+        var matchesTags = true;
+        if (matchesFilter) {
+            var tagFilters = $('#flow-analysis-rule-tag-cloud').tagcloud('getSelectedTags');
+            var hasSelectedTags = tagFilters.length > 0;
+            if (hasSelectedTags) {
+                matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+            }
+        }
+
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#flow-analysis-rule-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
+        // determine if this row should be visible
+        var matches = matchesFilter && matchesTags && matchesGroup;
+
+        // if this row is currently selected and its being filtered
+        if (matches === false && $('#selected-flow-analysis-rule-type').text() === item['type']) {
+            clearFlowAnalysisRuleSelection();
+        }
+
+        return matches;
+    };
+
+    /**
+     * Adds the currently selected flow analysis rule.
+     */
+    var addSelectedFlowAnalysisRule = function () {
+        var selectedTaskType = $('#selected-flow-analysis-rule-type').text();
+        var selectedTaskBundle = $('#selected-flow-analysis-rule-type').data('bundle');
+
+        // ensure something was selected
+        if (selectedTaskType === '') {
+            nfDialog.showOkDialog({
+                headerText: 'Settings',
+                dialogContent: 'The type of flow analysis rule to create must be selected.'
+            });
+        } else {
+            addFlowAnalysisRule(selectedTaskType, selectedTaskBundle);
+        }
+    };
+
+    /**
+     * Adds a new flow analysis rule of the specified type.
+     *
+     * @param {string} flowAnalysisRuleType
+     * @param {object} flowAnalysisRuleBundle
+     */
+    var addFlowAnalysisRule = function (flowAnalysisRuleType, flowAnalysisRuleBundle) {
+        // build the flow analysis rule entity
+        var flowAnalysisRuleEntity = {
+            'revision': nfClient.getRevision({
+                'revision': {
+                    'version': 0
+                }
+            }),
+            'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+            'component': {
+                'type': flowAnalysisRuleType,
+                'bundle': flowAnalysisRuleBundle
+            }
+        };
+
+        // add the new flow analysis rule
+        var addRule = $.ajax({
+            type: 'POST',
+            url: config.urls.createFlowAnalysisRule,
+            data: JSON.stringify(flowAnalysisRuleEntity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (flowAnalysisRuleEntity) {
+            // add the item
+            var flowAnalysisRuleGrid = $('#flow-analysis-rules-table').data('gridInstance');
+            var flowAnalysisRuleData = flowAnalysisRuleGrid.getData();
+            flowAnalysisRuleData.addItem($.extend({
+                type: 'FlowAnalysisRule',
+                bulletins: []
+            }, flowAnalysisRuleEntity));
+
+            // resort
+            flowAnalysisRuleData.reSort();
+            flowAnalysisRuleGrid.invalidate();
+
+            // select the new flow analysis rule
+            var row = flowAnalysisRuleData.getRowById(flowAnalysisRuleEntity.id);
+            nfFilteredDialogCommon.choseRow(flowAnalysisRuleGrid, row);
+            flowAnalysisRuleGrid.scrollRowIntoView(row);
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        // hide the dialog
+        $('#new-flow-analysis-rule-dialog').modal('hide');
+
+        return addRule;
+    };
+
+    /**
+     * Initializes the new flow analysis rule dialog.
+     */
+    var initNewFlowAnalysisRuleDialog = function () {
+        // initialize the flow analysis rule type table
+        var flowAnalysisRuleTypesColumns = [
+            {
+                id: 'type',
+                name: 'Type',
+                field: 'label',
+                formatter: nfCommon.typeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'tags',
+                name: 'Tags',
+                field: 'tags',
+                sortable: true,
+                resizable: true,
+                formatter: nfCommon.genericValueFormatter
+            }
+        ];
+
+        // initialize the dataview
+        var flowAnalysisRuleTypesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        flowAnalysisRuleTypesData.setItems([]);
+        flowAnalysisRuleTypesData.setFilterArgs({
+            searchString: getFlowAnalysisRuleTypeFilterText()
+        });
+        flowAnalysisRuleTypesData.setFilter(filterFlowAnalysisRuleTypes);
+
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, flowAnalysisRuleTypesData);
+
+        // initialize the grid
+        var flowAnalysisRuleTypesGrid = new Slick.Grid('#flow-analysis-rule-types-table', flowAnalysisRuleTypesData, flowAnalysisRuleTypesColumns, gridOptions);
+        flowAnalysisRuleTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+        flowAnalysisRuleTypesGrid.registerPlugin(new Slick.AutoTooltips());
+        flowAnalysisRuleTypesGrid.setSortColumn('type', true);
+        flowAnalysisRuleTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, flowAnalysisRuleTypesData);
+        });
+        flowAnalysisRuleTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+            if ($.isArray(args.rows) && args.rows.length === 1) {
+                var flowAnalysisRuleTypeIndex = args.rows[0];
+                var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(flowAnalysisRuleTypeIndex);
+
+                // set the flow analysis rule type description
+                if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleType)) {
+                    // show the selected flow analysis rule
+                    $('#flow-analysis-rule-description-container').show();
+
+                    if (nfCommon.isBlank(flowAnalysisRuleType.description)) {
+                        $('#flow-analysis-rule-type-description')
+                            .attr('title', '')
+                            .html('<span class="unset">No description specified</span>');
+                    } else {
+                        $('#flow-analysis-rule-type-description')
+                            .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                            .html(flowAnalysisRuleType.description)
+                            .ellipsis();
+                    }
+
+                    var bundle = nfCommon.formatBundle(flowAnalysisRuleType.bundle);
+                    var type = nfCommon.formatType(flowAnalysisRuleType);
+
+                    // populate the dom
+                    $('#flow-analysis-rule-type-name').text(type).attr('title', type);
+                    $('#flow-analysis-rule-type-bundle').text(bundle).attr('title', bundle);
+                    $('#selected-flow-analysis-rule-name').text(flowAnalysisRuleType.label);
+                    $('#selected-flow-analysis-rule-type').text(flowAnalysisRuleType.type).data('bundle', flowAnalysisRuleType.bundle);
+
+                    // refresh the buttons based on the current selection
+                    $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+                }
+            }
+        });
+        flowAnalysisRuleTypesGrid.onDblClick.subscribe(function (e, args) {
+            var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(args.row);
+
+            if (isSelectable(flowAnalysisRuleType)) {
+                addFlowAnalysisRule(flowAnalysisRuleType.type, flowAnalysisRuleType.bundle);
+            }
+        });
+        flowAnalysisRuleTypesGrid.onViewportChanged.subscribe(function (e, args) {
+            nfCommon.cleanUpTooltips($('#flow-analysis-rule-types-table'), 'div.view-usage-restriction');
+        });
+
+        // wire up the dataview to the grid
+        flowAnalysisRuleTypesData.onRowCountChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.updateRowCount();
+            flowAnalysisRuleTypesGrid.render();
+
+            // update the total number of displayed processors
+            $('#displayed-flow-analysis-rule-types').text(args.current);
+        });
+        flowAnalysisRuleTypesData.onRowsChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.invalidateRows(args.rows);
+            flowAnalysisRuleTypesGrid.render();
+        });
+        flowAnalysisRuleTypesData.syncGridSelection(flowAnalysisRuleTypesGrid, true);
+
+        // hold onto an instance of the grid
+        $('#flow-analysis-rule-types-table').data('gridInstance', flowAnalysisRuleTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var usageRestriction = $(this).find('div.view-usage-restriction');
+            if (usageRestriction.length && !usageRestriction.data('qtip')) {
+                var rowId = $(this).find('span.row-id').text();
+
+                // get the status item
+                var item = flowAnalysisRuleTypesData.getItemById(rowId);
+
+                // show the tooltip
+                if (item.restricted === true) {
+                    var restrictionTip = $('<div></div>');
+
+                    if (nfCommon.isBlank(item.usageRestriction)) {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+                    } else {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+                    }
+
+                    var restrictions = [];
+                    if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+                        $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+                            restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+                        });
+                    } else {
+                        restrictions.push('Access to restricted components regardless of restrictions.');
+                    }
+                    restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+                    usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+                        content: restrictionTip,
+                        position: {
+                            container: $('#summary'),
+                            at: 'bottom right',
+                            my: 'top left',
+                            adjust: {
+                                x: 4,
+                                y: 4
+                            }
+                        }
+                    }));
+                }
+            }
+        });
+
+        var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+        // load the available flow analysis rules
+        $.ajax({
+            type: 'GET',
+            url: config.urls.flowAnalysisRuleTypes,
+            dataType: 'json'
+        }).done(function (response) {
+            var id = 0;
+            var tags = [];
+            var groups = d3.set();
+            var restrictedUsage = d3.map();
+            var requiredPermissions = d3.map();
+
+            // begin the update
+            flowAnalysisRuleTypesData.beginUpdate();
+
+            // go through each flow analysis rule type
+            $.each(response.flowAnalysisRuleTypes, function (i, documentedType) {
+                if (documentedType.restricted === true) {
+                    if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+                        $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+
+                            // update required permissions
+                            if (!requiredPermissions.has(requiredPermission.id)) {
+                                requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+                            }
+
+                            // update component restrictions
+                            if (!restrictedUsage.has(requiredPermission.id)) {
+                                restrictedUsage.set(requiredPermission.id, []);
+                            }
+
+                            restrictedUsage.get(requiredPermission.id).push({
+                                type: nfCommon.formatType(documentedType),
+                                bundle: nfCommon.formatBundle(documentedType.bundle),
+                                explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+                            })
+                        });
+                    } else {
+                        // update required permissions
+                        if (!requiredPermissions.has(generalRestriction.value)) {
+                            requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+                        }
+
+                        // update component restrictions
+                        if (!restrictedUsage.has(generalRestriction.value)) {
+                            restrictedUsage.set(generalRestriction.value, []);
+                        }
+
+                        restrictedUsage.get(generalRestriction.value).push({
+                            type: nfCommon.formatType(documentedType),
+                            bundle: nfCommon.formatBundle(documentedType.bundle),
+                            explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+                        });
+                    }
+                }
+
+                // record the group
+                groups.add(documentedType.bundle.group);
+
+                // add the documented type
+                flowAnalysisRuleTypesData.addItem({
+                    id: id++,
+                    label: nfCommon.substringAfterLast(documentedType.type, '.'),
+                    type: documentedType.type,
+                    bundle: documentedType.bundle,
+                    description: nfCommon.escapeHtml(documentedType.description),
+                    restricted:  documentedType.restricted,
+                    usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+                    explicitRestrictions: documentedType.explicitRestrictions,
+                    tags: documentedType.tags.join(', ')
+                });
+
+                // count the frequency of each tag for this type
+                $.each(documentedType.tags, function (i, tag) {
+                    tags.push(tag.toLowerCase());
+                });
+            });
+
+            // end the update
+            flowAnalysisRuleTypesData.endUpdate();
+
+            // resort
+            flowAnalysisRuleTypesData.reSort();
+            flowAnalysisRuleTypesGrid.invalidate();
+
+            // set the component restrictions and the corresponding required permissions
+            nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+            // set the total number of processors
+            $('#total-flow-analysis-rule-types, #displayed-flow-analysis-rule-types').text(response.flowAnalysisRuleTypes.length);
+
+            // create the tag cloud
+            $('#flow-analysis-rule-tag-cloud').tagcloud({
+                tags: tags,
+                select: applyFlowAnalysisRuleTypeFilter,
+                remove: applyFlowAnalysisRuleTypeFilter
+            });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.each(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#flow-analysis-rule-bundle-group-combo').combo({
+                options: options,
+                select: applyFlowAnalysisRuleTypeFilter
+            });
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+        // define the function for filtering the list
+        $('#flow-analysis-rule-type-filter').off('keyup').on('keyup', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+
+            // ignore navigation keys
+            if ($.inArray(code, navigationKeys) !== -1) {
+                return;
+            }
+
+            if (code === $.ui.keyCode.ENTER) {
+                var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                if (selected.length > 0) {
+                    // grid configured with multi-select = false
+                    var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                    if (isSelectable(item)) {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            } else {
+                applyFlowAnalysisRuleTypeFilter();
+            }
+        });
+
+        // setup row navigation
+        nfFilteredDialogCommon.addKeydownListener('#flow-analysis-rule-type-filter', flowAnalysisRuleTypesGrid, flowAnalysisRuleTypesGrid.getData());
+
+        // initialize the flow analysis rule dialog
+        $('#new-flow-analysis-rule-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Add Flow Analysis Rule',
+            buttons: [{
+                buttonText: 'Add',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                    if (selected.length > 0) {
+                        // grid configured with multi-select = false
+                        var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                        return isSelectable(item) === false;
+                    } else {
+                        return flowAnalysisRuleTypesGrid.getData().getLength() === 0;
+                    }
+                },
+                handler: {
+                    click: function () {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            },
+                {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+            handler: {
+                close: function () {
+                    // clear the selected row
+                    clearSelectedFlowAnalysisRule();
+
+                    // clear any filter strings
+                    $('#flow-analysis-rule-type-filter').val('');
+
+                    // clear the tagcloud
+                    $('#flow-analysis-rule-tag-cloud').tagcloud('clearSelectedTags');
+
+                    // reset the group combo
+                    $('#flow-analysis-rule-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
+                    // reset the filter
+                    applyFlowAnalysisRuleTypeFilter();
+
+                    // unselect any current selection
+                    var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+                    flowAnalysisRuleTypesGrid.setSelectedRows([]);
+                    flowAnalysisRuleTypesGrid.resetActiveCell();
+                },
+                resize: function () {
+                    $('#flow-analysis-rule-type-description')
+                        .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                        .text($('#flow-analysis-rule-type-description').attr('title'))
+                        .ellipsis();
+                }
+            }
+        });
+
+        // initialize the registry configuration dialog
+        $('#registry-configuration-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            handler: {
+                close: function () {
+                    $('#registry-id').text('');
+                    $('#registry-name').val('');
+                    $('#registry-location').val('');
+                    $('#registry-description').val('');
+                }
+            }
+        });
+    };
+
+    /**
+     * Initializes the flow analysis rules tab.
+     */
+    var initFlowAnalysisRules = function () {
+        // initialize the new flow analysis rule dialog
+        initNewFlowAnalysisRuleDialog();
+
+        var moreFlowAnalysisRuleDetails = function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            var markup = '<div title="View Details" class="pointer view-flow-analysis-rule fa fa-info-circle"></div>';
+
+            // always include a button to view the usage
+            markup += '<div title="Usage" class="pointer flow-analysis-rule-usage fa fa-book"></div>';
+
+            var hasErrors = !nfCommon.isEmpty(dataContext.component.validationErrors);
+            var hasBulletins = !nfCommon.isEmpty(dataContext.bulletins);
+
+            if (hasErrors) {
+                markup += '<div class="pointer has-errors fa fa-warning" ></div>';
+            }
+
+            if (hasBulletins) {
+                markup += '<div class="has-bulletins fa fa-sticky-note-o"></div>';
+            }
+
+            if (hasErrors || hasBulletins) {
+                markup += '<span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.component.id) + '</span>';
+            }
+
+            return markup;
+        };
+
+        var flowAnalysisRuleRunStatusFormatter = function (row, cell, value, columnDef, dataContext) {
+            var icon = '', label = '';
+            if (dataContext.status.validationStatus === 'VALIDATING') {
+                icon = 'validating fa fa-spin fa-circle-notch';
+                label = 'Validating';
+            } else if (dataContext.status.validationStatus === 'INVALID') {
+                icon = 'invalid fa fa-warning';
+                label = 'Invalid';
+            } else {
+                if (dataContext.status.runStatus === 'DISABLED') {
+                    icon = 'disabled icon icon-enable-false"';
+                    label = 'Disabled';
+                } else if (dataContext.status.runStatus === 'ENABLED') {
+                    icon = 'enabled fa fa-flash';
+                    label = 'Enabled';
+                }
+            }
+
+            // format the markup
+            var formattedValue = '<div layout="row"><div class="' + icon + '"></div>';
+            return formattedValue + '<div class="status-text">' + label + '</div></div>';
+        };
+
+        var flowAnalysisRuleActionFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            var canWrite = dataContext.permissions.canWrite;
+            var canRead = dataContext.permissions.canRead;
+            var canOperate = canWrite || (dataContext.operatePermissions && dataContext.operatePermissions.canWrite);
+
+            var isDisabled = dataContext.status.runStatus === 'DISABLED';
+
+            if (canRead) {
+                if (canWrite && isDisabled) {
+                    markup += '<div class="pointer edit-flow-analysis-rule fa fa-gear" title="Configure"></div>';
+                } else {
+                    markup += '<div class="pointer view-flow-analysis-rule fa fa-gear" title="View Configuration"></div>';
+                }
+            }
+
+            if (canOperate) {
+                if (dataContext.status.runStatus === 'ENABLED') {
+                    markup += '<div class="pointer disable-flow-analysis-rule icon icon-enable-false" title="Disable"></div>';
+                } else if (isDisabled && dataContext.status.validationStatus === 'VALID') {
+                    markup += '<div class="pointer enable-flow-analysis-rule fa fa-flash" title="Enable"></div>';
+                }
+            }
+
+            if (isDisabled && canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) {
+                markup += '<div title="Change Version" class="pointer change-version-flow-analysis-rule fa fa-exchange"></div>';
+            }
+
+            if (isDisabled && canRead && canWrite && nfCommon.canModifyController()) {
+                markup += '<div title="Remove" class="pointer delete-flow-analysis-rule fa fa-trash"></div>';
+            }
+
+            if (canRead && canWrite && dataContext.component.persistsState === true) {
+                markup += '<div title="View State" class="pointer view-state-flow-analysis-rule fa fa-tasks"></div>';

Review Comment:
   Do Rules persist State?



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js:
##########
@@ -1925,6 +1931,885 @@
         });
     };
 
+    /**
+     * Get the text out of the filter field. If the filter field doesn't
+     * have any text it will contain the text 'filter list' so this method
+     * accounts for that.
+     */
+    var getFlowAnalysisRuleTypeFilterText = function () {
+        return $('#flow-analysis-rule-type-filter').val();
+    };
+
+    /**
+     * Filters the flow analysis rule type table.
+     */
+    var applyFlowAnalysisRuleTypeFilter = function () {
+        // get the dataview
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+
+        // ensure the grid has been initialized
+        if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleTypesGrid)) {
+            var flowAnalysisRuleTypesData = flowAnalysisRuleTypesGrid.getData();
+
+            // update the search criteria
+            flowAnalysisRuleTypesData.setFilterArgs({
+                searchString: getFlowAnalysisRuleTypeFilterText()
+            });
+            flowAnalysisRuleTypesData.refresh();
+
+            // update the buttons to possibly trigger the disabled state
+            $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+
+            // update the selection if possible
+            if (flowAnalysisRuleTypesData.getLength() > 0) {
+                nfFilteredDialogCommon.choseFirstRow(flowAnalysisRuleTypesGrid);
+            }
+        }
+    };
+
+    /**
+     * Hides the selected flow analysis rule.
+     */
+    var clearSelectedFlowAnalysisRule = function () {
+        $('#flow-analysis-rule-type-description').attr('title', '').text('');
+        $('#flow-analysis-rule-type-name').attr('title', '').text('');
+        $('#flow-analysis-rule-type-bundle').attr('title', '').text('');
+        $('#selected-flow-analysis-rule-name').text('');
+        $('#selected-flow-analysis-rule-type').text('').removeData('bundle');
+        $('#flow-analysis-rule-description-container').hide();
+    };
+
+    /**
+     * Clears the selected flow analysis rule type.
+     */
+    var clearFlowAnalysisRuleSelection = function () {
+        // clear the selected row
+        clearSelectedFlowAnalysisRule();
+
+        // clear the active cell the it can be reselected when its included
+        var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+        flowAnalysisRuleTypesGrid.resetActiveCell();
+    };
+
+    /**
+     * Performs the filtering.
+     *
+     * @param {object} item     The item subject to filtering
+     * @param {object} args     Filter arguments
+     * @returns {Boolean}       Whether or not to include the item
+     */
+    var filterFlowAnalysisRuleTypes = function (item, args) {
+        // determine if the item matches the filter
+        var matchesFilter = matchesRegex(item, args);
+
+        // determine if the row matches the selected tags
+        var matchesTags = true;
+        if (matchesFilter) {
+            var tagFilters = $('#flow-analysis-rule-tag-cloud').tagcloud('getSelectedTags');
+            var hasSelectedTags = tagFilters.length > 0;
+            if (hasSelectedTags) {
+                matchesTags = matchesSelectedTags(tagFilters, item['tags']);
+            }
+        }
+
+        // determine if the row matches the selected source group
+        var matchesGroup = true;
+        if (matchesFilter && matchesTags) {
+            var bundleGroup = $('#flow-analysis-rule-bundle-group-combo').combo('getSelectedOption');
+            if (nfCommon.isDefinedAndNotNull(bundleGroup) && bundleGroup.value !== '') {
+                matchesGroup = (item.bundle.group === bundleGroup.value);
+            }
+        }
+
+        // determine if this row should be visible
+        var matches = matchesFilter && matchesTags && matchesGroup;
+
+        // if this row is currently selected and its being filtered
+        if (matches === false && $('#selected-flow-analysis-rule-type').text() === item['type']) {
+            clearFlowAnalysisRuleSelection();
+        }
+
+        return matches;
+    };
+
+    /**
+     * Adds the currently selected flow analysis rule.
+     */
+    var addSelectedFlowAnalysisRule = function () {
+        var selectedTaskType = $('#selected-flow-analysis-rule-type').text();
+        var selectedTaskBundle = $('#selected-flow-analysis-rule-type').data('bundle');
+
+        // ensure something was selected
+        if (selectedTaskType === '') {
+            nfDialog.showOkDialog({
+                headerText: 'Settings',
+                dialogContent: 'The type of flow analysis rule to create must be selected.'
+            });
+        } else {
+            addFlowAnalysisRule(selectedTaskType, selectedTaskBundle);
+        }
+    };
+
+    /**
+     * Adds a new flow analysis rule of the specified type.
+     *
+     * @param {string} flowAnalysisRuleType
+     * @param {object} flowAnalysisRuleBundle
+     */
+    var addFlowAnalysisRule = function (flowAnalysisRuleType, flowAnalysisRuleBundle) {
+        // build the flow analysis rule entity
+        var flowAnalysisRuleEntity = {
+            'revision': nfClient.getRevision({
+                'revision': {
+                    'version': 0
+                }
+            }),
+            'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+            'component': {
+                'type': flowAnalysisRuleType,
+                'bundle': flowAnalysisRuleBundle
+            }
+        };
+
+        // add the new flow analysis rule
+        var addRule = $.ajax({
+            type: 'POST',
+            url: config.urls.createFlowAnalysisRule,
+            data: JSON.stringify(flowAnalysisRuleEntity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (flowAnalysisRuleEntity) {
+            // add the item
+            var flowAnalysisRuleGrid = $('#flow-analysis-rules-table').data('gridInstance');
+            var flowAnalysisRuleData = flowAnalysisRuleGrid.getData();
+            flowAnalysisRuleData.addItem($.extend({
+                type: 'FlowAnalysisRule',
+                bulletins: []
+            }, flowAnalysisRuleEntity));
+
+            // resort
+            flowAnalysisRuleData.reSort();
+            flowAnalysisRuleGrid.invalidate();
+
+            // select the new flow analysis rule
+            var row = flowAnalysisRuleData.getRowById(flowAnalysisRuleEntity.id);
+            nfFilteredDialogCommon.choseRow(flowAnalysisRuleGrid, row);
+            flowAnalysisRuleGrid.scrollRowIntoView(row);
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        // hide the dialog
+        $('#new-flow-analysis-rule-dialog').modal('hide');
+
+        return addRule;
+    };
+
+    /**
+     * Initializes the new flow analysis rule dialog.
+     */
+    var initNewFlowAnalysisRuleDialog = function () {
+        // initialize the flow analysis rule type table
+        var flowAnalysisRuleTypesColumns = [
+            {
+                id: 'type',
+                name: 'Type',
+                field: 'label',
+                formatter: nfCommon.typeFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'version',
+                name: 'Version',
+                field: 'version',
+                formatter: nfCommon.typeVersionFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'tags',
+                name: 'Tags',
+                field: 'tags',
+                sortable: true,
+                resizable: true,
+                formatter: nfCommon.genericValueFormatter
+            }
+        ];
+
+        // initialize the dataview
+        var flowAnalysisRuleTypesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        flowAnalysisRuleTypesData.setItems([]);
+        flowAnalysisRuleTypesData.setFilterArgs({
+            searchString: getFlowAnalysisRuleTypeFilterText()
+        });
+        flowAnalysisRuleTypesData.setFilter(filterFlowAnalysisRuleTypes);
+
+        // initialize the sort
+        nfCommon.sortType({
+            columnId: 'type',
+            sortAsc: true
+        }, flowAnalysisRuleTypesData);
+
+        // initialize the grid
+        var flowAnalysisRuleTypesGrid = new Slick.Grid('#flow-analysis-rule-types-table', flowAnalysisRuleTypesData, flowAnalysisRuleTypesColumns, gridOptions);
+        flowAnalysisRuleTypesGrid.setSelectionModel(new Slick.RowSelectionModel());
+        flowAnalysisRuleTypesGrid.registerPlugin(new Slick.AutoTooltips());
+        flowAnalysisRuleTypesGrid.setSortColumn('type', true);
+        flowAnalysisRuleTypesGrid.onSort.subscribe(function (e, args) {
+            nfCommon.sortType({
+                columnId: args.sortCol.field,
+                sortAsc: args.sortAsc
+            }, flowAnalysisRuleTypesData);
+        });
+        flowAnalysisRuleTypesGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+            if ($.isArray(args.rows) && args.rows.length === 1) {
+                var flowAnalysisRuleTypeIndex = args.rows[0];
+                var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(flowAnalysisRuleTypeIndex);
+
+                // set the flow analysis rule type description
+                if (nfCommon.isDefinedAndNotNull(flowAnalysisRuleType)) {
+                    // show the selected flow analysis rule
+                    $('#flow-analysis-rule-description-container').show();
+
+                    if (nfCommon.isBlank(flowAnalysisRuleType.description)) {
+                        $('#flow-analysis-rule-type-description')
+                            .attr('title', '')
+                            .html('<span class="unset">No description specified</span>');
+                    } else {
+                        $('#flow-analysis-rule-type-description')
+                            .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                            .html(flowAnalysisRuleType.description)
+                            .ellipsis();
+                    }
+
+                    var bundle = nfCommon.formatBundle(flowAnalysisRuleType.bundle);
+                    var type = nfCommon.formatType(flowAnalysisRuleType);
+
+                    // populate the dom
+                    $('#flow-analysis-rule-type-name').text(type).attr('title', type);
+                    $('#flow-analysis-rule-type-bundle').text(bundle).attr('title', bundle);
+                    $('#selected-flow-analysis-rule-name').text(flowAnalysisRuleType.label);
+                    $('#selected-flow-analysis-rule-type').text(flowAnalysisRuleType.type).data('bundle', flowAnalysisRuleType.bundle);
+
+                    // refresh the buttons based on the current selection
+                    $('#new-flow-analysis-rule-dialog').modal('refreshButtons');
+                }
+            }
+        });
+        flowAnalysisRuleTypesGrid.onDblClick.subscribe(function (e, args) {
+            var flowAnalysisRuleType = flowAnalysisRuleTypesGrid.getDataItem(args.row);
+
+            if (isSelectable(flowAnalysisRuleType)) {
+                addFlowAnalysisRule(flowAnalysisRuleType.type, flowAnalysisRuleType.bundle);
+            }
+        });
+        flowAnalysisRuleTypesGrid.onViewportChanged.subscribe(function (e, args) {
+            nfCommon.cleanUpTooltips($('#flow-analysis-rule-types-table'), 'div.view-usage-restriction');
+        });
+
+        // wire up the dataview to the grid
+        flowAnalysisRuleTypesData.onRowCountChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.updateRowCount();
+            flowAnalysisRuleTypesGrid.render();
+
+            // update the total number of displayed processors
+            $('#displayed-flow-analysis-rule-types').text(args.current);
+        });
+        flowAnalysisRuleTypesData.onRowsChanged.subscribe(function (e, args) {
+            flowAnalysisRuleTypesGrid.invalidateRows(args.rows);
+            flowAnalysisRuleTypesGrid.render();
+        });
+        flowAnalysisRuleTypesData.syncGridSelection(flowAnalysisRuleTypesGrid, true);
+
+        // hold onto an instance of the grid
+        $('#flow-analysis-rule-types-table').data('gridInstance', flowAnalysisRuleTypesGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var usageRestriction = $(this).find('div.view-usage-restriction');
+            if (usageRestriction.length && !usageRestriction.data('qtip')) {
+                var rowId = $(this).find('span.row-id').text();
+
+                // get the status item
+                var item = flowAnalysisRuleTypesData.getItemById(rowId);
+
+                // show the tooltip
+                if (item.restricted === true) {
+                    var restrictionTip = $('<div></div>');
+
+                    if (nfCommon.isBlank(item.usageRestriction)) {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text('Requires the following permissions:'));
+                    } else {
+                        restrictionTip.append($('<p style="margin-bottom: 3px;"></p>').text(item.usageRestriction + ' Requires the following permissions:'));
+                    }
+
+                    var restrictions = [];
+                    if (nfCommon.isDefinedAndNotNull(item.explicitRestrictions)) {
+                        $.each(item.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+                            restrictions.push("'" + requiredPermission.label + "' - " + nfCommon.escapeHtml(explicitRestriction.explanation));
+                        });
+                    } else {
+                        restrictions.push('Access to restricted components regardless of restrictions.');
+                    }
+                    restrictionTip.append(nfCommon.formatUnorderedList(restrictions));
+
+                    usageRestriction.qtip($.extend({}, nfCommon.config.tooltipConfig, {
+                        content: restrictionTip,
+                        position: {
+                            container: $('#summary'),
+                            at: 'bottom right',
+                            my: 'top left',
+                            adjust: {
+                                x: 4,
+                                y: 4
+                            }
+                        }
+                    }));
+                }
+            }
+        });
+
+        var generalRestriction = nfCommon.getPolicyTypeListing('restricted-components');
+
+        // load the available flow analysis rules
+        $.ajax({
+            type: 'GET',
+            url: config.urls.flowAnalysisRuleTypes,
+            dataType: 'json'
+        }).done(function (response) {
+            var id = 0;
+            var tags = [];
+            var groups = d3.set();
+            var restrictedUsage = d3.map();
+            var requiredPermissions = d3.map();
+
+            // begin the update
+            flowAnalysisRuleTypesData.beginUpdate();
+
+            // go through each flow analysis rule type
+            $.each(response.flowAnalysisRuleTypes, function (i, documentedType) {
+                if (documentedType.restricted === true) {
+                    if (nfCommon.isDefinedAndNotNull(documentedType.explicitRestrictions)) {
+                        $.each(documentedType.explicitRestrictions, function (_, explicitRestriction) {
+                            var requiredPermission = explicitRestriction.requiredPermission;
+
+                            // update required permissions
+                            if (!requiredPermissions.has(requiredPermission.id)) {
+                                requiredPermissions.set(requiredPermission.id, requiredPermission.label);
+                            }
+
+                            // update component restrictions
+                            if (!restrictedUsage.has(requiredPermission.id)) {
+                                restrictedUsage.set(requiredPermission.id, []);
+                            }
+
+                            restrictedUsage.get(requiredPermission.id).push({
+                                type: nfCommon.formatType(documentedType),
+                                bundle: nfCommon.formatBundle(documentedType.bundle),
+                                explanation: nfCommon.escapeHtml(explicitRestriction.explanation)
+                            })
+                        });
+                    } else {
+                        // update required permissions
+                        if (!requiredPermissions.has(generalRestriction.value)) {
+                            requiredPermissions.set(generalRestriction.value, generalRestriction.text);
+                        }
+
+                        // update component restrictions
+                        if (!restrictedUsage.has(generalRestriction.value)) {
+                            restrictedUsage.set(generalRestriction.value, []);
+                        }
+
+                        restrictedUsage.get(generalRestriction.value).push({
+                            type: nfCommon.formatType(documentedType),
+                            bundle: nfCommon.formatBundle(documentedType.bundle),
+                            explanation: nfCommon.escapeHtml(documentedType.usageRestriction)
+                        });
+                    }
+                }
+
+                // record the group
+                groups.add(documentedType.bundle.group);
+
+                // add the documented type
+                flowAnalysisRuleTypesData.addItem({
+                    id: id++,
+                    label: nfCommon.substringAfterLast(documentedType.type, '.'),
+                    type: documentedType.type,
+                    bundle: documentedType.bundle,
+                    description: nfCommon.escapeHtml(documentedType.description),
+                    restricted:  documentedType.restricted,
+                    usageRestriction: nfCommon.escapeHtml(documentedType.usageRestriction),
+                    explicitRestrictions: documentedType.explicitRestrictions,
+                    tags: documentedType.tags.join(', ')
+                });
+
+                // count the frequency of each tag for this type
+                $.each(documentedType.tags, function (i, tag) {
+                    tags.push(tag.toLowerCase());
+                });
+            });
+
+            // end the update
+            flowAnalysisRuleTypesData.endUpdate();
+
+            // resort
+            flowAnalysisRuleTypesData.reSort();
+            flowAnalysisRuleTypesGrid.invalidate();
+
+            // set the component restrictions and the corresponding required permissions
+            nfCanvasUtils.addComponentRestrictions(restrictedUsage, requiredPermissions);
+
+            // set the total number of processors
+            $('#total-flow-analysis-rule-types, #displayed-flow-analysis-rule-types').text(response.flowAnalysisRuleTypes.length);
+
+            // create the tag cloud
+            $('#flow-analysis-rule-tag-cloud').tagcloud({
+                tags: tags,
+                select: applyFlowAnalysisRuleTypeFilter,
+                remove: applyFlowAnalysisRuleTypeFilter
+            });
+
+            // build the combo options
+            var options = [{
+                text: 'all groups',
+                value: ''
+            }];
+            groups.each(function (group) {
+                options.push({
+                    text: group,
+                    value: group
+                });
+            });
+
+            // initialize the bundle group combo
+            $('#flow-analysis-rule-bundle-group-combo').combo({
+                options: options,
+                select: applyFlowAnalysisRuleTypeFilter
+            });
+        }).fail(nfErrorHandler.handleAjaxError);
+
+        var navigationKeys = [$.ui.keyCode.UP, $.ui.keyCode.PAGE_UP, $.ui.keyCode.DOWN, $.ui.keyCode.PAGE_DOWN];
+
+        // define the function for filtering the list
+        $('#flow-analysis-rule-type-filter').off('keyup').on('keyup', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+
+            // ignore navigation keys
+            if ($.inArray(code, navigationKeys) !== -1) {
+                return;
+            }
+
+            if (code === $.ui.keyCode.ENTER) {
+                var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                if (selected.length > 0) {
+                    // grid configured with multi-select = false
+                    var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                    if (isSelectable(item)) {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            } else {
+                applyFlowAnalysisRuleTypeFilter();
+            }
+        });
+
+        // setup row navigation
+        nfFilteredDialogCommon.addKeydownListener('#flow-analysis-rule-type-filter', flowAnalysisRuleTypesGrid, flowAnalysisRuleTypesGrid.getData());
+
+        // initialize the flow analysis rule dialog
+        $('#new-flow-analysis-rule-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Add Flow Analysis Rule',
+            buttons: [{
+                buttonText: 'Add',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var selected = flowAnalysisRuleTypesGrid.getSelectedRows();
+
+                    if (selected.length > 0) {
+                        // grid configured with multi-select = false
+                        var item = flowAnalysisRuleTypesGrid.getDataItem(selected[0]);
+                        return isSelectable(item) === false;
+                    } else {
+                        return flowAnalysisRuleTypesGrid.getData().getLength() === 0;
+                    }
+                },
+                handler: {
+                    click: function () {
+                        addSelectedFlowAnalysisRule();
+                    }
+                }
+            },
+                {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+            handler: {
+                close: function () {
+                    // clear the selected row
+                    clearSelectedFlowAnalysisRule();
+
+                    // clear any filter strings
+                    $('#flow-analysis-rule-type-filter').val('');
+
+                    // clear the tagcloud
+                    $('#flow-analysis-rule-tag-cloud').tagcloud('clearSelectedTags');
+
+                    // reset the group combo
+                    $('#flow-analysis-rule-bundle-group-combo').combo('setSelectedOption', {
+                        value: ''
+                    });
+
+                    // reset the filter
+                    applyFlowAnalysisRuleTypeFilter();
+
+                    // unselect any current selection
+                    var flowAnalysisRuleTypesGrid = $('#flow-analysis-rule-types-table').data('gridInstance');
+                    flowAnalysisRuleTypesGrid.setSelectedRows([]);
+                    flowAnalysisRuleTypesGrid.resetActiveCell();
+                },
+                resize: function () {
+                    $('#flow-analysis-rule-type-description')
+                        .width($('#flow-analysis-rule-description-container').innerWidth() - 1)
+                        .text($('#flow-analysis-rule-type-description').attr('title'))
+                        .ellipsis();
+                }
+            }
+        });
+
+        // initialize the registry configuration dialog
+        $('#registry-configuration-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            handler: {
+                close: function () {
+                    $('#registry-id').text('');
+                    $('#registry-name').val('');
+                    $('#registry-location').val('');
+                    $('#registry-description').val('');
+                }
+            }
+        });
+    };
+
+    /**
+     * Initializes the flow analysis rules tab.
+     */
+    var initFlowAnalysisRules = function () {
+        // initialize the new flow analysis rule dialog
+        initNewFlowAnalysisRuleDialog();
+
+        var moreFlowAnalysisRuleDetails = function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            var markup = '<div title="View Details" class="pointer view-flow-analysis-rule fa fa-info-circle"></div>';
+
+            // always include a button to view the usage
+            markup += '<div title="Usage" class="pointer flow-analysis-rule-usage fa fa-book"></div>';
+
+            var hasErrors = !nfCommon.isEmpty(dataContext.component.validationErrors);
+            var hasBulletins = !nfCommon.isEmpty(dataContext.bulletins);
+
+            if (hasErrors) {
+                markup += '<div class="pointer has-errors fa fa-warning" ></div>';
+            }
+
+            if (hasBulletins) {
+                markup += '<div class="has-bulletins fa fa-sticky-note-o"></div>';
+            }
+
+            if (hasErrors || hasBulletins) {
+                markup += '<span class="hidden row-id">' + nfCommon.escapeHtml(dataContext.component.id) + '</span>';
+            }
+
+            return markup;
+        };
+
+        var flowAnalysisRuleRunStatusFormatter = function (row, cell, value, columnDef, dataContext) {
+            var icon = '', label = '';
+            if (dataContext.status.validationStatus === 'VALIDATING') {
+                icon = 'validating fa fa-spin fa-circle-notch';
+                label = 'Validating';
+            } else if (dataContext.status.validationStatus === 'INVALID') {
+                icon = 'invalid fa fa-warning';
+                label = 'Invalid';
+            } else {
+                if (dataContext.status.runStatus === 'DISABLED') {
+                    icon = 'disabled icon icon-enable-false"';
+                    label = 'Disabled';
+                } else if (dataContext.status.runStatus === 'ENABLED') {
+                    icon = 'enabled fa fa-flash';
+                    label = 'Enabled';
+                }
+            }
+
+            // format the markup
+            var formattedValue = '<div layout="row"><div class="' + icon + '"></div>';
+            return formattedValue + '<div class="status-text">' + label + '</div></div>';
+        };
+
+        var flowAnalysisRuleActionFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            var canWrite = dataContext.permissions.canWrite;
+            var canRead = dataContext.permissions.canRead;
+            var canOperate = canWrite || (dataContext.operatePermissions && dataContext.operatePermissions.canWrite);
+
+            var isDisabled = dataContext.status.runStatus === 'DISABLED';
+
+            if (canRead) {
+                if (canWrite && isDisabled) {
+                    markup += '<div class="pointer edit-flow-analysis-rule fa fa-gear" title="Configure"></div>';
+                } else {
+                    markup += '<div class="pointer view-flow-analysis-rule fa fa-gear" title="View Configuration"></div>';
+                }
+            }
+
+            if (canOperate) {
+                if (dataContext.status.runStatus === 'ENABLED') {
+                    markup += '<div class="pointer disable-flow-analysis-rule icon icon-enable-false" title="Disable"></div>';
+                } else if (isDisabled && dataContext.status.validationStatus === 'VALID') {
+                    markup += '<div class="pointer enable-flow-analysis-rule fa fa-flash" title="Enable"></div>';
+                }
+            }
+
+            if (isDisabled && canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) {
+                markup += '<div title="Change Version" class="pointer change-version-flow-analysis-rule fa fa-exchange"></div>';
+            }
+
+            if (isDisabled && canRead && canWrite && nfCommon.canModifyController()) {
+                markup += '<div title="Remove" class="pointer delete-flow-analysis-rule fa fa-trash"></div>';
+            }
+
+            if (canRead && canWrite && dataContext.component.persistsState === true) {
+                markup += '<div title="View State" class="pointer view-state-flow-analysis-rule fa fa-tasks"></div>';
+            }
+
+            // allow policy configuration conditionally
+            if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) {
+                markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key"></div>';

Review Comment:
   Do we need to support managing access policies on a per Rule basis?



-- 
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: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org