You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by sc...@apache.org on 2019/09/05 18:07:21 UTC

[nifi] 01/12: NIFI-6506 - Add ability to convert properties to parameters * open existing add param dialog from property table * convert prop to param, update param context * keep add parameter dialog open until update of the context completes when converting property to param * Updating parameter context while converting property to parameter. * Disable the Apply button while updating * When Cancel is pressed and there is an update in progress, issue the DELETE request to cancel the update. * Show some [...]

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

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

commit eddc5624d8b4da4707d790c563c929dfd1af4bf1
Author: Rob Fellows <ro...@gmail.com>
AuthorDate: Tue Aug 13 14:36:13 2019 -0400

    NIFI-6506 - Add ability to convert properties to parameters
    * open existing add param dialog from property table
    * convert prop to param, update param context
    * keep add parameter dialog open until update of the context completes when converting property to param
    * Updating parameter context while converting property to parameter.
    * Disable the Apply button while updating
    * When Cancel is pressed and there is an update in progress, issue the DELETE request to cancel the update.
    * Show some status in the Add Parameter dialog when parameter context is updating following a property conversion.
    * enforce character restrictions on property names.
    * Add convert property to parameter to controller service config.
    * Properly set sensitive setting when converting sensitive properties
    * Allow converting of properties from controller services configuration into parameters
    * Refactor addNewParameter method to be able to share common parts with convertPropertyToParameter
---
 .../canvas/new-parameter-context-dialog.jsp        |   4 +
 .../nifi-web-ui/src/main/webapp/css/dialog.css     |  19 +
 .../jquery/propertytable/jquery.propertytable.js   |  62 ++-
 .../webapp/js/nf/canvas/nf-controller-service.js   |  10 +
 .../webapp/js/nf/canvas/nf-parameter-contexts.js   | 444 +++++++++++++++------
 .../js/nf/canvas/nf-processor-configuration.js     |   6 +-
 6 files changed, 418 insertions(+), 127 deletions(-)

diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp
index bc264bd..3735355 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp
@@ -117,6 +117,10 @@
             <div class="clear"></div>
         </div>
     </div>
+    <div id="parameter-context-updating-status">
+        <div class='parameter-context-step ajax-loading'></div>
+        <div class='status-message'>Updating parameter context</div>
+    </div>
 </div>
 <div id="referencing-components-template" class="referencing-components-template hidden clear">
     <div class="setting">
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css
index eb71a9e..0503100 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.css
@@ -384,3 +384,22 @@ div.parameter-context-step {
     background-color: transparent;
     float: right;
 }
+
+#parameter-context-updating-status {
+    display: none;
+    position: absolute;
+    left: 12px;
+    bottom: 0;
+    height: 32px;
+}
+
+#parameter-context-updating-status.show-status {
+    display: flex;
+    flex-direction: row;
+    align-items: center;
+}
+
+#parameter-context-updating-status .status-message {
+    white-space: nowrap;
+    margin-left: 6px;
+}
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
index 39334ac..7709824 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
@@ -30,7 +30,9 @@
                 'nf.Client',
                 'nf.ErrorHandler',
                 'nf.ProcessGroupConfiguration',
-                'nf.Settings'],
+                'nf.Settings',
+                'nf.ParameterContexts',
+                'lodash-core'],
             function ($,
                       Slick,
                       nfCommon,
@@ -40,7 +42,9 @@
                       nfClient,
                       nfErrorHandler,
                       nfProcessGroupConfiguration,
-                      nfSettings) {
+                      nfSettings,
+                      nfParameterContexts,
+                      _) {
                 factory($,
                     Slick,
                     nfCommon,
@@ -50,7 +54,9 @@
                     nfClient,
                     nfErrorHandler,
                     nfProcessGroupConfiguration,
-                    nfSettings);
+                    nfSettings,
+                    nfParameterContexts,
+                    _);
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         factory(require('jquery'),
@@ -62,7 +68,9 @@
             require('nf.Client'),
             require('nf.ErrorHandler'),
             require('nf.ProcessGroupConfiguration'),
-            require('nf.Settings'));
+            require('nf.Settings'),
+            recuire('nf.ParameterContexts'),
+            require('lodash-core'));
     } else {
         factory(root.$,
             root.Slick,
@@ -73,7 +81,9 @@
             root.nf.Client,
             root.nf.ErrorHandler,
             root.nf.ProcessGroupConfiguration,
-            root.nf.Settings);
+            root.nf.Settings,
+            root.nf.ParameterContexts,
+            root._);
     }
 }(this, function ($,
                   Slick,
@@ -84,7 +94,9 @@
                   nfClient,
                   nfErrorHandler,
                   nfProcessGroupConfiguration,
-                  nfSettings) {
+                  nfSettings,
+                  nfParameterContexts,
+                  _) {
 
     var groupId = null;
     var COMBO_MIN_WIDTH = 212;
@@ -1350,9 +1362,22 @@
                 });
             }
 
-            // allow user defined properties to be removed
-            if (options.readOnly !== true && dataContext.type === 'userDefined') {
-                markup += '<div title="Delete" class="delete-property pointer fa fa-trash"></div>';
+            if (options.readOnly !== true) {
+                var canModifyParamContexts = nfCommon.canModifyParameterContexts();
+                var paramContextIsSet = false;
+                if (_.isFunction(options.getParameterContextId)) {
+                    paramContextIsSet = !_.isNil(options.getParameterContextId(groupId));
+                }
+                var referencesParam = referencesParameter(dataContext.value);
+
+                if (canModifyParamContexts && paramContextIsSet && !referencesParam && !identifiesControllerService) {
+                    markup += '<div title="Convert to parameter" class="convert-to-parameter pointer fa fa-level-up"></div>';
+                }
+
+                // allow user defined properties to be removed
+                if (dataContext.type === 'userDefined') {
+                    markup += '<div title="Delete" class="delete-property pointer fa fa-trash"></div>';
+                }
             }
 
             return markup;
@@ -1545,6 +1570,25 @@
                             });
                         }
                     }
+                } else if (target.hasClass('convert-to-parameter')) {
+                    var parameterContextId;
+                    if (_.isFunction(options.getParameterContextId)) {
+                        parameterContextId = options.getParameterContextId(groupId);
+                    }
+
+                    if (options.readOnly !== true && !_.isNil(parameterContextId)) {
+                        var descriptors = table.data('descriptors');
+                        var propertyDescriptor = descriptors[property.displayName];
+
+                        nfParameterContexts.convertPropertyToParameter(property, propertyDescriptor, parameterContextId)
+                            .done(function (parameter) {
+                                // set the property value to the reference the parameter that was created
+                                propertyData.updateItem(property.id, $.extend(property, {
+                                    previousValue: property.value,
+                                    value: '#{' + parameter.name + '}'
+                                }));
+                            });
+                    }
                 }
             }
         });
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
index 6814edc..c80d8c5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
@@ -1875,6 +1875,16 @@
                     },
                     goToServiceDeferred: function () {
                         return goToServiceFromProperty(serviceTable);
+                    },
+                    getParameterContextId: function (groupId) {
+                        // attempt to identify the parameter context id, conditional based on whether
+                        // the user is configuring the current process group
+                        if (_.isNil(groupId) || groupId === nfCanvasUtils.getGroupId()) {
+                            return nfCanvasUtils.getParameterContextId();
+                        } else {
+                            var parentProcessGroup = nfCanvasUtils.getComponentByType('ProcessGroup').get(groupId);
+                            return parentProcessGroup.parameterContextId;
+                        }
                     }
                 });
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
index 336d7de..ec617aa 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
@@ -36,9 +36,10 @@
                 'nf.PolicyManagement',
                 'nf.Processor',
                 'nf.ProcessGroup',
-                'nf.ProcessGroupConfiguration'],
-            function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration) {
-                return (nf.ParameterContexts = factory($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration));
+                'nf.ProcessGroupConfiguration',
+                'lodash-core'],
+            function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration, _) {
+                return (nf.ParameterContexts = factory($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration, _));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ParameterContexts =
@@ -59,7 +60,8 @@
                 require('nf.PolicyManagement'),
                 require('nf.Processor'),
                 require('nf.ProcessGroup'),
-                require('nf.ProcessGroupConfiguration')));
+                require('nf.ProcessGroupConfiguration'),
+                require('lodash-core')));
     } else {
         nf.ParameterContexts = factory(root.$,
             root.Slick,
@@ -78,9 +80,10 @@
             root.nf.PolicyManagement,
             root.nf.Processor,
             root.nf.ProcessGroup,
-            root.nf.ProcessGroupConfiguration);
+            root.nf.ProcessGroupConfiguration,
+            root._);
     }
-}(this, function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration) {
+}(this, function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration, _) {
     'use strict';
 
     var config = {
@@ -139,8 +142,10 @@
         // defines a function for sorting
         var comparer = function (a, b) {
             if (a.permissions.canRead && b.permissions.canRead) {
-                var aString = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? a.component[sortDetails.columnId] : '';
-                var bString = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? b.component[sortDetails.columnId] : '';
+
+                // use _.get to try to access the piece of the object you want, but provide a default value it it is not there
+                var aString = _.get(a, 'component[' +  sortDetails.columnId + ']', '');
+                var bString = _.get(b, 'component[' +  sortDetails.columnId + ']', '');
                 return aString === bString ? 0 : aString > bString ? 1 : -1;
             } else {
                 if (!a.permissions.canRead && !b.permissions.canRead) {
@@ -170,8 +175,8 @@
         // defines a function for sorting
         var comparer = function (a, b) {
             if (sortDetails.columnId === 'name') {
-                var aString = nfCommon.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : '';
-                var bString = nfCommon.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : '';
+                var aString = _.get(a, '[' +  sortDetails.columnId + ']', '');
+                var bString = _.get(b, '[' +  sortDetails.columnId + ']', '');
                 return aString === bString ? 0 : aString > bString ? 1 : -1;
             }
         };
@@ -752,139 +757,128 @@
         return a.component.name.localeCompare(b.component.name);
     };
 
-    var parameterKeyRegex = /^[a-zA-Z0-9-_. ]+$/;
-
     /**
      * Adds a new parameter.
      */
     var addNewParameter = function () {
-        var parameterName = $.trim($('#parameter-name').val());
+        var param = getParameterFromFieldValues();
 
-        // ensure the parameter name is specified
-        if (parameterName !== '' && parameterKeyRegex.test(parameterName)) {
-            var parameterGrid = $('#parameter-table').data('gridInstance');
-            var parameterData = parameterGrid.getData();
+        var parameterGrid = $('#parameter-table').data('gridInstance');
+        var parameterData = parameterGrid.getData();
 
-            // ensure the parameter name is unique
-            var matchingParameter = null;
-            $.each(parameterData.getItems(), function (_, item) {
-                if (parameterName === item.name) {
-                    matchingParameter = item;
-                    return false;
-                }
-            });
+        var isValid = validateParameter(param, parameterData.getItems());
 
-            var isSensitive = $('#parameter-dialog').find('input[name="sensitive"]:checked').val() === "sensitive" ? true : false;
-            var isChecked = $('#parameter-set-empty-string-field').hasClass('checkbox-checked');
+        if (isValid) {
 
-            if (matchingParameter === null) {
-                var parameter = {
-                    id: parameterCount,
-                    hidden: false,
-                    type: 'Parameter',
-                    sensitive: isSensitive,
-                    name: parameterName,
-                    description: $('#parameter-description-field').val(),
-                    previousValue: null,
-                    previousDescription: null,
-                    isEditable: true,
-                    isEmptyStringSet: isChecked,
-                    isModified: true,
-                    hasValueChanged: false,
-                    isNew: true
-                };
-
-                var value = $('#parameter-value-field').val();
-                if (!nfCommon.isBlank(value)) {
-                    parameter.value = value;
-                } else {
-                    if (isChecked) {
-                        parameter.value = '';
-                    } else {
-                        parameter.value = null;
-                    }
-                }
+            var parameter = _.extend({}, param, {
+                id: _.defaultTo(param.id, parameterCount),
+                hidden: false,
+                type: 'Parameter',
+                previousValue: null,
+                previousDescription: null,
+                isEditable: true,
+                isModified: true,
+                hasValueChanged: false,
+                isNew: true,
+            });
 
+            if (_.isNil(param.id)) {
                 // add a row for the new parameter
                 parameterData.addItem(parameter);
-
-                // sort the data
-                parameterData.reSort();
-
-                // select the new parameter row
-                var row = parameterData.getRowById(parameterCount);
-                parameterGrid.setActiveCell(row, parameterGrid.getColumnIndex('value'));
-                parameterCount++;
             } else {
-                // if this row is currently hidden, make sure the sensitivity is equivalent before we allow recreate
-                if (matchingParameter.hidden === true && matchingParameter.sensitive !== isSensitive) {
-                    nfDialog.showOkDialog({
-                        headerText: 'Parameter Exists',
-                        dialogContent: 'A parameter with this name has been marked for deletion. Please apply this change to delete this parameter from the parameter context before recreating it with a different sensitivity.'
-                    });
-                } else if (matchingParameter.hidden === true && matchingParameter.sensitive === isSensitive) {
-                    var parameter = $.extend(matchingParameter, {
-                        hidden: false,
-                        sensitive: isSensitive,
-                        previousValue: null,
-                        description: $('#parameter-description-field').val(),
-                        previousDescription: null,
-                        isEditable: true,
-                        isEmptyStringSet: isChecked,
-                        isModified: true,
-                        hasValueChanged: false,
-                        isNew: true
-                    });
+                parameterData.updateItem(param.id, parameter);
+            }
 
-                    var value = $('#parameter-value-field').val();
-                    if (!nfCommon.isBlank(value)) {
-                        parameter.value = value;
-                    } else {
-                        if (isChecked) {
-                            parameter.value = '';
-                        } else {
-                            parameter.value = null;
-                        }
-                    }
+            // sort the data
+            parameterData.reSort();
 
-                    parameterData.updateItem(matchingParameter.id, parameter);
+            // select the new parameter row
+            var row = parameterData.getRowById(parameterCount);
+            parameterGrid.setActiveCell(row, parameterGrid.getColumnIndex('value'));
+            parameterCount++;
 
-                    // sort the data
-                    parameterData.reSort();
+            // close the new parameter dialog
+            $('#parameter-dialog').modal('hide');
 
-                    // select the new parameter row
-                    var row = parameterData.getRowById(matchingParameter.id);
-                    parameterGrid.setActiveCell(row, parameterGrid.getColumnIndex('value'));
-                    parameterCount++;
-                } else {
-                    nfDialog.showOkDialog({
-                        headerText: 'Parameter Exists',
-                        dialogContent: 'A parameter with this name already exists.'
-                    });
+        }
 
-                    // select the existing properties row
-                    var matchingRow = parameterData.getRowById(matchingParameter.id);
-                    parameterGrid.setSelectedRows([matchingRow]);
-                    parameterGrid.scrollRowIntoView(matchingRow);
-                }
-            }
+        // update the buttons to possibly trigger the disabled state
+        $('#parameter-context-dialog').modal('refreshButtons');
+    };
 
-            // close the new parameter dialog
-            $('#parameter-dialog').modal('hide');
+    /**
+     * Builds a parameter object from the user-entered parameter inputs
+     *
+     * @return {{isEmptyStringSet: *, name: *, description: *, sensitive: *, value: *}}
+     */
+    var getParameterFromFieldValues = function () {
+        var name = $.trim($('#parameter-name').val());
+        var value = $('#parameter-value-field').val();
+        var description = $('#parameter-description-field').val();
+        var isSensitive = $('#parameter-dialog').find('input[name="sensitive"]:checked').val() === 'sensitive' ? true : false;
+        var isEmptyStringSet = $('#parameter-set-empty-string-field').hasClass('checkbox-checked');
+
+        if (_.isEmpty(value)) {
+            value = isEmptyStringSet ? '' : null;
+        }
 
-            // update the buttons to possibly trigger the disabled state
-            $('#parameter-context-dialog').modal('refreshButtons');
+        return {
+            name,
+            value,
+            description,
+            sensitive: isSensitive,
+            isEmptyStringSet
+        }
+    };
 
-        } else if (!parameterKeyRegex.test(parameterName)) {
+    /**
+     * Checks the validity of a parameter
+     * @param parameter Parameter to validate
+     * @param existingParameters Existing parameters to verify there are no duplicates
+     * @return {boolean}
+     */
+    var validateParameter = function (parameter, existingParameters) {
+        if (parameter.name === '') {
             nfDialog.showOkDialog({
                 headerText: 'Configuration Error',
-                dialogContent: 'This parameter 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.'
+                dialogContent: 'The name of the parameter must be specified.'
             });
-        } else {
+            return false;
+        }
+
+        // make sure the parameter name does not use any unsupported characters
+        var parameterNameRegex = /^[a-zA-Z0-9-_. ]+$/;
+        if (!parameterNameRegex.test(parameter.name)) {
             nfDialog.showOkDialog({
                 headerText: 'Configuration Error',
-                dialogContent: 'The name of the parameter must be specified.'
+                dialogContent: 'The name of the parameter 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 is not a duplicate
+        var matchingParameter = _.find(existingParameters, { name: parameter.name });
+
+        if (_.isNil(matchingParameter)) {
+            return true;
+        } else {
+            var matchingParamIsHidden = _.get(matchingParameter, 'hidden', false);
+            if (matchingParamIsHidden && matchingParameter.sensitive !== parameter.sensitive) {
+                nfDialog.showOkDialog({
+                    headerText: 'Parameter Exists',
+                    dialogContent: 'A parameter with this name has been marked for deletion. Please apply this change to delete this parameter from the parameter context before recreating it with a different sensitivity.'
+                });
+            } else if (matchingParamIsHidden && matchingParameter.sensitive === parameter.sensitive) {
+                // set the id of the parameter that was found. It could be used to update it.
+                parameter.id = matchingParameter.id;
+                return true;
+            } else {
+                nfDialog.showOkDialog({
+                    headerText: 'Parameter Exists',
+                    dialogContent: 'A parameter with this name already exists.'
+                });
+            }
+            return false;
         }
     };
 
@@ -1856,6 +1850,50 @@
         initParameterTable();
     };
 
+    var openAddParameterDialog = function ({ onApply, onCancel }) {
+        $('#parameter-dialog')
+            .modal('setHeaderText', 'Add Parameter')
+            .modal('setButtonModel', [{
+                buttonText: 'Apply',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                disabled: function () {
+                    var parameterName = $('#parameter-name').val();
+                    var parameterValue = $('#parameter-value-field').val();
+                    var isEmptyString = $('#parameter-dialog').find('.nf-checkbox').hasClass('checkbox-checked');
+                    var isUpdatingParameterContext = $('#parameter-context-updating-status').hasClass('show-status');
+
+                    if (isUpdatingParameterContext) {
+                        return true;
+                    }
+
+                    if ((parameterName !== '' && parameterValue !== '') || (parameterName !== '' && isEmptyString)) {
+                        return false;
+                    }
+                    return true;
+                },
+                handler: {
+                    click: onApply
+                }
+            }, {
+                buttonText: 'Cancel',
+                color: {
+                    base: '#E3E8EB',
+                    hover: '#C7D2D7',
+                    text: '#004849'
+                },
+                handler: {
+                    click: onCancel
+                }
+            }])
+            .modal('show');
+
+        $('#parameter-dialog').modal('show');
+    };
+
     /**
      * Loads the parameter contexts.
      */
@@ -2298,6 +2336,178 @@
                 var parameterContextData = parameterContextGrid.getData();
                 parameterContextData.deleteItem(parameterContextEntity.id);
             }).fail(nfErrorHandler.handleAjaxError);
+        },
+
+        /**
+         * Converts a property in to a parameter and adds the parameter to the current parameter context.
+         * @param property              property to convert
+         * @param propertyDescriptor    property descriptor for the property bering converted
+         * @param parameterContextId    id of the current parameter context
+         * @return Promise              A Promise that resolves with the added parameter
+         */
+        convertPropertyToParameter: function (property, propertyDescriptor, parameterContextId) {
+            return $.Deferred(function (deferred) {
+
+                if (_.isNil(parameterContextId)) {
+                    nfDialog.showOkDialog({
+                        headerText: 'Unable to Convert Property',
+                        dialogContent: 'There is no parameter context set for the current process group.'
+                    });
+
+                    deferred.reject();
+                }
+
+                var getContext = $.ajax({
+                    type: 'GET',
+                    url: config.urls.parameterContexts + '/' + encodeURIComponent(parameterContextId),
+                    dataType: 'json'
+                });
+
+                // get the parameter context, show the dialog
+                getContext
+                    .done(function (parameterContextEntity) {
+                        var showUpdateStatus = function (isOn) {
+                            if (isOn) {
+                                // show the status message
+                                $('#parameter-context-updating-status').addClass('show-status');
+
+                                // disable the apply button
+                                $('#parameter-dialog').modal('refreshButtons');
+                            } else {
+                                $('#parameter-context-updating-status').removeClass('show-status');
+
+                                // possibly re-enable the apply button
+                                $('#parameter-dialog').modal('refreshButtons');
+                            }
+                        };
+
+                        var requestId;
+                        var updateTimeoutReference;
+
+                        openAddParameterDialog({
+                            onApply: function () {
+                                showUpdateStatus(true);
+
+                                var existingParameters = parameterContextEntity.component.parameters.map(function(p) { return p.parameter });
+                                var parameter = getParameterFromFieldValues();
+
+                                var isValid = validateParameter(parameter, existingParameters);
+
+                                if (!isValid) {
+                                    // Do not resolve or reject here. Give the user the chance to fix the issue.
+                                    showUpdateStatus(false);
+                                    return;
+                                }
+
+                                // only adding a new parameter, just put the new one in the list for the update
+                                parameterContextEntity.component.parameters = [{
+                                    parameter: {
+                                        name: parameter.name,
+                                        value: parameter.value,
+                                        sensitive: parameter.sensitive,
+                                        description: parameter.description
+                                    }}];
+
+                                requestId = null;
+
+                                // initiate the parameter context update with the new parameter
+                                submitUpdateRequest(parameterContextEntity)
+                                    .done(function(response) {
+                                        var pollUpdateRequest = function (updateRequestEntity) {
+                                            var updateRequest = updateRequestEntity.request;
+                                            var errored = !_.isEmpty(updateRequest.failureReason);
+
+                                            // get the request id
+                                            requestId = updateRequest.requestId;
+
+                                            if (updateRequest.complete === true) {
+                                                showUpdateStatus(false);
+
+                                                if (errored) {
+                                                    nfDialog.showOkDialog({
+                                                        headerText: 'Parameter Context Update Error',
+                                                        dialogContent: 'Unable to complete parameter context update request: ' + nfCommon.escapeHtml(updateRequest.failureReason)
+                                                    });
+                                                    // update failed, therefore converting failed. reject the promise for the caller
+                                                    deferred.reject();
+                                                } else {
+                                                    // resolve the promise for the caller if the update was successful
+                                                    deferred.resolve(parameter);
+                                                }
+
+                                                // delete the update request
+                                                deleteUpdateRequest(parameterContextEntity.id, requestId);
+
+                                                // hide the param dialog
+                                                $('#parameter-dialog').modal('hide');
+
+                                            } else {
+                                                // wait to get an updated status
+                                                updateTimeoutReference = setTimeout(function () {
+                                                    getUpdateRequest(parameterContextEntity.id, requestId)
+                                                        .done(function (getResponse) {
+                                                            pollUpdateRequest(getResponse);
+                                                        })
+                                                        .fail(function (e) {
+                                                            if (!_.isNil(parameterContextEntity.id) && !_.isNil(requestId)) {
+                                                                deleteUpdateRequest(parameterContextEntity.id, requestId);
+                                                            }
+                                                            deferred.reject(e)
+                                                        });
+                                                }, 1000);
+                                            }
+                                        };
+
+                                        pollUpdateRequest(response);
+                                    })
+                                    .fail(function (e) {
+                                        deferred.reject(e)
+                                    });
+                            },
+                            onCancel: function() {
+                                showUpdateStatus(false);
+
+                                if (!_.isNil(parameterContextEntity.id) && !_.isNil(requestId)) {
+                                    deleteUpdateRequest(parameterContextEntity.id, requestId);
+                                    requestId = null;
+                                    if (!_.isNil(updateTimeoutReference)) {
+                                        clearTimeout(updateTimeoutReference);
+                                    }
+                                }
+
+                                // hide the dialog
+                                $(this).modal('hide');
+                            }
+                        });
+
+                        // set the values of the form from the passed in property
+                        $('#parameter-name').val(property.displayName);
+                        $('#parameter-name').prop('disabled', false);
+                        $('#parameter-sensitive-radio-button').prop('disabled', true);
+                        $('#parameter-not-sensitive-radio-button').prop('disabled', true);
+                        if (property.value === '') {
+                            $('#parameter-dialog').find('.nf-checkbox').removeClass('checkbox-unchecked').addClass('checkbox-checked');
+                        } else {
+                            $('#parameter-dialog').find('.nf-checkbox').removeClass('checkbox-checked').addClass('checkbox-unchecked');
+                        }
+
+                        if (nfCommon.isSensitiveProperty(propertyDescriptor)) {
+                            $('#parameter-sensitive-radio-button').prop('checked', true);
+                            $('#parameter-not-sensitive-radio-button').prop('checked', false);
+                        } else {
+                            $('#parameter-sensitive-radio-button').prop('checked', false);
+                            $('#parameter-not-sensitive-radio-button').prop('checked', true);
+                            $('#parameter-value-field').val(property.value);
+                        }
+                        $('#parameter-description-field').val(property.description);
+
+                        // update the buttons to possibly trigger the disabled state
+                        $('#parameter-dialog').modal('refreshButtons');
+                    })
+                    .fail(function(e) {
+                        deferred.reject(e);
+                    });
+            }).promise();
         }
     };
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
index aaa45f4..90cd05f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
@@ -659,7 +659,11 @@
                         }
                     }).promise();
                 },
-                goToServiceDeferred: goToServiceFromProperty
+                goToServiceDeferred: goToServiceFromProperty,
+                getParameterContextId: function (groupId) {
+                    // processors being configured must be in the current group
+                    return nfCanvasUtils.getParameterContextId();
+                }
             });
         },