You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by GitBox <gi...@apache.org> on 2022/08/26 18:57:40 UTC

[GitHub] [nifi] mcgilman commented on a diff in pull request #5671: NIFI-9514 NIFI-9515: Add UI support for Parameter Providers in Controller Services

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


##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js:
##########
@@ -132,6 +134,27 @@
         return nfCommon.escapeHtml(dataContext.component.name);
     };
 
+    /**
+     * Formatter for the provider column.
+     *
+     * @param {type} row
+     * @param {type} cell
+     * @param {type} value
+     * @param {type} columnDef
+     * @param {type} dataContext
+     * @returns {String}
+     */
+    var providerFormatter = function (row, cell, value, columnDef, dataContext) {
+        if (dataContext.component.parameterProviderConfiguration) {

Review Comment:
   This condition assumes the user `canRead`.  When the user lacks permissions to a given Parameter Context, the `component` will not be provided.



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-provider.js:
##########
@@ -0,0 +1,2631 @@
+/*
+ * 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',
+                'Slick',
+                'nf.ErrorHandler',
+                'nf.Common',
+                'nf.CanvasUtils',
+                'nf.Dialog',
+                'nf.Storage',
+                'nf.Client',
+                'nf.ControllerService',
+                'nf.ControllerServices',
+                'nf.UniversalCapture',
+                'nf.CustomUi',
+                'nf.Verify',
+                'nf.Processor',
+                'nf.ProcessGroup',
+                'nf.ParameterContexts',
+                'nf.ProcessGroupConfiguration'],
+            function ($, Slick, nfErrorHandler, nfCommon, nfCanvasUtils, nfDialog, nfStorage, nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify, nfProcessor, nfProcessGroup, nfParameterContexts, nfProcessGroupConfiguration) {
+                return (nf.ParameterProvider = factory($, Slick, nfErrorHandler, nfCommon, nfCanvasUtils, nfDialog, nfStorage, nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify, nfProcessor, nfProcessGroup, nfParameterContexts, nfProcessGroupConfiguration));
+            });
+    } else if (typeof exports === 'object' && typeof module === 'object') {
+        module.exports = (nf.ParameterProvider =
+            factory(require('jquery'),
+                require('Slick'),
+                require('nf.ErrorHandler'),
+                require('nf.Common'),
+                require('nf.CanvasUtils'),
+                require('nf.Dialog'),
+                require('nf.Storage'),
+                require('nf.Client'),
+                require('nf.ControllerService'),
+                require('nf.ControllerServices'),
+                require('nf.UniversalCapture'),
+                require('nf.CustomUi'),
+                require('nf.Verify'),
+                require('nf.Processor'),
+                require('nf.ProcessGroup'),
+                require('nf.ParameterContexts'),
+                require('nf.ProcessGroupConfiguration')));
+    } else {
+        nf.ParameterProvider = factory(root.$,
+            root.Slick,
+            root.nf.ErrorHandler,
+            root.nf.Common,
+            root.nf.CanvasUtils,
+            root.nf.Dialog,
+            root.nf.Storage,
+            root.nf.Client,
+            root.nf.ControllerService,
+            root.nf.ControllerServices,
+            root.nf.UniversalCapture,
+            root.nf.CustomUi,
+            root.nf.Verify,
+            root.nf.Processor,
+            root.nf.ProcessGroup,
+            root.nf.ParameterContexts,
+            root.nf.ProcessGroupConfiguration);
+    }
+}(this, function ($, Slick, nfErrorHandler, nfCommon, nfCanvasUtils, nfDialog, nfStorage, nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify, nfProcessor, nfProcessGroup, nfParameterContexts, nfProcessGroupConfiguration) {
+    'use strict';
+
+    var nfSettings;
+    var fetchParameterProviderOptions;
+
+    var config = {
+        edit: 'edit',
+        readOnly: 'read-only',
+        urls: {
+            parameterProviders: '../nifi-api/parameter-providers',
+            api: '../nifi-api'
+        }
+    };
+
+    // load the controller services
+    var controllerServicesUri = config.urls.api + '/flow/controller/controller-services';
+
+    var groupCount = 0;
+
+    var parameterCount = 0;
+    var sensitiveParametersArray = [];
+
+    var SENSITIVE = 'SENSITIVE';
+    var NON_SENSITIVE = 'NON_SENSITIVE';
+
+    var parameterGroupsGridOptions = {
+        autosizeColsMode: Slick.GridAutosizeColsMode.LegacyForceFit,
+        enableTextSelectionOnCells: true,
+        enableCellNavigation: true,
+        enableColumnReorder: false,
+        editable: false,
+        enableAddRow: false,
+        autoEdit: false,
+        multiSelect: false,
+        rowHeight: 24
+    };
+
+    var selectableParametersGridOptions = {
+        autosizeColsMode: Slick.GridAutosizeColsMode.LegacyForceFit,
+        enableTextSelectionOnCells: true,
+        enableCellNavigation: true,
+        enableColumnReorder: false,
+        editable: false,
+        enableAddRow: false,
+        autoEdit: false,
+        multiSelect: false,
+        rowHeight: 24,
+        asyncEditorLoading: false
+    };
+
+    // the last submitted referenced attributes
+    var referencedAttributes = null;
+
+    /**
+     * 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 parameter provider configuration
+     * that needs to be saved.
+     */
+    var isSaveRequired = function () {
+        var entity = $('#parameter-provider-configuration').data('parameterProviderDetails');
+
+        // determine if any parameter provider settings have changed
+        if ($('#parameter-provider-name').val() !== entity.component['name']) {
+            return true;
+        }
+        if ($('#parameter-provider-comments').val() !== entity.component['comments']) {
+            return true;
+        }
+
+        // defer to the properties
+        return $('#parameter-provider-properties').propertytable('isSaveRequired');
+    };
+
+
+    /**
+     * Marshals the data that will be used to update the parameter provider's configuration.
+     */
+    var marshalDetails = function () {
+        // properties
+        var properties = $('#parameter-provider-properties').propertytable('marshalProperties');
+
+        // create the parameter provider dto
+        var parameterProviderDto = {};
+        parameterProviderDto['id'] = $('#parameter-provider-id').text();
+        parameterProviderDto['name'] = $('#parameter-provider-name').val();
+        parameterProviderDto['comments'] = $('#parameter-provider-comments').val();
+
+        // set the properties
+        if ($.isEmptyObject(properties) === false) {
+            parameterProviderDto['properties'] = properties;
+        }
+
+        // create the parameter provider entity
+        var parameterProviderEntity = {};
+        parameterProviderEntity['component'] = parameterProviderDto;
+
+        // return the marshaled details
+        return parameterProviderEntity;
+    };
+
+    /**
+     * Marshals the parameter groups in the table.
+     */
+    var marshalParameterGroups = function () {
+        var groups = [];
+        var table = $('#parameter-groups-table');
+        var groupsGrid = table.data('gridInstance');
+        var groupsData = groupsGrid.getData();
+        $.each(groupsData.getItems(), function (_, g) {
+            if (g.isParameterContext || g.createNewParameterContext) {
+                var group = {
+                    'groupName': g.name,
+                    'parameterContextName': g.parameterContextName,
+                    'synchronized': true,
+                    'parameterSensitivities': g.parameterSensitivities
+                }
+
+                groups.push(group);
+            }
+        })
+
+        return groups;
+    };
+
+    /**
+     * Validates the specified details.
+     *
+     * @param providerDetails the parameter provider details to validate
+     * @param originalProviderDetails the original parameter provider details to compare changes
+     * @param existingParametersProviders existing parameter providers to verify there are no duplicates
+     * @return {boolean}
+     */
+    var validateDetails = function (providerDetails, existingParametersProviders, originalProviderDetails) {
+        var parameterProvider = providerDetails['component'];
+
+        if (parameterProvider.name === '') {
+            nfDialog.showOkDialog({
+                headerText: 'Configuration Error',
+                dialogContent: 'The name of the Parameter Provider must be specified.'
+            });
+            return false;
+        }
+
+        // make sure the parameter provider name does not use any unsupported characters
+        var parameterProviderNameRegex = /^[a-zA-Z0-9-_. ]+$/;
+        if (!parameterProviderNameRegex.test(parameterProvider.name)) {
+            nfDialog.showOkDialog({
+                headerText: 'Configuration Error',
+                dialogContent: 'The name of the Parameter Provider appears to have an invalid character or characters. Only alpha-numeric characters (a-z, A-Z, 0-9), hyphens (-), underscores (_), periods (.), and spaces ( ) are accepted.'
+            });
+            return false;
+        }
+
+        // validate the parameter provider is not a duplicate
+        var matchingParameterProvider;
+        var match;
+
+        if (nfCommon.isUndefinedOrNull(matchingParameterProvider) && originalProviderDetails.component.name !== providerDetails.component.name){
+            $.each(existingParametersProviders, function (i, provider) {
+                if (nfCommon.isUndefinedOrNull(match)) {
+                    match = _.find(provider, {name: parameterProvider.name});
+                    if (match) {
+                        matchingParameterProvider = match;
+                    }
+                }
+
+            });
+        }
+
+        if (_.isNil(matchingParameterProvider)) {
+            return true;
+        } else {
+            nfDialog.showOkDialog({
+                headerText: 'Parameter Provider Exists',
+                dialogContent: 'A Parameter Provider with this name already exists.'
+            });
+        }
+        return false;
+    };
+
+    /**
+     * Renders the specified parameter provider.
+     *
+     * @param {object} parameterProviderEntity parameter provider entity
+     */
+    var renderParameterProvider = function (parameterProviderEntity) {
+        // get the table and update the row accordingly
+        var parameterProviderGrid = $('#parameter-providers-table').data('gridInstance');
+        var parameterProviderData = parameterProviderGrid.getData();
+        var currentParameterProvider = parameterProviderData.getItemById(parameterProviderEntity.id);
+        parameterProviderData.updateItem(parameterProviderEntity.id, $.extend({
+            type: 'ParameterProvider',
+            bulletins: currentParameterProvider.bulletins
+        }, parameterProviderEntity));
+    };
+
+    /**
+     * Goes to a service configuration from the property table.
+     */
+    var goToServiceFromProperty = function () {
+        return $.Deferred(function (deferred) {
+            // close all fields currently being edited
+            $('#parameter-provider-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 parameterProvider = $('#parameter-provider-configuration').data('parameterProviderDetails');
+                        saveParameterProvider(parameterProvider).done(function () {
+                            deferred.resolve();
+                        }).fail(function () {
+                            deferred.reject();
+                        });
+                    }
+                });
+            } else {
+                deferred.resolve();
+            }
+        }).promise();
+    };
+
+    /**
+     * Saves the specified parameter provider.
+     *
+     * @param {type} parameterProviderEntity parameter provider entity
+     */
+    var saveParameterProvider = function (parameterProviderEntity) {
+        // save the original provider to detect a name change
+        var originalParameterProvider = parameterProviderEntity;
+
+        // marshal the settings and properties and update the parameter provider
+        var updatedParameterProvider = marshalDetails();
+
+        // ensure details are valid as far as we can tell
+        var parameterProvidersGrid = $('#parameter-providers-table').data('gridInstance');
+        var parameterProvidersData = parameterProvidersGrid.getData();
+
+        if (validateDetails(updatedParameterProvider, parameterProvidersData.getItems(), originalParameterProvider)) {
+            updatedParameterProvider['revision'] = nfClient.getRevision(parameterProviderEntity);
+            updatedParameterProvider['disconnectedNodeAcknowledged'] = nfStorage.isDisconnectionAcknowledged();
+
+            // update the selected component
+            return $.ajax({
+                type: 'PUT',
+                data: JSON.stringify(updatedParameterProvider),
+                url: parameterProviderEntity.uri,
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                // update the parameter provider
+                renderParameterProvider(response);
+            }).fail(nfErrorHandler.handleConfigurationUpdateAjaxError);
+        } else {
+            return $.Deferred(function (deferred) {
+                deferred.reject();
+            }).promise();
+        }
+    };
+
+    /**
+     * Gets a property descriptor for the parameter provider currently being configured.
+     *
+     * @param {type} propertyName property descriptor name
+     * @param {type} sensitive requested sensitive status
+     */
+    var getParameterProviderPropertyDescriptor = function (propertyName, sensitive) {
+        var details = $('#parameter-provider-configuration').data('parameterProviderDetails');
+        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 = $('#parameter-provider-properties-verification-results');
+
+        // expand the dialog to make room for the verification result
+        if (verificationResultsContainer.is(':visible') === false) {
+            // show the verification results
+            $('#parameter-provider-properties').css('bottom', '40%').propertytable('resetTableSize')
+            verificationResultsContainer.show();
+        }
+
+        // show borders if appropriate
+        var verificationResultsListing = $('#parameter-provider-properties-verification-results-listing');
+        if (verificationResultsListing.get(0).scrollHeight > Math.round(verificationResultsListing.innerHeight())) {
+            verificationResultsListing.css('border-width', '1px');
+        }
+    };
+
+    /**
+     * Applies the fetched parameters of a specified Parameter Provider.
+     *
+     * @param parameterProviderEntity
+     * @returns {*}
+     */
+    var applyParametersHandler = function (parameterProviderEntity) {
+        var currentParameterProviderEntity = parameterProviderEntity;
+        var fetchParametersDialog = $('#fetch-parameters-dialog');
+
+        // clean up any tooltips that may have been generated
+        nfCommon.cleanUpTooltips($('#parameter-table'), 'div.fa-question-circle, div.fa-info');
+
+        var groups = marshalParameterGroups();
+        currentParameterProviderEntity.component.parameterGroupConfigurations = groups;
+
+        return $.Deferred(function (deferred) {
+            // updates the button model to show the close button
+            var updateToCloseButtonModel = function () {
+                fetchParametersDialog.modal('setButtonModel', [{
+                    buttonText: 'Close',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            deferred.resolve();
+                            closeModal('#fetch-parameters-dialog');
+                        }
+                    }
+                }]);
+            };
+
+            var updateToApplyOrCancelButtonModel = function () {
+                fetchParametersDialog.modal('setButtonModel', [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            applyParametersHandler(currentParameterProviderEntity).done(function () {
+                                // reload the parameter provider
+                                nfParameterProvider.reload(parameterProviderEntity.id);
+                            });
+                        }
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            deferred.resolve();
+                            confirmCancelDialog('#fetch-parameters-dialog');
+                        }
+                    }
+                }]);
+            };
+
+            var cancelled = false;
+
+            // update the button model to show the cancel button
+            fetchParametersDialog.modal('setButtonModel', [{
+                buttonText: 'Cancel',
+                color: {
+                    base: '#E3E8EB',
+                    hover: '#C7D2D7',
+                    text: '#004849'
+                },
+                handler: {
+                    click: function () {
+                        cancelled = true;
+                        updateToCloseButtonModel();
+                    }
+                }
+            }]);
+
+            var requestId;
+            var handleAjaxFailure = function (xhr, status, error) {
+                // delete the request if possible
+                if (nfCommon.isDefinedAndNotNull(requestId)) {
+                    deleteUpdateRequest(currentParameterProviderEntity.id, requestId);
+                }
+
+                // update the step status
+                $('#fetch-parameters-update-steps')
+                    .find('div.fetch-parameters-step.ajax-loading')
+                    .removeClass('ajax-loading')
+                    .addClass('ajax-error');
+
+                if ($('#affected-referencing-components-container').is(':visible')) {
+                    updateReferencingComponentsBorder($('#affected-referencing-components-container'));
+                }
+
+                // update the button model
+                updateToApplyOrCancelButtonModel();
+            };
+
+            submitUpdateRequest(currentParameterProviderEntity).done(function (response) {
+                var pollUpdateRequest = function (updateRequestEntity) {
+                    var updateRequest = updateRequestEntity.request;
+                    var errored = nfCommon.isDefinedAndNotNull(updateRequest.failureReason);
+
+                    // get the request id
+                    requestId = updateRequest.requestId;
+
+                    // update the affected referencing components
+                    populateAffectedReferencingComponents(updateRequest.referencingComponents);
+
+                    // update the progress/steps
+                    populateFetchParametersUpdateStep(updateRequest.updateSteps, cancelled, errored);
+
+                    // show update steps
+                    $('#fetch-parameters-update-status-container').show();
+
+                    // if this request was cancelled, remove the update request
+                    if (cancelled) {
+                        deleteUpdateRequest(currentParameterProviderEntity.id, requestId);
+                    } else {
+                        if (updateRequest.complete === true) {
+                            if (errored) {
+                                nfDialog.showOkDialog({
+                                    headerText: 'Apply Parameter Provider Error',
+                                    dialogContent: 'Unable to complete parameter provider update request: ' + nfCommon.escapeHtml(updateRequest.failureReason)
+                                });
+                            }
+
+                            // reload referencing processors
+                            $.each(updateRequest.referencingComponents, function (_, referencingComponentEntity) {
+                                if (referencingComponentEntity.permissions.canRead === true) {
+                                    var referencingComponent = referencingComponentEntity.component;
+
+                                    // reload the processor if it's in the current group
+                                    if (referencingComponent.referenceType === 'PROCESSOR' && nfCanvasUtils.getGroupId() === referencingComponent.processGroupId) {
+                                        nfProcessor.reload(referencingComponent.id);
+                                    }
+                                }
+                            });
+
+                            // update the fetch parameter table if displayed
+                            if ($('#fetch-parameters-table').is(':visible')) {
+                                var parameterProviderGrid = $('#fetch-parameters-table').data('gridInstance');
+                                var parameterProviderData = parameterProviderGrid.getData();
+
+                                $.extend(currentParameterProviderEntity, {
+                                    revision: updateRequestEntity.parameterContextRevision,
+                                    component: updateRequestEntity.request.parameterProvider
+                                });
+
+                                var item = parameterProviderData.getItemById(currentParameterProviderEntity.id);
+                                if (nfCommon.isDefinedAndNotNull(item)) {
+                                    parameterProviderData.updateItem(currentParameterProviderEntity.id, currentParameterProviderEntity);
+                                }
+                            }
+
+                            // delete the update request
+                            deleteUpdateRequest(currentParameterProviderEntity.id, requestId);
+
+                            // update the button model
+                            updateToCloseButtonModel();
+
+                            // check if border is necessary
+                            updateReferencingComponentsBorder($('#affected-referencing-components-container'));
+                        } else {
+                            // wait to get an updated status
+                            setTimeout(function () {
+                                getUpdateRequest(currentParameterProviderEntity.id, requestId).done(function (getResponse) {
+                                    pollUpdateRequest(getResponse);
+                                }).fail(handleAjaxFailure);
+                            }, 2000);
+                        }
+                    }
+                };
+
+                // get the parameter provider groups names
+                var parameterProviderGroupNames = [];
+                $.each(response.request.parameterProvider.parameterGroupConfigurations, function (_, parameterProviderGroup) {
+                    parameterProviderGroupNames.push(parameterProviderGroup.groupName);
+                });
+                $('#apply-groups-list')
+                    .removeClass('unset')
+                    .attr('title', parameterProviderGroupNames.join(', '))
+                    .text(parameterProviderGroupNames.join(', '));
+
+                // update the visibility
+                // left column
+                $('#fetch-parameters-usage-container').hide();
+                $('#apply-groups-container').show();
+
+                // middle column
+                $('#parameters-container').hide();
+                $('#fetch-parameters-update-status').show();
+
+                pollUpdateRequest(response);
+            }).fail(handleAjaxFailure);
+        }).promise();
+    };
+
+    /**
+     * Confirms a cancel dialog.
+     */
+    var confirmCancelDialog = function (dialog) {
+        nfDialog.showYesNoDialog({
+            headerText: 'Fetch Parameters',
+            dialogContent: 'Are you sure you want to cancel?',
+            noText: 'Cancel',
+            yesText: 'Yes',
+            yesHandler: function () {
+                closeModal(dialog);
+            }
+        });
+    };
+
+    /**
+     * Shows the dialog to fetch parameters.
+     *
+     * @param {object} parameterProviderEntity parameterProviderEntity
+     * @param {object} fetchParameterProviderOptions fetchParameterProviderOptions
+     */
+    var showFetchParametersDialog = function (parameterProviderEntity, fetchParameterProviderOptions) {
+        updateFetchParametersRequest(parameterProviderEntity).done(function (response) {
+            var updatedParameterProviderEntity = response;
+
+            groupCount = 0;
+            parameterCount = 0;
+
+            // populate the fetch parameters dialog
+            $('#fetch-parameters-id').text(updatedParameterProviderEntity.id);
+            $('#fetch-parameters-name').text(nfCommon.getComponentName(updatedParameterProviderEntity));
+
+            // set parameters contexts to be updated to none
+            $('<div class="parameter-contexts-to-create"><span class="unset">None</span></div>')
+                .appendTo($('#parameter-contexts-to-create-container'));
+
+            // list parameter contexts to update
+            var parameterContextsToUpdate = $('#parameter-contexts-to-update-container').empty();
+
+            if (!updatedParameterProviderEntity.component.referencingParameterContexts) {
+                $('<div class="parameter-contexts-to-update"><span class="unset">None</span></div>')
+                    .appendTo(parameterContextsToUpdate);
+            } else {
+                // populate contexts to be updated
+                var parameterContextNames = [];
+                $.each(updatedParameterProviderEntity.component.referencingParameterContexts, function (_, paramContext) {
+                    parameterContextNames.push(paramContext.component.name);
+                });
+                parameterContextNames.sort();
+                parameterContextsToUpdate
+                    .removeClass('unset')
+                    .attr('title', parameterContextNames.join(', '))
+                    .text(parameterContextNames.join(', '));
+            }
+
+            // hide affected referencing components list
+            var affectedReferencingComponents = $('#fetch-parameters-affected-referencing-components-container');
+            if (!affectedReferencingComponents.hasClass('hidden')) {
+                affectedReferencingComponents.addClass('hidden');
+            }
+
+            loadParameterGroups(updatedParameterProviderEntity);
+
+            // update visibility
+            $('#fetch-parameters-usage-container').show();
+            $('#parameters-container').show();
+
+            // build the button model
+            var buttons = [{
+                buttonText: 'Apply',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var disabled = true;
+
+                    if (!updatedParameterProviderEntity.component.referencingParameterContexts) {
+                        var groupsGrid = $('#parameter-groups-table').data('gridInstance');
+                        var groupsData = groupsGrid.getData();
+
+                        $.each(groupsData.getItems(), function (_, group) {
+                            if (group.createNewParameterContext) {
+                                disabled = false;
+                            }
+                        })
+                    } else {
+                        disabled = false;
+                    }
+
+                    return disabled;
+                },
+                handler: {
+                    click: function () {
+                        applyParametersHandler(updatedParameterProviderEntity).done(function () {
+                            // reload the parameter provider
+                            nfParameterProvider.reload(parameterProviderEntity.id);
+                        });
+                    }
+                }
+            }, {
+                buttonText: 'Cancel',
+                color: {
+                    base: '#E3E8EB',
+                    hover: '#C7D2D7',
+                    text: '#004849'
+                },
+                handler: {
+                    click: function () {
+                        confirmCancelDialog('#fetch-parameters-dialog');
+                    }
+                }
+            }];
+
+            // synchronize the current component canvas attributes in the status bar
+            if (fetchParameterProviderOptions.supportsStatusBar) {
+                // initialize the canvas synchronization
+                if (updatedParameterProviderEntity.bulletins.length !== 0) {
+                    $('#fetch-parameters-status-bar').statusbar(
+                        'observe',
+                        { provider: updatedParameterProviderEntity.bulletins }
+                    );
+                }
+            }
+
+            // show the dialog
+            $('#fetch-parameters-dialog')
+                .modal('setButtonModel', buttons)
+                .modal('show');
+
+            if ($('#fetched-parameters-listing-container').is(':visible')) {
+                updateReferencingComponentsBorder($('#fetched-parameters-listing-container'));
+            }
+        });
+    };
+
+    /**
+     * Loads the specified fetched groups.
+     *
+     * @param {object} parameterProviderGroupEntity
+     * @param {boolean} if the parameters should be displayed in a read-only state regardless of permissions
+     */
+    var loadParameterGroups = function (parameterProviderGroupEntity) {
+        // providedGroups will be an array of groups
+        if (nfCommon.isDefinedAndNotNull(parameterProviderGroupEntity)) {
+            var groupsGrid = $('#parameter-groups-table').data('gridInstance');
+            var groupsData = groupsGrid.getData();
+
+            // begin the update
+            groupsData.beginUpdate();
+
+            var parameterGroups = [];
+            $.each(parameterProviderGroupEntity.component.parameterGroupConfigurations, function (i, groupConfig) {
+                var isReferencingParameterContext = parameterProviderGroupEntity.component.referencingParameterContexts
+                    ? isReferencingParamContext(parameterProviderGroupEntity.component.referencingParameterContexts, groupConfig.parameterContextName)
+                    : false;
+
+                var group = {
+                    id: groupCount++,
+                    hidden: false,
+                    isParameterContext: isReferencingParameterContext,
+                    name: groupConfig.groupName,
+                    parameterContextName: groupConfig.parameterContextName,
+                    parameterSensitivities: groupConfig.parameterSensitivities,
+                    referencingParameterContexts: groupConfig.referencingParameterContexts ? groupConfig.referencingParameterContexts : null
+                };
+
+                parameterGroups.push({
+                    group: group
+                });
+
+                groupsData.addItem(group);
+            });
+
+            // complete the update
+            groupsData.endUpdate();
+            groupsData.reSort();
+
+            // select the first row
+            groupsGrid.setSelectedRows([0]);
+        }
+    };
+
+    /**
+     * Determines if the provided group is synced to a parameter context.
+     *
+     * @param {object} referencingParameterContexts
+     * @param {string} parameterContextName
+     * @returns {boolean}
+     */
+    var isReferencingParamContext = function (referencingParameterContexts, parameterContextName) {
+        var isReferencingParamContext = false;
+        $.each(referencingParameterContexts, function (_, paramContext) {
+            if (paramContext.component.name.includes(parameterContextName)) {
+                return isReferencingParamContext = true;
+            }
+        })
+
+        return isReferencingParamContext;
+    }
+
+    /**
+     * Loads the selectable parameters for a specified parameter group.
+     *
+     * @param groupId
+     * @param {object} parametersEntity
+     */
+    var loadSelectableParameters = function (groupId, parametersEntity) {
+        sensitiveParametersArray = [];
+        if (nfCommon.isDefinedAndNotNull(parametersEntity)) {
+            var selectableParametersGrid = $('#selectable-parameters-table').data('gridInstance');
+            var parametersData = selectableParametersGrid.getData();
+
+            // clear the rows
+            selectableParametersGrid.setSelectedRows([]);
+            parametersData.setItems([]);
+
+            // begin the update
+            parametersData.beginUpdate();
+
+            var idx = 0;
+            for (var param in parametersEntity) {
+
+                var parameter = {
+                    id: idx++,
+                    groupId: groupId,
+                    name: param,
+                    sensitivity: parametersEntity[param] ? parametersEntity[param] : SENSITIVE
+                }
+                // if the parameter is sensitive, then show the parameter checkbox as check-marked
+                if (parameter.sensitivity === SENSITIVE) {
+                    sensitiveParametersArray.push(parameter.id);
+                }
+
+                parametersData.addItem(parameter);
+            }
+
+            // complete the update
+            parametersData.endUpdate();
+            parametersData.reSort();
+
+            // select the sensitive rows
+            if (sensitiveParametersArray.length !== 0) {
+                selectableParametersGrid.setSelectedRows(sensitiveParametersArray);
+            }
+
+            // list the parameters to be created
+            loadParameterContextsToCreate();
+        }
+    };
+
+    /**
+     * Populates the affected referencing components for the specified parameter provider.
+     *
+     * @param {object} referencingComponents
+     */
+    var populateAffectedReferencingComponents = function (referencingComponents) {
+        // toggles the visibility of a container
+        var toggle = function (twist, container) {
+            if (twist.hasClass('expanded')) {
+                twist.removeClass('expanded').addClass('collapsed');
+                container.hide();
+            } else {
+                twist.removeClass('collapsed').addClass('expanded');
+                container.show();
+            }
+        };
+
+        // update visibility
+        if ($('#fetch-parameters-affected-referencing-components-container').hasClass('hidden')) {
+            $('#fetch-parameters-affected-referencing-components-container').removeClass('hidden');
+        }
+
+        var referencingProcessors = [];
+        var referencingControllerServices = [];
+        var unauthorizedReferencingComponents = [];
+
+        var spinner = $('#fetch-parameters-affected-referencing-components-container .referencing-components-loading');
+
+        var loadingDeferred = $.Deferred(function (deferred) {
+            spinner.addClass('ajax-loading');
+            deferred.resolve();
+        });
+        loadingDeferred.then(function () {
+            resetUsage();
+        }).then(function() {
+            var parameterReferencingComponentsContainer = $('#affected-referencing-components-container').empty();
+
+            // referencing component will be undefined when a new parameter is added
+            if (nfCommon.isUndefined(referencingComponents)) {
+                // set to pending
+                $('<div class="referencing-component-container"><span class="unset">Pending Apply</span></div>').appendTo(parameterReferencingComponentsContainer);
+            } else {
+                // bin the referencing components according to their type
+                $.each(referencingComponents, function (_, referencingComponentEntity) {
+                    if (referencingComponentEntity.permissions.canRead === true && referencingComponentEntity.permissions.canWrite === true) {
+                        if (referencingComponentEntity.component.referenceType === 'PROCESSOR') {
+                            referencingProcessors.push(referencingComponentEntity);
+                        } else {
+                            referencingControllerServices.push(referencingComponentEntity);
+                        }
+                    } else {
+                        unauthorizedReferencingComponents.push(referencingComponentEntity);
+                    }
+                });
+
+                var referencingProcessGroups = {};
+
+                // bin the referencing processors according to their PG
+                $.each(referencingProcessors, function (_, referencingProcessorEntity) {
+                    if (referencingProcessGroups[referencingProcessorEntity.processGroup.id]) {
+                        referencingProcessGroups[referencingProcessorEntity.processGroup.id].referencingProcessors.push(referencingProcessorEntity);
+                        referencingProcessGroups[referencingProcessorEntity.processGroup.id].id = referencingProcessorEntity.processGroup.id;
+                        referencingProcessGroups[referencingProcessorEntity.processGroup.id].name = referencingProcessorEntity.processGroup.name;
+                    } else {
+                        referencingProcessGroups[referencingProcessorEntity.processGroup.id] = {
+                            referencingProcessors: [],
+                            referencingControllerServices: [],
+                            unauthorizedReferencingComponents: [],
+                            name: referencingProcessorEntity.processGroup.name,
+                            id: referencingProcessorEntity.processGroup.id
+                        };
+
+                        referencingProcessGroups[referencingProcessorEntity.processGroup.id].referencingProcessors.push(referencingProcessorEntity);
+                    }
+                });
+
+                // bin the referencing CS according to their PG
+                $.each(referencingControllerServices, function (_, referencingControllerServiceEntity) {
+                    if (referencingProcessGroups[referencingControllerServiceEntity.processGroup.id]) {
+                        referencingProcessGroups[referencingControllerServiceEntity.processGroup.id].referencingControllerServices.push(referencingControllerServiceEntity);
+                        referencingProcessGroups[referencingControllerServiceEntity.processGroup.id].id = referencingControllerServiceEntity.processGroup.id;
+                        referencingProcessGroups[referencingControllerServiceEntity.processGroup.id].name = referencingControllerServiceEntity.processGroup.name;
+                    } else {
+                        referencingProcessGroups[referencingControllerServiceEntity.processGroup.id] = {
+                            referencingProcessors: [],
+                            referencingControllerServices: [],
+                            unauthorizedReferencingComponents: [],
+                            name: referencingControllerServiceEntity.processGroup.name,
+                            id: referencingControllerServiceEntity.processGroup.id
+                        };
+
+                        referencingProcessGroups[referencingControllerServiceEntity.processGroup.id].referencingControllerServices.push(referencingControllerServiceEntity);
+                    }
+                });
+
+                // bin the referencing unauthorized components according to their PG
+                $.each(unauthorizedReferencingComponents, function (_, unauthorizedReferencingComponentEntity) {
+                    if (referencingProcessGroups[unauthorizedReferencingComponentEntity.processGroup.id]) {
+                        referencingProcessGroups[unauthorizedReferencingComponentEntity.processGroup.id].unauthorizedReferencingComponents.push(unauthorizedReferencingComponentEntity);
+                        referencingProcessGroups[unauthorizedReferencingComponentEntity.processGroup.id].id = unauthorizedReferencingComponentEntity.processGroup.id;
+                        referencingProcessGroups[unauthorizedReferencingComponentEntity.processGroup.id].name = unauthorizedReferencingComponentEntity.processGroup.name;
+                    } else {
+                        referencingProcessGroups[unauthorizedReferencingComponentEntity.processGroup.id] = {
+                            referencingProcessors: [],
+                            referencingControllerServices: [],
+                            unauthorizedReferencingComponents: [],
+                            name: unauthorizedReferencingComponentEntity.processGroup.name,
+                            id: unauthorizedReferencingComponentEntity.processGroup.id
+                        };
+
+                        referencingProcessGroups[unauthorizedReferencingComponentEntity.processGroup.id].unauthorizedReferencingComponents.push(unauthorizedReferencingComponentEntity);
+                    }
+                });
+
+                var parameterReferencingComponentsContainer = $('#affected-referencing-components-container');
+                var groups = $('<ul class="referencing-component-listing clear"></ul>');
+
+                var referencingProcessGroupsArray = [];
+                for (var key in referencingProcessGroups) {
+                    if (referencingProcessGroups.hasOwnProperty(key)) {
+                        referencingProcessGroupsArray.push(referencingProcessGroups[key]);
+                    }
+                }
+
+                if (nfCommon.isEmpty(referencingProcessGroupsArray)) {
+                    // set to none
+                    $('<div class="referencing-component-container"><span class="unset">None</span></div>').appendTo(parameterReferencingComponentsContainer);
+                } else {
+                    //sort alphabetically
+                    var sortedReferencingProcessGroups = referencingProcessGroupsArray.sort(function (a, b) {
+                        if (a.name < b.name) {
+                            return -1;
+                        }
+                        if (a.name > b.name) {
+                            return 1;
+                        }
+                        return 0;
+                    });
+
+                    sortedReferencingProcessGroups.forEach(function (referencingProcessGroup) {
+                        // container for this pg's references
+                        var referencingPgReferencesContainer = $('<div class="referencing-component-references"></div>');
+                        parameterReferencingComponentsContainer.append(referencingPgReferencesContainer);
+
+                        // create the collapsable listing for each PG
+                        var createReferenceBlock = function (referencingProcessGroup, list) {
+                            var twist = $('<div class="expansion-button collapsed"></div>');
+                            var title = $('<span class="referencing-component-title"></span>').text(referencingProcessGroup.name);
+                            var count = $('<span class="referencing-component-count"></span>').text('(' + (referencingProcessGroup.referencingProcessors.length + referencingProcessGroup.referencingControllerServices.length + referencingProcessGroup.unauthorizedReferencingComponents.length) + ')');
+                            var referencingComponents = $('#affected-referencing-components-template').clone();
+                            referencingComponents.removeAttr('id');
+                            referencingComponents.removeClass('hidden');
+
+                            // create the reference block
+                            var groupTwist = $('<div class="referencing-component-block pointer unselectable"></div>').data('processGroupId', referencingProcessGroup.id).on('click', function () {
+                                if (twist.hasClass('collapsed')) {
+                                    groupTwist.append(referencingComponents);
+
+                                    var processorContainer = groupTwist.find('.fetch-parameters-referencing-processors');
+                                    nfCommon.cleanUpTooltips(processorContainer, 'div.referencing-component-state');
+                                    nfCommon.cleanUpTooltips(processorContainer, 'div.referencing-component-bulletins');
+                                    processorContainer.empty();
+
+                                    var controllerServiceContainer = groupTwist.find('.fetch-parameters-referencing-controller-services');
+                                    nfCommon.cleanUpTooltips(controllerServiceContainer, 'div.referencing-component-state');
+                                    nfCommon.cleanUpTooltips(controllerServiceContainer, 'div.referencing-component-bulletins');
+                                    controllerServiceContainer.empty();
+
+                                    var unauthorizedComponentsContainer = groupTwist.find('.fetch-parameters-referencing-unauthorized-components').empty();
+
+                                    if (referencingProcessGroups[$(this).data('processGroupId')].referencingProcessors.length === 0) {
+                                        $('<li class="referencing-component-container"><span class="unset">None</span></li>').appendTo(processorContainer);
+                                    } else {
+                                        // sort the referencing processors
+                                        referencingProcessGroups[$(this).data('processGroupId')].referencingProcessors.sort(nameComparator);
+
+                                        // render each and register a click handler
+                                        $.each(referencingProcessGroups[$(this).data('processGroupId')].referencingProcessors, function (_, referencingProcessorEntity) {
+                                            renderReferencingProcessor(referencingProcessorEntity, processorContainer);
+                                        });
+                                    }
+
+                                    if (referencingProcessGroups[$(this).data('processGroupId')].referencingControllerServices.length === 0) {
+                                        $('<li class="referencing-component-container"><span class="unset">None</span></li>').appendTo(controllerServiceContainer);
+                                    } else {
+                                        // sort the referencing controller services
+                                        referencingProcessGroups[$(this).data('processGroupId')].referencingControllerServices.sort(nameComparator);
+
+                                        // render each and register a click handler
+                                        $.each(referencingProcessGroups[$(this).data('processGroupId')].referencingControllerServices, function (_, referencingControllerServiceEntity) {
+                                            renderReferencingControllerService(referencingControllerServiceEntity, controllerServiceContainer);
+                                        });
+                                    }
+
+                                    if (referencingProcessGroups[$(this).data('processGroupId')].unauthorizedReferencingComponents.length === 0) {
+                                        $('<li class="referencing-component-container"><span class="unset">None</span></li>').appendTo(unauthorizedComponentsContainer);
+                                    } else {
+                                        // sort the unauthorized referencing components
+                                        referencingProcessGroups[$(this).data('processGroupId')].unauthorizedReferencingComponents.sort(function (a, b) {
+                                            if (a.permissions.canRead === true && b.permissions.canRead === true) {
+                                                // processors before controller services
+                                                var sortVal = a.component.referenceType === b.component.referenceType ? 0 : a.component.referenceType > b.component.referenceType ? -1 : 1;
+
+                                                // if a and b are the same type, then sort by name
+                                                if (sortVal === 0) {
+                                                    sortVal = a.component.name === b.component.name ? 0 : a.component.name > b.component.name ? 1 : -1;
+                                                }
+
+                                                return sortVal;
+                                            } else {
+
+                                                // if lacking read and write perms on both, sort by id
+                                                if (a.permissions.canRead === false && b.permissions.canRead === false) {
+                                                    return a.id > b.id ? 1 : -1;
+                                                } else {
+                                                    // if only one has read perms, then let it come first
+                                                    if (a.permissions.canRead === true) {
+                                                        return -1;
+                                                    } else {
+                                                        return 1;
+                                                    }
+                                                }
+                                            }
+                                        });
+
+                                        $.each(referencingProcessGroups[$(this).data('processGroupId')].unauthorizedReferencingComponents, function (_, unauthorizedReferencingComponentEntity) {
+                                            if (unauthorizedReferencingComponentEntity.permissions.canRead === true) {
+                                                if (unauthorizedReferencingComponentEntity.component.referenceType === 'PROCESSOR') {
+                                                    renderReferencingProcessor(unauthorizedReferencingComponentEntity, unauthorizedComponentsContainer);
+                                                } else {
+                                                    renderReferencingControllerService(unauthorizedReferencingComponentEntity, unauthorizedComponentsContainer);
+                                                }
+                                            } else {
+                                                var referencingUnauthorizedComponentContainer = $('<li class="referencing-component-container"></li>').appendTo(unauthorizedComponentsContainer);
+                                                $('<span class="fetch-parameters-referencing-component-name link ellipsis"></span>')
+                                                    .prop('title', unauthorizedReferencingComponentEntity.id)
+                                                    .text(unauthorizedReferencingComponentEntity.id)
+                                                    .on('click', function () {
+                                                        // close the shell
+                                                        $('#shell-dialog').modal('hide');
+
+                                                        // show the component in question
+                                                        if (unauthorizedReferencingComponentEntity.referenceType === 'PROCESSOR') {
+                                                            nfCanvasUtils.showComponent(unauthorizedReferencingComponentEntity.processGroup.id, unauthorizedReferencingComponentEntity.id);
+                                                        } else if (unauthorizedReferencingComponentEntity.referenceType === 'CONTROLLER_SERVICE') {
+                                                            nfProcessGroupConfiguration.showConfiguration(unauthorizedReferencingComponentEntity.processGroup.id).done(function () {
+                                                                nfProcessGroup.enterGroup(unauthorizedReferencingComponentEntity.processGroup.id);
+                                                                nfProcessGroupConfiguration.selectControllerService(unauthorizedReferencingComponentEntity.id);
+                                                            });
+                                                        }
+                                                    })
+                                                    .appendTo(referencingUnauthorizedComponentContainer);
+                                            }
+                                        });
+                                    }
+                                } else {
+                                    groupTwist.find('.affected-referencing-components-template').remove();
+                                }
+
+                                // toggle this block
+                                toggle(twist, list);
+
+                                // update the border if necessary
+                                updateReferencingComponentsBorder($('#affected-referencing-components-container'));
+                            }).append(twist).append(title).append(count).appendTo(referencingPgReferencesContainer);
+
+                            // add the listing
+                            list.appendTo(referencingPgReferencesContainer);
+
+                            // expand the group twist
+                            groupTwist.click();
+                        };
+
+                        // create block for this process group
+                        createReferenceBlock(referencingProcessGroup, groups);
+                    });
+                }
+            }
+        })
+            .always(function () {
+                spinner.removeClass('ajax-loading');
+            });
+        return loadingDeferred.promise();
+    };
+
+    /**
+     * Loads the referencing components for this parameter provider.
+     *
+     * @param {jQuery} parameterProviderReferencingComponentsContainer
+     * @param {object} parameterProviderEntity
+     */
+    var loadParameterProviderReferencingComponents = function (parameterProviderReferencingComponentsContainer, parameterProviderEntity) {
+        parameterProviderReferencingComponentsContainer.empty();
+        var parameterProviderComponent = parameterProviderEntity;
+
+        if (nfCommon.isEmpty(parameterProviderComponent.referencingParameterContexts)) {
+            parameterProviderReferencingComponentsContainer.append('<div class="unset">No referencing components.</div>');
+            return;
+        }
+
+        // toggles the visibility of a container
+        var toggle = function (twist, container) {
+            if (twist.hasClass('expanded')) {
+                twist.removeClass('expanded').addClass('collapsed');
+                container.hide();
+            } else {
+                twist.removeClass('collapsed').addClass('expanded');
+                container.show();
+            }
+        };
+
+        var parameterContexts = $('<ul class="referencing-component-listing clear"></ul>');
+        var unauthorized = $('<ul class="referencing-component-listing clear"></ul>');
+
+        $.each(parameterProviderComponent.referencingParameterContexts, function (_, refParameterContextComponent) {
+            // check the access policy for this referencing component
+            if (nfCommon.isDefinedAndNotNull(parameterProviderEntity.permissions) && parameterProviderEntity.permissions.canRead === false) {
+                var unauthorizedReferencingComponent = $('<div class="unset"></div>').text(refParameterContextComponent.id);
+                unauthorized.append(unauthorizedReferencingComponent);
+            } else {
+                var referencingComponent = refParameterContextComponent.component;
+
+                var parameterContextLink = $('<span class="referencing-component-name link"></span>')
+                    .text(referencingComponent.name)
+                    .on('click', function () {
+                        // show the component
+                        nfParameterContexts.showParameterContext(referencingComponent.id, null, referencingComponent.name);

Review Comment:
   When linking to a Parameter Context, I would expect to be taken to the listing with `this` Parameter Context selected. As-is the user is returned to the canvas with the Parameter Context shown in a dialog.
   
   Linking in the other direction is working as expected. When linking to a Parameter Provider from a Parameter Context, the user is taken to the listing of Parameter Providers with the desired Parameter Provider selected.



##########
nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js:
##########
@@ -2493,6 +2525,10 @@
                 markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key"></div>';
             }
 
+            if (dataContext.component.parameterProviderConfiguration && canRead) {

Review Comment:
   `canRead` should be evaluated prior to `dataContext.component.parameterProviderConfiguration`. If `canRead` is false, `component` will not be available.



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