You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2019/08/29 18:49:22 UTC

[nifi] branch master updated: [NIFI-6282] manage parameters and parameter contexts [NIFI-6282] when creating a parameter context inline during PG configuration set the newly created parameter context as the selected option [NIFI-6282] If a request to update a parameter context fails, then update the button model to give the user the ability to Apply or Cancel again. [NIFI-6282] address pr review comments [NIFI-6282] remove es6 let [NIFI-6282] update marshall parameters logic and add comments [NIFI-6282] deterministic p [...]

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f678c75  [NIFI-6282] manage parameters and parameter contexts [NIFI-6282] when creating a parameter context inline during PG configuration set the newly created parameter context as the selected option [NIFI-6282] If a request to update a parameter context fails, then update the button model to give the user the ability to Apply or Cancel again. [NIFI-6282] address pr review comments [NIFI-6282] remove es6 let [NIFI-6282] update marshall parameters logic and add comments [NIFI-62 [...]
f678c75 is described below

commit f678c75d7004656918d42496666db49350515cc2
Author: Scott Aslan <sc...@gmail.com>
AuthorDate: Thu Jul 25 23:03:27 2019 -0400

    [NIFI-6282] manage parameters and parameter contexts
    [NIFI-6282] when creating a parameter context inline during PG configuration set the newly created parameter context as the selected option
    [NIFI-6282] If a request to update a parameter context fails, then update the button model to give the user the ability to Apply or Cancel again.
    [NIFI-6282] address pr review comments
    [NIFI-6282] remove es6 let
    [NIFI-6282] update marshall parameters logic and add comments
    [NIFI-6282] deterministic parameter usage listing, update CS status on PG PC change, expand all twisties by default, remove es6 const
    [NIFI-6282] update regex and
    [NIFI-6282] update parameter loading/serialization/marshalling
    [NIFI-6282] use referencingComponents instead of affectedComponents
    [NIFI-6282] activate Apply button for sensitive parameter set empty string change
    [NIFI-6282] fix bug with PG parameters context menu enable
    [NIFI-6282] only allow delete and recreate of a parameter with equivalent sensitivity
    [NIFI-6282] display referencing components during parameter management as well as during the parameter context update
    [NIFI-6282] display no value set in parameter table when parameter value is null
    [NIFI-6282]
    - Add ellipsis to referencing component names.
    - Addressing issues canceling update requests.
    - Addressing issues with incorrect service scope.
    - Addressing issue showing the affected parameters.
---
 .../api/dto/ParameterContextUpdateRequestDTO.java  |   12 +-
 .../nifi/web/api/dto/ProcessGroupNameDTO.java      |   45 +
 .../web/api/entity/AffectedComponentEntity.java    |   14 +-
 .../nifi/web/api/entity/CurrentUserEntity.java     |   13 +
 .../ParameterContextUpdateEndpointMerger.java      |    4 +-
 .../manager/AffectedComponentEntityMerger.java     |    4 +
 .../apache/nifi/web/StandardNiFiServiceFacade.java |    5 +-
 .../nifi/web/api/ParameterContextResource.java     |    6 +-
 .../web/api/concurrent/AsyncRequestManager.java    |    5 +-
 .../org/apache/nifi/web/api/dto/DtoFactory.java    |   75 +-
 .../org/apache/nifi/web/api/dto/EntityFactory.java |    5 +-
 .../web/dao/impl/StandardParameterContextDAO.java  |    4 +-
 .../util/ClusterReplicationComponentLifecycle.java |    6 +-
 .../nifi-framework/nifi-web/nifi-web-ui/pom.xml    |   39 +-
 .../src/main/resources/filters/canvas.properties   |    3 +-
 .../src/main/webapp/WEB-INF/pages/canvas.jsp       |    4 +-
 .../WEB-INF/partials/canvas/canvas-header.jsp      |    8 +-
 .../canvas/new-parameter-context-dialog.jsp        |  146 ++
 .../partials/canvas/parameter-contexts-content.jsp |   32 +
 .../WEB-INF/partials/canvas/policy-management.jsp  |   12 +-
 .../canvas/process-group-configuration.jsp         |   15 +-
 .../partials/canvas/variable-configuration.jsp     |    4 +-
 .../nifi-web-ui/src/main/webapp/css/canvas.css     |    4 +-
 .../nifi-web-ui/src/main/webapp/css/dialog.css     |    7 +
 .../webapp/css/new-parameter-context-dialog.css    |  103 +
 ...up-configuration.css => parameter-contexts.css} |   84 +-
 .../webapp/css/process-group-configuration.css     |    6 +-
 .../nf-ng-canvas-global-menu-controller.js         |   30 +-
 .../src/main/webapp/js/nf/canvas/nf-actions.js     |   35 +-
 .../webapp/js/nf/canvas/nf-canvas-bootstrap.js     |   14 +-
 .../main/webapp/js/nf/canvas/nf-canvas-utils.js    |   18 +-
 .../src/main/webapp/js/nf/canvas/nf-canvas.js      |   26 +-
 .../main/webapp/js/nf/canvas/nf-context-menu.js    |   18 +-
 .../webapp/js/nf/canvas/nf-parameter-contexts.js   | 2305 ++++++++++++++++++++
 .../webapp/js/nf/canvas/nf-policy-management.js    |  107 +-
 .../js/nf/canvas/nf-process-group-configuration.js |  171 +-
 .../src/main/webapp/js/nf/canvas/nf-settings.js    |   12 +-
 .../webapp/js/nf/canvas/nf-variable-registry.js    |    8 +-
 .../nifi-web-ui/src/main/webapp/js/nf/nf-common.js |   19 +-
 39 files changed, 3280 insertions(+), 148 deletions(-)

diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterContextUpdateRequestDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterContextUpdateRequestDTO.java
index 76acc8d..94ab468 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterContextUpdateRequestDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ParameterContextUpdateRequestDTO.java
@@ -25,7 +25,7 @@ import java.util.Set;
 @XmlType(name = "parameterContextUpdateRequest")
 public class ParameterContextUpdateRequestDTO extends AsynchronousRequestDTO<ParameterContextUpdateStepDTO> {
     private ParameterContextDTO parameterContext;
-    private Set<AffectedComponentEntity> affectedComponents;
+    private Set<AffectedComponentEntity> referencingComponents;
 
     @ApiModelProperty(value = "The Parameter Context that is being operated on. This may not be populated until the request has successfully completed.", readOnly = true)
     public ParameterContextDTO getParameterContext() {
@@ -36,12 +36,12 @@ public class ParameterContextUpdateRequestDTO extends AsynchronousRequestDTO<Par
         this.parameterContext = parameterContext;
     }
 
-    @ApiModelProperty(value = "The components that are affected by the update.", readOnly = true)
-    public Set<AffectedComponentEntity> getAffectedComponents() {
-        return affectedComponents;
+    @ApiModelProperty(value = "The components that are referenced by the update.", readOnly = true)
+    public Set<AffectedComponentEntity> getReferencingComponents() {
+        return referencingComponents;
     }
 
-    public void setAffectedComponents(final Set<AffectedComponentEntity> affectedComponents) {
-        this.affectedComponents = affectedComponents;
+    public void setReferencingComponents(final Set<AffectedComponentEntity> referencingComponents) {
+        this.referencingComponents = referencingComponents;
     }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessGroupNameDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessGroupNameDTO.java
new file mode 100644
index 0000000..dc5190d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessGroupNameDTO.java
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+package org.apache.nifi.web.api.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType(name="processGroupName")
+public class ProcessGroupNameDTO {
+    private String id;
+    private String name;
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    @ApiModelProperty("The ID of the Process Group")
+    public String getId() {
+        return id;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @ApiModelProperty("The name of the Process Group, or the ID of the Process Group if the user does not have the READ policy for the Process Group")
+    public String getName() {
+        return name;
+    }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AffectedComponentEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AffectedComponentEntity.java
index efb7505..737d9c0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AffectedComponentEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AffectedComponentEntity.java
@@ -16,7 +16,9 @@
  */
 package org.apache.nifi.web.api.entity;
 
+import io.swagger.annotations.ApiModelProperty;
 import org.apache.nifi.web.api.dto.AffectedComponentDTO;
+import org.apache.nifi.web.api.dto.ProcessGroupNameDTO;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
@@ -24,10 +26,11 @@ import javax.xml.bind.annotation.XmlRootElement;
  * A serialized representation of this class can be placed in the entity body of a response to the API.
  * This particular entity holds a reference to component that references a variable.
  */
-@XmlRootElement(name = "affectComponentEntity")
+@XmlRootElement(name = "affectedComponentEntity")
 public class AffectedComponentEntity extends ComponentEntity implements Permissible<AffectedComponentDTO> {
 
     private AffectedComponentDTO component;
+    private ProcessGroupNameDTO processGroup;
 
     /**
      * @return variable referencing components that is being serialized
@@ -42,6 +45,15 @@ public class AffectedComponentEntity extends ComponentEntity implements Permissi
         this.component = component;
     }
 
+    @ApiModelProperty("The Process Group that the component belongs to")
+    public ProcessGroupNameDTO getProcessGroup() {
+        return processGroup;
+    }
+
+    public void setProcessGroup(ProcessGroupNameDTO processGroup) {
+        this.processGroup = processGroup;
+    }
+
     @Override
     public String toString() {
         return component == null ? "AffectedComponent[No Component]" : component.toString();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java
index c157c8b..a020a48 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/CurrentUserEntity.java
@@ -38,6 +38,7 @@ public class CurrentUserEntity extends Entity {
     private PermissionsDTO controllerPermissions;
     private PermissionsDTO policiesPermissions;
     private PermissionsDTO systemPermissions;
+    private PermissionsDTO parameterContextPermissions;
     private PermissionsDTO restrictedComponentsPermissions;
     private Set<ComponentRestrictionPermissionDTO> componentRestrictionPermissions;
 
@@ -140,6 +141,18 @@ public class CurrentUserEntity extends Entity {
     }
 
     /**
+     * @return permissions for accessing parameter contexts
+     */
+    @ApiModelProperty("Permissions for accessing parameter contexts.")
+    public PermissionsDTO getParameterContextPermissions() {
+        return parameterContextPermissions;
+    }
+
+    public void setParameterContextPermissions(PermissionsDTO parameterContextPermissions) {
+        this.parameterContextPermissions = parameterContextPermissions;
+    }
+
+    /**
      * @return permissions for accessing the restricted components
      */
     @ApiModelProperty("Permissions for accessing restricted components. Note: the read permission are not used and will always be false.")
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterContextUpdateEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterContextUpdateEndpointMerger.java
index 3ea4aac..2d7de0d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterContextUpdateEndpointMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ParameterContextUpdateEndpointMerger.java
@@ -66,7 +66,7 @@ public class ParameterContextUpdateEndpointMerger extends AbstractSingleEntityEn
             clientUpdateRequestDto.setPercentCompleted(Math.min(clientUpdateRequestDto.getPercentCompleted(), updateRequestDto.getPercentCompleted()));
 
             // Merge the Affected Components.
-            for (final AffectedComponentEntity entity : requestEntity.getRequest().getAffectedComponents()) {
+            for (final AffectedComponentEntity entity : requestEntity.getRequest().getReferencingComponents()) {
                 final AffectedComponentEntity mergedAffectedComponentEntity = affectedComponentEntities.get(entity.getId());
                 if (mergedAffectedComponentEntity == null) {
                     affectedComponentEntities.put(entity.getId(), entity);
@@ -81,7 +81,7 @@ public class ParameterContextUpdateEndpointMerger extends AbstractSingleEntityEn
         entityMap.forEach( (nodeId, entity) -> contextDtoMap.put(nodeId, entity.getRequest().getParameterContext()));
 
         ParameterContextMerger.merge(clientUpdateRequestDto.getParameterContext(), contextDtoMap);
-        clientUpdateRequestDto.setAffectedComponents(new HashSet<>(affectedComponentEntities.values()));
+        clientUpdateRequestDto.setReferencingComponents(new HashSet<>(affectedComponentEntities.values()));
     }
 
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AffectedComponentEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AffectedComponentEntityMerger.java
index 60a605d..1e66cbb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AffectedComponentEntityMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AffectedComponentEntityMerger.java
@@ -95,6 +95,10 @@ public class AffectedComponentEntityMerger {
                 } else {
                     affectedComponent.setPermissions(permissions);
                     affectedComponent.setComponent(null);
+
+                    if (affectedComponent.getProcessGroup() != null) {
+                        affectedComponent.getProcessGroup().setName(affectedComponent.getProcessGroup().getId());
+                    }
                 }
             }
         }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index da5ada2..03cdc31 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -1277,7 +1277,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
             }
 
             final Parameter parameter = parameterOption.get();
-            final boolean updated = !Objects.equals(updatedValue, parameter.getValue());
+            final boolean valueUpdated = !Objects.equals(updatedValue, parameter.getValue());
+            final boolean descriptionUpdated = parameterDto.getDescription() != null && !parameterDto.getDescription().equals(parameter.getDescriptor().getDescription());
+            final boolean updated = valueUpdated || descriptionUpdated;
             if (updated) {
                 updatedParameters.add(parameterName);
             }
@@ -4004,6 +4006,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
         entity.setControllerPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getController()));
         entity.setPoliciesPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getPolicies()));
         entity.setSystemPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getSystem()));
+        entity.setParameterContextPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getParameterContexts()));
         entity.setCanVersionFlows(CollectionUtils.isNotEmpty(flowRegistryClient.getRegistryIdentifiers()));
 
         entity.setRestrictedComponentsPermissions(dtoFactory.createPermissionsDto(authorizableLookup.getRestrictedComponents()));
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
index 9a0805d..63fcad6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
@@ -802,7 +802,7 @@ public class ParameterContextResource extends ApplicationResource {
         final Consumer<AsynchronousWebRequest<ParameterContextEntity, ParameterContextEntity>> updateTask = asyncRequest -> {
             try {
                 final ParameterContextEntity updatedParameterContextEntity = updateParameterContext(asyncRequest, requestWrapper.getComponentLifecycle(), requestWrapper.getExampleUri(),
-                    requestWrapper.getAffectedComponents(), requestWrapper.isReplicateRequest(), requestRevision, requestWrapper.getParameterContextEntity());
+                    requestWrapper.getReferencingComponents(), requestWrapper.isReplicateRequest(), requestRevision, requestWrapper.getParameterContextEntity());
 
                 asyncRequest.markStepComplete(updatedParameterContextEntity);
             } catch (final ResumeFlowException rfe) {
@@ -1162,7 +1162,7 @@ public class ParameterContextResource extends ApplicationResource {
             }
         }
 
-        updateRequestDto.setAffectedComponents(new HashSet<>(affectedComponents.values()));
+        updateRequestDto.setReferencingComponents(new HashSet<>(affectedComponents.values()));
 
         // Populate the Affected Components
         final ParameterContextEntity contextEntity = serviceFacade.getParameterContext(asyncRequest.getComponentId(), NiFiUserUtils.getNiFiUser());
@@ -1211,7 +1211,7 @@ public class ParameterContextResource extends ApplicationResource {
             return exampleUri;
         }
 
-        public Set<AffectedComponentEntity> getAffectedComponents() {
+        public Set<AffectedComponentEntity> getReferencingComponents() {
             return affectedComponents;
         }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsyncRequestManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsyncRequestManager.java
index 4ac11bf..5a6eec5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsyncRequestManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/concurrent/AsyncRequestManager.java
@@ -144,10 +144,11 @@ public class AsyncRequestManager<R, T> implements RequestManager<R, T> {
         }
 
         if (!request.isComplete()) {
-            throw new IllegalStateException("Cannot remove the request because it is not yet complete");
+            request.cancel();
         }
 
-        return requests.remove(key);
+        requests.remove(key);
+        return request;
     }
 
     @Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 3adc531..8e94a5b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -1943,12 +1943,17 @@ public final class DtoFactory {
 
         final ProcessorDTO processorDto = processorEntity.getComponent();
         final AffectedComponentDTO componentDto = new AffectedComponentDTO();
-        componentDto.setId(processorDto.getId());
-        componentDto.setName(processorDto.getName());
-        componentDto.setProcessGroupId(processorDto.getParentGroupId());
-        componentDto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_PROCESSOR);
-        componentDto.setState(processorDto.getState());
-        componentDto.setValidationErrors(processorDto.getValidationErrors());
+        if (componentDto == null) {
+            componentDto.setId(processorEntity.getId());
+            componentDto.setName(processorEntity.getId());
+        } else {
+            componentDto.setId(processorDto.getId());
+            componentDto.setName(processorDto.getName());
+            componentDto.setProcessGroupId(processorDto.getParentGroupId());
+            componentDto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_PROCESSOR);
+            componentDto.setState(processorDto.getState());
+            componentDto.setValidationErrors(processorDto.getValidationErrors());
+        }
         component.setComponent(componentDto);
 
         return component;
@@ -1969,12 +1974,18 @@ public final class DtoFactory {
 
         final PortDTO portDto = portEntity.getComponent();
         final AffectedComponentDTO componentDto = new AffectedComponentDTO();
-        componentDto.setId(portDto.getId());
-        componentDto.setName(portDto.getName());
-        componentDto.setProcessGroupId(portDto.getParentGroupId());
-        componentDto.setReferenceType(referenceType);
-        componentDto.setState(portDto.getState());
-        componentDto.setValidationErrors(portDto.getValidationErrors());
+        if (componentDto == null) {
+            componentDto.setId(portEntity.getId());
+            componentDto.setName(portEntity.getId());
+        } else {
+            componentDto.setId(portDto.getId());
+            componentDto.setName(portDto.getName());
+            componentDto.setProcessGroupId(portDto.getParentGroupId());
+            componentDto.setReferenceType(referenceType);
+            componentDto.setState(portDto.getState());
+            componentDto.setValidationErrors(portDto.getValidationErrors());
+        }
+
         component.setComponent(componentDto);
 
         return component;
@@ -1995,12 +2006,19 @@ public final class DtoFactory {
 
         final ControllerServiceDTO serviceDto = serviceEntity.getComponent();
         final AffectedComponentDTO componentDto = new AffectedComponentDTO();
-        componentDto.setId(serviceDto.getId());
-        componentDto.setName(serviceDto.getName());
-        componentDto.setProcessGroupId(serviceDto.getParentGroupId());
-        componentDto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_CONTROLLER_SERVICE);
-        componentDto.setState(serviceDto.getState());
-        componentDto.setValidationErrors(serviceDto.getValidationErrors());
+        if (serviceDto == null) {
+            componentDto.setId(serviceEntity.getId());
+            componentDto.setName(serviceEntity.getId());
+            componentDto.setProcessGroupId(serviceEntity.getParentGroupId());
+        } else {
+            componentDto.setId(serviceDto.getId());
+            componentDto.setName(serviceDto.getName());
+            componentDto.setProcessGroupId(serviceDto.getParentGroupId());
+            componentDto.setReferenceType(AffectedComponentDTO.COMPONENT_TYPE_CONTROLLER_SERVICE);
+            componentDto.setState(serviceDto.getState());
+            componentDto.setValidationErrors(serviceDto.getValidationErrors());
+        }
+
         component.setComponent(componentDto);
 
         return component;
@@ -2707,7 +2725,26 @@ public final class DtoFactory {
         final AffectedComponentDTO affectedComponent = createAffectedComponentDto(componentNode);
         final PermissionsDTO permissions = createPermissionsDto(componentNode);
         final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(componentNode.getIdentifier()));
-        return entityFactory.createAffectedComponentEntity(affectedComponent, revision, permissions);
+
+        final ProcessGroupNameDTO groupNameDto = new ProcessGroupNameDTO();
+        groupNameDto.setId(componentNode.getProcessGroupIdentifier());
+        groupNameDto.setName(componentNode.getProcessGroupIdentifier());
+
+        ProcessGroup processGroup = null;
+        if (componentNode instanceof ProcessorNode) {
+            processGroup = ((ProcessorNode) componentNode).getProcessGroup();
+        } else if (componentNode instanceof ControllerServiceNode) {
+            processGroup = ((ControllerServiceNode) componentNode).getProcessGroup();
+        }
+
+        if (processGroup != null) {
+            final boolean authorized = processGroup.isAuthorized(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+            if (authorized) {
+                groupNameDto.setName(processGroup.getName());
+            }
+        }
+
+        return entityFactory.createAffectedComponentEntity(affectedComponent, revision, permissions, groupNameDto);
     }
 
     public VariableRegistryDTO createVariableRegistryDto(final ProcessGroup processGroup, final RevisionManager revisionManager) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index 915ad2c..b55bfdb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -358,7 +358,8 @@ public final class EntityFactory {
         return entity;
     }
 
-    public AffectedComponentEntity createAffectedComponentEntity(final AffectedComponentDTO dto, final RevisionDTO revision, final PermissionsDTO permissions) {
+    public AffectedComponentEntity createAffectedComponentEntity(final AffectedComponentDTO dto, final RevisionDTO revision, final PermissionsDTO permissions,
+                                                                 final ProcessGroupNameDTO processGroupNameDto) {
         final AffectedComponentEntity entity = new AffectedComponentEntity();
         entity.setRevision(revision);
         if (dto != null) {
@@ -369,6 +370,8 @@ public final class EntityFactory {
                 entity.setComponent(dto);
             }
         }
+
+        entity.setProcessGroup(processGroupNameDto);
         return entity;
     }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
index 9ad06a3..bb5ec9a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardParameterContextDAO.java
@@ -81,10 +81,10 @@ public class StandardParameterContextDAO implements ParameterContextDAO {
 
             final boolean deletion = parameterDto.getDescription() == null && parameterDto.getSensitive() == null && parameterDto.getValue() == null;
             if (deletion) {
-                parameterMap.put(parameterDto.getName(), null);
+                parameterMap.put(parameterDto.getName().trim(), null);
             } else {
                 final Parameter parameter = createParameter(parameterDto);
-                parameterMap.put(parameterDto.getName(), parameter);
+                parameterMap.put(parameterDto.getName().trim(), parameter);
             }
         }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java
index 23825e7..934e927 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/util/ClusterReplicationComponentLifecycle.java
@@ -406,7 +406,11 @@ public class ClusterReplicationComponentLifecycle implements ComponentLifecycle
             boolean allReachedDesiredState = true;
             for (final ControllerServiceEntity serviceEntity : serviceEntities) {
                 final ControllerServiceDTO serviceDto = serviceEntity.getComponent();
-                if (!affectedServices.containsKey(serviceDto.getId())) {
+                if (serviceDto == null) {
+                    continue;
+                }
+
+                if (!serviceIds.contains(serviceDto.getId())) {
                     continue;
                 }
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
index ef283de..3e27542 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
@@ -394,6 +394,40 @@
     </build>
     <profiles>
         <profile>
+            <id>development-mode</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <build>
+                <plugins>
+                    <!--
+                        Speed up build time by excluding node, npm, and any node_modules from `mvn clean` since the front-end-maven plugin uses these
+                        directories as cache.
+                    -->
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-clean-plugin</artifactId>
+                        <version>3.0.0</version>
+                        <configuration>
+                            <excludeDefaultDirectories>true</excludeDefaultDirectories>
+                            <filesets>
+                                <fileset>
+                                    <directory>${project.build.directory}</directory>
+                                    <includes>
+                                        <include>**</include>
+                                    </includes>
+                                    <excludes>
+                                        <exclude>frontend-working-directory/node/**/*</exclude>
+                                        <exclude>frontend-working-directory/node_modules/**/*</exclude>
+                                    </excludes>
+                                </fileset>
+                            </filesets>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+        <profile>
             <id>minify-and-compress</id>
             <activation>
                 <activeByDefault>true</activeByDefault>
@@ -486,6 +520,7 @@
                                                 <include>${staging.dir}/js/nf/nf-status-history.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-queue-listing.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-policy-management.js</include>
+                                                <include>${staging.dir}/js/nf/canvas/nf-parameter-contexts.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-actions.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-canvas.js</include>
                                                 <include>${staging.dir}/js/nf/canvas/nf-canvas-error-handler.js</include>
@@ -688,12 +723,14 @@
                                                 <include>${staging.dir}/css/new-port-dialog.css</include>
                                                 <include>${staging.dir}/css/new-controller-service-dialog.css</include>
                                                 <include>${staging.dir}/css/new-reporting-task-dialog.css</include>
+                                                <include>${staging.dir}/css/new-parameter-context-dialog.css</include>
                                                 <include>${staging.dir}/css/graph.css</include>
                                                 <include>${staging.dir}/css/header.css</include>
                                                 <include>${staging.dir}/css/main.css</include>
                                                 <include>${staging.dir}/css/banner.css</include>
                                                 <include>${staging.dir}/css/navigation.css</include>
                                                 <include>${staging.dir}/css/flow-status.css</include>
+                                                <include>${staging.dir}/css/parameter-contexts.css</include>
                                                 <include>${staging.dir}/css/settings.css</include>
                                                 <include>${staging.dir}/css/about.css</include>
                                                 <include>${staging.dir}/css/status-history.css</include>
@@ -922,7 +959,7 @@
             <groupId>org.eclipse.jetty</groupId>
             <artifactId>apache-jstl</artifactId>
             <scope>provided</scope>
-        </dependency>        
+        </dependency>
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
index 40a3b1f..5cc9f3f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
@@ -62,6 +62,7 @@ nf.canvas.script.tags=<script type="text/javascript" src="js/nf/nf-ng-bridge.js?
 <script type="text/javascript" src="js/nf/nf-status-history.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-queue-listing.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-policy-management.js?${project.version}"></script>\n\
+<script type="text/javascript" src="js/nf/canvas/nf-parameter-contexts.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-actions.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-canvas.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-canvas-error-handler.js?${project.version}"></script>\n\
@@ -91,4 +92,4 @@ nf.canvas.script.tags=<script type="text/javascript" src="js/nf/nf-ng-bridge.js?
 <script type="text/javascript" src="js/nf/nf-ng-app-config.js?${project.version}"></script>\n\
 <script type="text/javascript" src="js/nf/canvas/nf-canvas-bootstrap.js?${project.version}"></script>
 nf.canvas.style.tags=<link rel="stylesheet" href="css/canvas.css?${project.version}" type="text/css" />\n\
-<link rel="stylesheet" href="css/common-ui.css?${project.version}" type="text/css" />
\ No newline at end of file
+<link rel="stylesheet" href="css/common-ui.css?${project.version}" type="text/css" />
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
index cee2c5e..90e5888 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
@@ -110,6 +110,7 @@
         <jsp:include page="/WEB-INF/partials/canvas/enable-controller-service-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/new-controller-service-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/new-reporting-task-dialog.jsp"/>
+        <jsp:include page="/WEB-INF/partials/canvas/new-parameter-context-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/new-processor-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/new-port-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/new-process-group-dialog.jsp"/>
@@ -133,6 +134,7 @@
         </div>
         <jsp:include page="/WEB-INF/partials/canvas/navigation.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/settings-content.jsp"/>
+        <jsp:include page="/WEB-INF/partials/canvas/parameter-contexts-content.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/shell.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/controller-service-configuration.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/reporting-task-configuration.jsp"/>
@@ -160,4 +162,4 @@
         <div id="context-menu" class="context-menu unselectable"></div>
         <span id="nifi-content-viewer-url" class="hidden"></span>
     </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
index a242c53..928e8f5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
@@ -132,6 +132,12 @@
                             <i class="fa fa-wrench"></i>Controller Settings
                         </a>
                     </md-menu-item>
+                    <md-menu-item layout-align="space-around center">
+                        <a id="parameter-contexts-link"
+                           ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.parameterContexts.shell.launch();">
+                            <i class="fa"></i>Parameter Contexts
+                        </a>
+                    </md-menu-item>
                     <md-menu-item ng-if="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.cluster.visible();"
                                   layout-align="space-around center">
                         <a id="cluster-link"
@@ -185,4 +191,4 @@
             </md-menu>
         </div>
     </div>
-</md-toolbar>
\ No newline at end of file
+</md-toolbar>
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
new file mode 100644
index 0000000..bc264bd
--- /dev/null
+++ 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
@@ -0,0 +1,146 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="parameter-context-dialog" layout="column" class="hidden">
+    <div id="parameter-context-status-bar"></div>
+    <div class="parameter-context-tab-container dialog-content">
+        <div id="parameter-context-tabs" class="tab-container"></div>
+        <div id="parameter-context-tabs-content">
+            <div id="parameter-context-standard-settings-tab-content" class="configuration-tab">
+                <div class="settings-left">
+                    <div class="setting">
+                        <div class="setting-name">Name</div>
+                        <div id="parameter-context-name-container" class="setting-field">
+                            <input type="text" id="parameter-context-name" name="parameter-context-name"/>
+                        </div>
+                    </div>
+                    <div class="setting">
+                        <div class="setting-name">Description</div>
+                        <div class="setting-field parameter-context-description-container">
+                            <textarea id="parameter-context-description-field" rows="6"></textarea>
+                        </div>
+                        <div class="clear"></div>
+                    </div>
+                </div>
+                <div class="spacer">&nbsp;</div>
+                <div class="settings-right">
+                </div>
+            </div>
+            <div id="parameter-context-parameters-tab-content" class="configuration-tab">
+                <div class="settings-left">
+                    <div>
+                        <div id="add-parameter"><button class="button fa fa-plus"></button></div>
+                        <div class="clear"></div>
+                    </div>
+                    <div id="parameter-table"></div>
+                    <div id="parameter-context-update-status" class="hidden">
+                        <div class="setting">
+                            <div class="setting-name">
+                                Steps to update parameters
+                            </div>
+                            <div class="setting-field">
+                                <ol id="parameter-context-update-steps"></ol>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="spacer">&nbsp;</div>
+                <div id="parameter-context-usage" class="settings-right">
+                    <div class="setting">
+                        <div class="setting-name">
+                            Parameter
+                        </div>
+                        <div class="setting-field">
+                            <div id="parameter-referencing-components-context"></div>
+                        </div>
+                    </div>
+                    <div class="setting">
+                        <div class="setting-name">
+                            Referencing Components
+                            <div class="fa fa-question-circle" alt="Info" title="Components referencing this parameter grouped by process group."></div>
+                        </div>
+                        <div id="parameter-referencing-components-container" class="setting-field">
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="parameter-dialog" class="dialog cancellable hidden">
+    <div class="dialog-content">
+        <div class="setting">
+            <div class="setting-name">Name</div>
+            <div class="setting-field new-parameter-name-container">
+                <input id="parameter-name" type="text"/>
+            </div>
+            <div class="clear"></div>
+        </div>
+        <div class="setting">
+            <div class="setting-name">Value</div>
+            <div class="setting-field new-parameter-value-container">
+                <textarea id="parameter-value-field"></textarea>
+                <div class="string-check-container">
+                    <div id="parameter-set-empty-string-field" class="nf-checkbox string-check checkbox-unchecked"></div>
+                    <span class="string-check-label nf-checkbox-label">Set empty string</span>
+                </div>
+            </div>
+            <div class="clear"></div>
+        </div>
+        <div class="setting">
+            <div class="setting-field new-parameter-sensitive-value-container">
+                <div class="setting-name">Sensitive value</div>
+                <input id="parameter-sensitive-radio-button" type="radio" name="sensitive" value="sensitive"/> Yes
+                <input id="parameter-not-sensitive-radio-button" type="radio" name="sensitive" value="plain" checked="checked" style="margin-left: 20px;"/> No
+            </div>
+            <div class="clear"></div>
+        </div>
+        <div class="setting">
+            <div class="setting-name">Description</div>
+            <div class="setting-field new-parameter-description-container">
+                <textarea id="parameter-description-field" rows="6"></textarea>
+            </div>
+            <div class="clear"></div>
+        </div>
+    </div>
+</div>
+<div id="referencing-components-template" class="referencing-components-template hidden clear">
+    <div class="setting">
+        <div class="setting-name">
+            Referencing Processors
+        </div>
+        <div class="setting-field">
+            <ul class="parameter-context-referencing-processors"></ul>
+        </div>
+    </div>
+    <div class="setting">
+        <div class="setting-name">
+            Referencing Controller Services
+        </div>
+        <div class="setting-field">
+            <ul class="parameter-context-referencing-controller-services"></ul>
+        </div>
+    </div>
+    <div class="setting">
+        <div class="setting-name">
+            Unauthorized referencing components
+        </div>
+        <div class="setting-field">
+            <ul class="parameter-context-referencing-unauthorized-components"></ul>
+        </div>
+    </div>
+</div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/parameter-contexts-content.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/parameter-contexts-content.jsp
new file mode 100644
index 0000000..f177871
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/parameter-contexts-content.jsp
@@ -0,0 +1,32 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="parameter-contexts" class="hidden">
+    <button id="new-parameter-context" class="add-button fa fa-plus" title="Create a new parameter context" style="display: block;"></button>
+    <div id="parameter-contexts-header-text" class="parameter-contexts-header-text">NiFi Parameter Contexts</div>
+    <div class="parameter-contexts-container">
+        <div id="parameter-contexts-table" class="parameter-contexts-table"></div>
+    </div>
+    <div id="parameter-contexts-refresh-container">
+        <button id="parameter-contexts-refresh-button" class="refresh-button pointer fa fa-refresh" title="Refresh"></button>
+        <div id="parameter-contexts-last-refreshed-container" class="last-refreshed-container">
+            Last updated:&nbsp;<span id="parameter-contexts-last-refreshed" class="value-color"></span>
+        </div>
+        <div id="parameter-contexts-loading-container" class="loading-container"></div>
+        <div class="clear"></div>
+    </div>
+</div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
index 946c53d..024c7ba 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
@@ -74,6 +74,16 @@
                 </div>
                 <div class="clear"></div>
             </div>
+            <div id="policy-selected-parameter-context-container" class="hidden policy-selected-component-container">
+                <div class="policy-selected-component-type-icon">
+                    <i class="icon icon-drop"></i>
+                </div>
+                <div class="policy-selected-component-details-container">
+                    <div class="policy-selected-component-name"></div>
+                    <div class="policy-selected-component-type">Parameter Context</div>
+                </div>
+                <div class="clear"></div>
+            </div>
             <div id="selected-policy-component-id" class="hidden"></div>
             <div id="selected-policy-component-type" class="hidden"></div>
             <div id="component-policy-target"></div>
@@ -94,4 +104,4 @@
         <div id="restriction-message" class="hidden">Only listing restriction specific users. Users with permission "regardless of restrictions" not shown but are also allowed.</div>
         <div class="clear"></div>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/process-group-configuration.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/process-group-configuration.jsp
index 6fd904d..b21ba0c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/process-group-configuration.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/process-group-configuration.jsp
@@ -33,7 +33,16 @@
                             <input type="text" id="process-group-name" class="setting-input"/>
                         </div>
                         <div class="read-only setting-field">
-                            <span id="read-only-process-group-name"></span>
+                            <span id="read-only-process-group-name" class="unset"></span>
+                        </div>
+                    </div>
+                    <div class="setting">
+                        <div class="setting-name">Process group parameter context</div>
+                        <div class="editable setting-field">
+                            <div id="process-group-parameter-context-combo"></div>
+                        </div>
+                        <div class="read-only setting-field">
+                            <span id="read-only-process-group-parameter-context" class="unset">Unauthorized</span>
                         </div>
                     </div>
                     <div class="setting">
@@ -42,7 +51,7 @@
                             <textarea id="process-group-comments" class="setting-input"></textarea>
                         </div>
                         <div class="read-only setting-field">
-                            <span id="read-only-process-group-comments"></span>
+                            <span id="read-only-process-group-comments" class="unset"></span>
                         </div>
                     </div>
                     <div class="editable settings-buttons">
@@ -65,4 +74,4 @@
         <div id="flow-cs-availability" class="hidden">Listed services are available to all descendant Processors and services of this Process Group.</div>
         <div class="clear"></div>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/variable-configuration.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/variable-configuration.jsp
index 4bbd9c8..054de83 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/variable-configuration.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/variable-configuration.jsp
@@ -48,7 +48,7 @@
                     Variables
                 </div>
                 <div class="setting-field">
-                    <div id="affected-components-context"></div>
+                    <div id="variable-affected-components-context"></div>
                 </div>
             </div>
             <div class="setting">
@@ -91,4 +91,4 @@
             </div>
         </div>
     </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
index 7a9c1f6..8159795 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/canvas.css
@@ -34,16 +34,18 @@
 @import url(new-port-dialog.css);
 @import url(new-controller-service-dialog.css);
 @import url(new-reporting-task-dialog.css);
+@import url(new-parameter-context-dialog.css);
 @import url(graph.css);
 @import url(header.css);
 @import url(main.css);
 @import url(banner.css);
 @import url(navigation.css);
 @import url(flow-status.css);
+@import url(parameter-contexts.css);
 @import url(settings.css);
 @import url(about.css);
 @import url(message-pane.css);
 @import url(common-ui.css);
 @import url(status-history.css);
 @import url(../fonts/flowfont/flowfont.css);
-@import url(../assets/font-awesome/css/font-awesome.css);
\ No newline at end of file
+@import url(../assets/font-awesome/css/font-awesome.css);
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 82ffc38..eb71a9e 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
@@ -377,3 +377,10 @@ div.variable-step {
     width: 300px;
     margin-bottom: 2px;
 }
+
+div.parameter-context-step {
+    width: 16px;
+    height: 16px;
+    background-color: transparent;
+    float: right;
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/new-parameter-context-dialog.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/new-parameter-context-dialog.css
new file mode 100644
index 0000000..2ccb910
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/new-parameter-context-dialog.css
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+/*
+    Parameter context dialog
+ */
+
+#parameter-context-dialog {
+    width: 850px;
+    height: 575px;
+}
+
+#parameter-context-dialog div.settings-left {
+    float: left;
+    width: 65%;
+}
+
+#parameter-context-dialog div.settings-right {
+    float: left;
+    width: 33%;
+    height: 100%;
+    position: absolute;
+    left: calc(65% - -15px);
+}
+
+#parameter-context-standard-settings-tab-content,
+#parameter-context-parameters-tab-content {
+    bottom: 10px;
+}
+
+#parameter-table {
+    height: 370px;
+}
+
+#add-parameter {
+    float: right;
+    margin-bottom: 4px;
+    font-size: 16px;
+    text-transform: uppercase;
+}
+
+#parameter-context-update-steps li {
+    width: 300px;
+    margin-bottom: 2px;
+}
+
+#parameter-value-field {
+    height: 54px;
+}
+
+#parameter-description-field {
+    height: 85px;
+}
+
+#parameter-context-description-field {
+    height: 85px;
+}
+
+#parameter-referencing-components-container {
+    position: absolute;
+    bottom: 10px;
+    top: 75px;
+    width: calc(100% - 5px);
+    overflow: auto;
+    border: 0 solid #CCCCCC;
+    overflow: auto;
+    padding: 2px;
+}
+
+.referencing-components-template {
+    position: relative;
+    left: 15px;
+    width: calc(100% - 15px);
+}
+
+/*
+    Parameter creation/configuration dialog
+ */
+
+#parameter-dialog {
+    width: 470px;
+    height: 470px;
+}
+
+span.parameter-context-referencing-component-name {
+    margin-left: 5px;
+    margin-right: 5px;
+    max-width: calc(100% - 10px);
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/parameter-contexts.css
similarity index 57%
copy from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css
copy to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/parameter-contexts.css
index dcafd5d..ef512e4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/parameter-contexts.css
@@ -15,8 +15,7 @@
  * limitations under the License.
  */
 
-#process-group-configuration {
-    display: none;
+#parameter-contexts {
     position: absolute;
     top: 0px;
     bottom: 0px;
@@ -25,34 +24,61 @@
     overflow: auto;
 }
 
-#add-process-group-configuration-controller-service {
-    float: right;
+#new-parameter-context {
+    position: absolute;
+    right: 0;
+    top: 26px;
 }
 
-/* settings tabs */
-
-#process-group-configuration-tabs {
-    float: left;
+div.parameter-contexts-header-text {
+    height: 28px;
+    font-size: 18px;
+    font-weight: bold;
+    color: #728E9B;
+    margin-bottom: 30px;
 }
 
-#process-group-configuration-tabs-content {
-    top: 53px;
+div.parameter-contexts-container {
+    padding: 0;
+    position: absolute;
+    top: 58px;
     bottom: 52px;
+    left: 0px;
+    right: 0px;
 }
 
-#process-group-refresh-container {
+#parameter-contexts-refresh-container {
     position: absolute;
     bottom: 20px;
-    left: 0px;
     right: 0px;
+    left: 0px;
     height: 32px;
 }
 
-#process-group-configuration-refresh-button {
+div.parameter-contexts-table {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    bottom: 20px;
+    right: 0px;
+    min-height: 150px;
+}
+
+#parameter-contexts-table div.slick-viewport {
+    overflow-x: hidden !important;
+}
+
+span.sorted {
+    text-decoration: underline;
+}
+
+div.parameter-contexts-refresh-button {
+    height: 24px;
+    width: 26px;
     float: left;
 }
 
-#process-group-configuration-loading-container {
+#parameter-contexts-loading-container {
     float: left;
     width: 16px;
     height: 16px;
@@ -61,34 +87,6 @@
     margin-left: 3px;
 }
 
-#process-group-configuration-last-refreshed {
-    color: #775351;
+#parameter-contexts-last-refreshed {
     font-weight: 500;
 }
-
-#flow-cs-availability {
-    float: right;
-    margin-top: 8px;
-    color: #775351;
-    font-family: Roboto;
-    font-size: 13px;
-}
-
-/* general */
-
-#general-process-group-configuration input, #general-process-group-configuration textarea {
-    width: 350px;
-    vertical-align: middle;
-}
-
-#process-group-comments {
-    height: 100px;
-}
-
-#read-only-process-group-comments {
-    white-space: pre-wrap;
-}
-
-#process-group-controller-services-tab-content {
-    top: 32px;
-}
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css
index dcafd5d..ac968b4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/process-group-configuration.css
@@ -81,6 +81,10 @@
     vertical-align: middle;
 }
 
+#process-group-parameter-context-combo {
+    width: 328px;
+}
+
 #process-group-comments {
     height: 100px;
 }
@@ -91,4 +95,4 @@
 
 #process-group-controller-services-tab-content {
     top: 32px;
-}
\ 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/nf/canvas/controllers/nf-ng-canvas-global-menu-controller.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-global-menu-controller.js
index 28d818e..88dc30d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-global-menu-controller.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-global-menu-controller.js
@@ -23,12 +23,13 @@
                 'nf.Common',
                 'nf.QueueListing',
                 'nf.Shell',
+                'nf.ParameterContexts',
                 'nf.PolicyManagement',
                 'nf.ClusterSummary',
                 'nf.ErrorHandler',
                 'nf.Settings',
                 'nf.CanvasUtils'],
-            function ($, nfCommon, nfQueueListing, nfShell, nfPolicyManagement, nfClusterSummary, nfErrorHandler, nfSettings, nfCanvasUtils) {
+            function ($, nfCommon, nfQueueListing, nfShell, nfParameterContexts, nfPolicyManagement, nfClusterSummary, nfErrorHandler, nfSettings, nfCanvasUtils) {
                 return (nf.ng.Canvas.GlobalMenuCtrl = factory($, nfCommon, nfQueueListing, nfShell, nfPolicyManagement, nfClusterSummary, nfErrorHandler, nfSettings, nfCanvasUtils));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
@@ -37,6 +38,7 @@
                 require('nf.Common'),
                 require('nf.QueueListing'),
                 require('nf.Shell'),
+                require('nf.ParameterContexts'),
                 require('nf.PolicyManagement'),
                 require('nf.ClusterSummary'),
                 require('nf.ErrorHandler'),
@@ -47,13 +49,14 @@
             root.nf.Common,
             root.nf.QueueListing,
             root.nf.Shell,
+            root.nf.ParameterContexts,
             root.nf.PolicyManagement,
             root.nf.ClusterSummary,
             root.nf.ErrorHandler,
             root.nf.Settings,
             root.nf.CanvasUtils);
     }
-}(this, function ($, nfCommon, nfQueueListing, nfShell, nfPolicyManagement, nfClusterSummary, nfErrorHandler, nfSettings, nfCanvasUtils) {
+}(this, function ($, nfCommon, nfQueueListing, nfShell, nfParameterContexts, nfPolicyManagement, nfClusterSummary, nfErrorHandler, nfSettings, nfCanvasUtils) {
     'use strict';
 
     return function (serviceProvider) {
@@ -168,6 +171,25 @@
             };
 
             /**
+             * The parameter contexts menu item controller.
+             */
+            this.parameterContexts = {
+
+                /**
+                 * The parameter contexts menu item's shell controller.
+                 */
+                shell: {
+
+                    /**
+                     * Launch the parameter contexts shell.
+                     */
+                    launch: function () {
+                        nfParameterContexts.showParameterContexts();
+                    }
+                }
+            };
+
+            /**
              * The cluster menu item controller.
              */
             this.cluster = {
@@ -432,9 +454,9 @@
             init: function () {
                 this.about.init();
             }
-        }
+        };
 
         var globalMenuCtrl = new GlobalMenuCtrl();
         return globalMenuCtrl;
     };
-}));
\ 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/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
index 0aeaf43..3d0c9d0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
@@ -28,6 +28,7 @@
                 'nf.Client',
                 'nf.ErrorHandler',
                 'nf.Clipboard',
+                'nf.ParameterContexts',
                 'nf.Snippet',
                 'nf.GoTo',
                 'nf.ng.Bridge',
@@ -57,8 +58,8 @@
                 'nf.ComponentVersion',
                 'nf.QueueListing',
                 'nf.StatusHistory'],
-            function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfStorage, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionD [...]
-                return (nf.Actions = factory($, d3, nfCanvasUtils, nfCommon, nfDialog, nfStorage, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfi [...]
+            function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfStorage, nfClient, nfErrorHandler, nfClipboard, nfParameterContexts, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfigu [...]
+                return (nf.Actions = factory($, d3, nfCanvasUtils, nfCommon, nfDialog, nfStorage, nfClient, nfErrorHandler, nfClipboard, nfParameterContexts, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetai [...]
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.Actions =
@@ -71,6 +72,7 @@
                 require('nf.Client'),
                 require('nf.ErrorHandler'),
                 require('nf.Clipboard'),
+                require('nf.ParameterContexts'),
                 require('nf.Snippet'),
                 require('nf.GoTo'),
                 require('nf.ng.Bridge'),
@@ -110,6 +112,7 @@
             root.nf.Client,
             root.nf.ErrorHandler,
             root.nf.Clipboard,
+            root.nf.ParameterContexts,
             root.nf.Snippet,
             root.nf.GoTo,
             root.nf.ng.Bridge,
@@ -140,13 +143,14 @@
             root.nf.QueueListing,
             root.nf.StatusHistory);
     }
-}(this, function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfStorage, nfClient, nfErrorHandler, nfClipboard, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfiguration, nfConnectionDetai [...]
+}(this, function ($, d3, nfCanvasUtils, nfCommon, nfDialog, nfStorage, nfClient, nfErrorHandler, nfClipboard, nfParameterContexts, nfSnippet, nfGoto, nfNgBridge, nfShell, nfVariableRegistry, nfComponentState, nfFlowVersion, nfDraggable, nfBirdseye, nfConnection, nfGraph, nfProcessGroupConfiguration, nfProcessorConfiguration, nfProcessorDetails, nfLabelConfiguration, nfRemoteProcessGroupConfiguration, nfRemoteProcessGroupDetails, nfPortConfiguration, nfPortDetails, nfConnectionConfigurati [...]
     'use strict';
 
     var config = {
         urls: {
             api: '../nifi-api',
-            controller: '../nifi-api/controller'
+            controller: '../nifi-api/controller',
+            parameterContexts: '../nifi-api/parameter-contexts'
         }
     };
 
@@ -1402,6 +1406,27 @@
         },
 
         /**
+         * Opens the parameter context for the specified selection of the current group if the selection is empty.
+         *
+         * @param {selection} selection
+         */
+        openParameterContext: function (selection) {
+            var pcid;
+            if (selection.empty()) {
+                pcid = nfCanvasUtils.getParameterContextId();
+            } else if (selection.size() === 1) {
+                if (nfCanvasUtils.isProcessGroup(selection)) {
+                    var pg = selection.datum();
+                    pcid = pg.component.parameterContext.id;
+                }
+            }
+
+            if (nfCommon.isDefinedAndNotNull(pcid)) {
+                nfParameterContexts.showParameterContext(pcid);
+            }
+        },
+
+        /**
          * Views the state for the specified processor.
          *
          * @param {selection} selection
@@ -1944,4 +1969,4 @@
     };
 
     return nfActions;
-}));
\ 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/nf/canvas/nf-canvas-bootstrap.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
index ac0bb47..485d290 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
@@ -32,6 +32,7 @@
                 'nf.ContextMenu',
                 'nf.QuickSelect',
                 'nf.Shell',
+                'nf.ParameterContexts',
                 'nf.Settings',
                 'nf.Snippet',
                 'nf.Actions',
@@ -82,8 +83,8 @@
                 'nf.ng.Canvas.OperateCtrl',
                 'nf.ng.BreadcrumbsDirective',
                 'nf.ng.DraggableDirective'],
-            function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfContro [...]
-                return factory($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, [...]
+            function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupCo [...]
+                return factory($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProce [...]
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = factory(require('jquery'),
@@ -99,6 +100,7 @@
             require('nf.ContextMenu'),
             require('nf.QuickSelect'),
             require('nf.Shell'),
+            require('nf.ParameterContexts'),
             require('nf.Settings'),
             require('nf.Actions'),
             require('nf.Snippet'),
@@ -163,6 +165,7 @@
             root.nf.ContextMenu,
             root.nf.QuickSelect,
             root.nf.Shell,
+            root.nf.ParameterContexts,
             root.nf.Settings,
             root.nf.Actions,
             root.nf.Snippet,
@@ -214,7 +217,7 @@
             root.nf.ng.BreadcrumbsDirective,
             root.nf.ng.DraggableDirective);
     }
-}(this, function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfiguration, nfController [...]
+}(this, function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfig [...]
 
     var config = {
         urls: {
@@ -343,6 +346,7 @@
                     nfShell.init(nfContextMenu);
                     nfNgBridge.injector.get('headerCtrl').init();
                     nfSettings.init();
+                    nfParameterContexts.init();
                     nfActions.init();
                     nfQueueListing.init();
                     nfVariableRegistry.init();
@@ -369,8 +373,8 @@
                         supportsStatusBar : true,
                         nfActions : nfActions
                     });
-                    // initialize the PG config and invert control of the controllerServices
-                    nfProcessGroupConfiguration.init(nfControllerServices);
+                    // initialize the PG config and invert control of the controllerServices and parameter contexts
+                    nfProcessGroupConfiguration.init(nfControllerServices, nfParameterContexts);
                     nfRemoteProcessGroupConfiguration.init();
                     nfRemoteProcessGroupPorts.init();
                     nfPortConfiguration.init();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 34ac67f..5241263 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -2089,6 +2089,22 @@
         },
 
         /**
+         * Set the parameter context id.
+         *
+         * @argument {string} pcid       The parameter context id
+         */
+        setParameterContextId: function (pcid) {
+            return nfCanvas.setParameterContextId(pcid);
+        },
+
+        /**
+         * Get the parameter context id.
+         */
+        getParameterContextId: function () {
+            return nfCanvas.getParameterContextId();
+        },
+
+        /**
          * Get the group name.
          */
         getGroupName: function () {
@@ -2199,4 +2215,4 @@
         }
     };
     return nfCanvasUtils;
-}));
\ 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/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index c06da21..67b7a54 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -86,6 +86,7 @@
     var polling = false;
     var allowPageRefresh = false;
     var groupId = 'root';
+    var parameterContextId;
     var groupName = null;
     var permissions = null;
     var parentGroupId = null;
@@ -147,8 +148,9 @@
             // get the controller and its contents
             var processGroupFlow = flowResponse.processGroupFlow;
 
-            // set the group details
+            // set the group and parameter context details
             nfCanvas.setGroupId(processGroupFlow.id);
+            nfCanvas.setParameterContextId(processGroupFlow.parameterContextId);
 
             // get the current group name from the breadcrumb
             var breadcrumb = processGroupFlow.breadcrumb;
@@ -218,15 +220,17 @@
     var changeProcessGroup = function (processGroupId, options) {
         // capture the current group id to reset to in case of failure
         var currentProcessGroup = nfCanvas.getGroupId();
+        var currentParameterContext = nfCanvas.getParameterContextId();
 
         // update process group id and attempt to reload
         nfCanvas.setGroupId(processGroupId);
         var processGroupXhr = reloadProcessGroup(options);
 
-        // if the request fails, ensure the process group id is reset
+        // if the request fails, ensure the process group id and parameter context id is reset
         processGroupXhr
             .fail(function (xhr, status, error) {
                 nfCanvas.setGroupId(currentProcessGroup);
+                nfCanvas.setParameterContextId(currentParameterContext);
             });
 
         return processGroupXhr;
@@ -904,6 +908,22 @@
         },
 
         /**
+         * Set the parameter context id.
+         *
+         * @argument {string} pcid       The parameter context id
+         */
+        setParameterContextId: function (pcid) {
+            parameterContextId = pcid;
+        },
+
+        /**
+         * Get the parameter context id.
+         */
+        getParameterContextId: function () {
+            return parameterContextId;
+        },
+
+        /**
          * Set the group id.
          *
          * @argument {string} gi       The group id
@@ -1369,4 +1389,4 @@
     };
 
     return nfCanvas;
-}));
\ 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/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
index 7420758..9cbd904 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
@@ -82,6 +82,21 @@
     };
 
     /**
+     * Determines whether the component in the specified selection has a parameter context.
+     *
+     * @param {selection} selection         The selection of currently selected components
+     */
+    var hasParameterContext = function (selection) {
+        if (selection.empty()) {
+            return !nfCommon.isUndefinedOrNull(nfCanvasUtils.getParameterContextId());
+        } else if (nfCanvasUtils.isProcessGroup(selection)) {
+            var pg = selection.datum();
+            return !nfCommon.isUndefinedOrNull(pg.component.parameterContext.id);
+        }
+        return false;
+    };
+
+    /**
      * Determines whether the component in the specified selection has configuration details.
      *
      * @param {selection} selection         The selection of currently selected components
@@ -832,6 +847,7 @@
         {separator: true},
         {id: 'show-configuration-menu-item', condition: isConfigurable, menuItem: {clazz: 'fa fa-gear', text: 'Configure', action: 'showConfiguration'}},
         {id: 'show-details-menu-item', condition: hasDetails, menuItem: {clazz: 'fa fa-gear', text: 'View configuration', action: 'showDetails'}},
+        {id: 'parameters-menu-item', condition: hasParameterContext, menuItem: {clazz: 'fa', text: 'Parameters', action: 'openParameterContext'}},
         {id: 'variable-registry-menu-item', condition: hasVariables, menuItem: {clazz: 'fa', text: 'Variables', action: 'openVariableRegistry'}},
         {separator: true},
         {id: 'version-menu-item', groupMenuItem: {clazz: 'fa', text: 'Version'}, menuItems: [
@@ -1116,4 +1132,4 @@
     };
 
     return nfContextMenu;
-}));
\ 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/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
new file mode 100644
index 0000000..336d7de
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-parameter-contexts.js
@@ -0,0 +1,2305 @@
+/*
+ * 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',
+                'd3',
+                'nf.Client',
+                'nf.Dialog',
+                'nf.Storage',
+                'nf.Common',
+                'nf.CanvasUtils',
+                'nf.ng.Bridge',
+                'nf.ErrorHandler',
+                'nf.FilteredDialogCommon',
+                'nf.Shell',
+                'nf.ComponentState',
+                'nf.ComponentVersion',
+                '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));
+            });
+    } else if (typeof exports === 'object' && typeof module === 'object') {
+        module.exports = (nf.ParameterContexts =
+            factory(require('jquery'),
+                require('Slick'),
+                require('d3'),
+                require('nf.Client'),
+                require('nf.Dialog'),
+                require('nf.Storage'),
+                require('nf.Common'),
+                require('nf.CanvasUtils'),
+                require('nf.ng.Bridge'),
+                require('nf.ErrorHandler'),
+                require('nf.FilteredDialogCommon'),
+                require('nf.Shell'),
+                require('nf.ComponentState'),
+                require('nf.ComponentVersion'),
+                require('nf.PolicyManagement'),
+                require('nf.Processor'),
+                require('nf.ProcessGroup'),
+                require('nf.ProcessGroupConfiguration')));
+    } else {
+        nf.ParameterContexts = factory(root.$,
+            root.Slick,
+            root.d3,
+            root.nf.Client,
+            root.nf.Dialog,
+            root.nf.Storage,
+            root.nf.Common,
+            root.nf.CanvasUtils,
+            root.nf.ng.Bridge,
+            root.nf.ErrorHandler,
+            root.nf.FilteredDialogCommon,
+            root.nf.Shell,
+            root.nf.ComponentState,
+            root.nf.ComponentVersion,
+            root.nf.PolicyManagement,
+            root.nf.Processor,
+            root.nf.ProcessGroup,
+            root.nf.ProcessGroupConfiguration);
+    }
+}(this, function ($, Slick, d3, nfClient, nfDialog, nfStorage, nfCommon, nfCanvasUtils, nfNgBridge, nfErrorHandler, nfFilteredDialogCommon, nfShell, nfComponentState, nfComponentVersion, nfPolicyManagement, nfProcessor, nfProcessGroup, nfProcessGroupConfiguration) {
+    'use strict';
+
+    var config = {
+        urls: {
+            parameterContexts: '../nifi-api/parameter-contexts'
+        }
+    };
+
+    var parameterContextsGridOptions = {
+        forceFitColumns: true,
+        enableTextSelectionOnCells: true,
+        enableCellNavigation: true,
+        enableColumnReorder: false,
+        autoEdit: false,
+        multiSelect: false,
+        rowHeight: 24
+    };
+
+    var parametersGridOptions = {
+        forceFitColumns: true,
+        enableTextSelectionOnCells: true,
+        enableCellNavigation: true,
+        enableColumnReorder: false,
+        editable: false,
+        enableAddRow: false,
+        autoEdit: false,
+        multiSelect: false,
+        rowHeight: 24
+    };
+
+    /**
+     * Formatter for the name column.
+     *
+     * @param {type} row
+     * @param {type} cell
+     * @param {type} value
+     * @param {type} columnDef
+     * @param {type} dataContext
+     * @returns {String}
+     */
+    var nameFormatter = function (row, cell, value, columnDef, dataContext) {
+        if (!dataContext.permissions.canRead) {
+            return '<span class="blank">' + nfCommon.escapeHtml(dataContext.id) + '</span>';
+        }
+
+        return nfCommon.escapeHtml(dataContext.component.name);
+    };
+
+    /**
+     * Sorts the specified data using the specified sort details.
+     *
+     * @param {object} sortDetails
+     * @param {object} data
+     */
+    var sort = function (sortDetails, data) {
+        // 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] : '';
+                return aString === bString ? 0 : aString > bString ? 1 : -1;
+            } else {
+                if (!a.permissions.canRead && !b.permissions.canRead) {
+                    return 0;
+                }
+                if (a.permissions.canRead) {
+                    return 1;
+                } else {
+                    return -1;
+                }
+            }
+        };
+
+        // perform the sort
+        data.sort(comparer, sortDetails.sortAsc);
+    };
+
+    var lastSelectedId = null;
+
+    /**
+     * Sorts the specified data using the specified sort details.
+     *
+     * @param {object} sortDetails
+     * @param {object} data
+     */
+    var sortParameters = function (sortDetails, data) {
+        // 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] : '';
+                return aString === bString ? 0 : aString > bString ? 1 : -1;
+            }
+        };
+
+        // perform the sort
+        data.sort(comparer, sortDetails.sortAsc);
+    };
+
+    /**
+     * Reset the dialog.
+     */
+    var resetDialog = function () {
+        $('#parameter-context-name').val('');
+        $('#parameter-context-description-field').val('');
+        $('#parameter-table, #add-parameter').show();
+        $('#parameter-context-tabs').show();
+        $('#parameter-context-tabs').find('.tab')[0].click();
+        $('#parameter-context-update-status').hide();
+
+        $('#process-group-parameter').text('');
+        $('#parameter-process-group-id').text('').removeData('revision');
+        $('#parameter-referencing-components-context').removeClass('unset').text('');
+
+        var parameterGrid = $('#parameter-table').data('gridInstance');
+        var parameterData = parameterGrid.getData();
+        parameterData.setItems([]);
+
+        resetUsage();
+
+        // reset the last selected parameter
+        lastSelectedId = null;
+
+        // reset the current parameter context
+        currentParameterContextEntity = null;
+
+        // clean up any tooltips that may have been generated
+        nfCommon.cleanUpTooltips($('#parameter-table'), 'div.fa-question-circle');
+    };
+
+    /**
+     * Marshals the parameters in the table.
+     */
+    var marshalParameters = function () {
+        var parameters = [];
+        var table = $('#parameter-table');
+        var parameterGrid = table.data('gridInstance');
+        var parameterData = parameterGrid.getData();
+        $.each(parameterData.getItems(), function () {
+            var parameter = {
+                'name': this.name
+            };
+
+            // if the parameter has been deleted
+            if (this.hidden === true && this.isNew !== true) {
+                // hidden parameters were removed by the user, clear the value
+                parameters.push({
+                    'parameter': parameter
+                });
+            } else if (this.isNew === true) {
+                parameter['value'] = this.value;
+                parameter['sensitive'] = this.sensitive;
+                parameter['description'] = this.description;
+
+                parameters.push({
+                    'parameter': parameter
+                });
+            } else if (this.isModified === true) {
+                parameter['sensitive'] = this.sensitive;
+
+                // if modified grab what's changed
+                if (this.hasValueChanged) {
+                    parameter['value'] = this.value;
+                }
+
+                parameter['description'] = this.description;
+
+                parameters.push({
+                    'parameter': parameter
+                });
+            }
+        });
+
+        return parameters;
+    };
+
+    /**
+     * Handles outstanding changes.
+     *
+     * @returns {deferred}
+     */
+    var handleOutstandingChanges = function () {
+        if (!$('#parameter-dialog').hasClass('hidden')) {
+            // commit the current edit
+            addNewParameter();
+        }
+
+        return $.Deferred(function (deferred) {
+            if ($('#parameter-context-update-status').is(':visible')) {
+                close();
+                deferred.resolve();
+            } else {
+                var parameters = marshalParameters();
+
+                // if there are no parameters there is nothing to save
+                if ($.isEmptyObject(parameters)) {
+                    close();
+                    deferred.resolve();
+                } else {
+                    // see if those changes should be saved
+                    nfDialog.showYesNoDialog({
+                        headerText: 'Parameters',
+                        dialogContent: 'Save changes before leaving parameter context configuration?',
+                        noHandler: function () {
+                            close();
+                            deferred.resolve();
+                        },
+                        yesHandler: function () {
+                            updateParameterContext(currentParameterContextEntity).done(function () {
+                                deferred.resolve();
+                            }).fail(function () {
+                                deferred.reject();
+                            });
+                        }
+                    });
+                }
+            }
+
+        }).promise();
+    };
+
+    /**
+     * Adds a border to the controller service referencing components if necessary.
+     *
+     * @argument {jQuery} referenceContainer
+     */
+    var updateReferencingComponentsBorder = function (referenceContainer) {
+        // determine if it is too big
+        var tooBig = referenceContainer.get(0).scrollHeight > Math.round(referenceContainer.innerHeight()) ||
+            referenceContainer.get(0).scrollWidth > Math.round(referenceContainer.innerWidth());
+
+        // draw the border if necessary
+        if (referenceContainer.is(':visible') && tooBig) {
+            referenceContainer.css('border-width', '1px');
+        } else {
+            referenceContainer.css('border-width', '0px');
+        }
+    };
+
+    /**
+     * Cancels adding a new parameter context.
+     */
+    var close = function () {
+        $('#parameter-context-dialog').modal('hide');
+    };
+
+    /**
+     * Renders the specified referencing component.
+     *
+     * @param {object} referencingProcessorEntity
+     * @param {jQuery} container
+     */
+    var renderReferencingProcessor = function (referencingProcessorEntity, container) {
+        var referencingProcessorContainer = $('<li class="referencing-component-container"></li>').appendTo(container);
+        var referencingProcessor = referencingProcessorEntity.component;
+
+        // processor state
+        $('<div class="referencing-component-state"></div>').addClass(function () {
+            if (nfCommon.isDefinedAndNotNull(referencingProcessor.state)) {
+                var icon = $(this);
+
+                var state = referencingProcessor.state.toLowerCase();
+                if (state === 'stopped' && !nfCommon.isEmpty(referencingProcessor.validationErrors)) {
+                    state = 'invalid';
+
+                    // build the validation error listing
+                    var list = nfCommon.formatUnorderedList(referencingProcessor.validationErrors);
+
+                    // add tooltip for the warnings
+                    icon.qtip($.extend({},
+                        nfCanvasUtils.config.systemTooltipConfig,
+                        {
+                            content: list
+                        }));
+                }
+
+                return state;
+            } else {
+                return '';
+            }
+        }).appendTo(referencingProcessorContainer);
+
+
+        // processor name
+        $('<span class="parameter-context-referencing-component-name link ellipsis"></span>').prop('title', referencingProcessor.name).text(referencingProcessor.name).on('click', function () {
+            // check if there are outstanding changes
+            handleOutstandingChanges().done(function () {
+                // close the shell
+                $('#shell-dialog').modal('hide');
+
+                // show the component in question
+                nfCanvasUtils.showComponent(referencingProcessorEntity.processGroup.id, referencingProcessor.id);
+            });
+        }).appendTo(referencingProcessorContainer);
+
+        // bulletin
+        $('<div class="referencing-component-bulletins"></div>').addClass(referencingProcessor.id + '-referencing-bulletins').appendTo(referencingProcessorContainer);
+
+        // processor active threads
+        $('<span class="referencing-component-active-thread-count"></span>').text(function () {
+            if (nfCommon.isDefinedAndNotNull(referencingProcessor.activeThreadCount) && referencingProcessor.activeThreadCount > 0) {
+                return '(' + referencingProcessor.activeThreadCount + ')';
+            } else {
+                return '';
+            }
+        }).appendTo(referencingProcessorContainer);
+    };
+
+    /**
+     * Renders the specified affect controller service.
+     *
+     * @param {object} referencingControllerServiceEntity
+     * @param {jQuery} container
+     */
+    var renderReferencingControllerService = function (referencingControllerServiceEntity, container) {
+        var referencingControllerServiceContainer = $('<li class="referencing-component-container"></li>').appendTo(container);
+        var referencingControllerService = referencingControllerServiceEntity.component;
+
+        // controller service state
+        $('<div class="referencing-component-state"></div>').addClass(function () {
+            if (nfCommon.isDefinedAndNotNull(referencingControllerService.state)) {
+                var icon = $(this);
+
+                var state = referencingControllerService.state === 'ENABLED' ? 'enabled' : 'disabled';
+                if (state === 'disabled' && !nfCommon.isEmpty(referencingControllerService.validationErrors)) {
+                    state = 'invalid';
+
+                    // build the error listing
+                    var list = nfCommon.formatUnorderedList(referencingControllerService.validationErrors);
+
+                    // add tooltip for the warnings
+                    icon.qtip($.extend({},
+                        nfCanvasUtils.config.systemTooltipConfig,
+                        {
+                            content: list
+                        }));
+                }
+                return state;
+            } else {
+                return '';
+            }
+        }).appendTo(referencingControllerServiceContainer);
+
+        // bulletin
+        $('<div class="referencing-component-bulletins"></div>').addClass(referencingControllerService.id + '-referencing-bulletins').appendTo(referencingControllerServiceContainer);
+
+        // controller service name
+        $('<span class="parameter-context-referencing-component-name link ellipsis"></span>').prop('title', referencingControllerService.name).text(referencingControllerService.name).on('click', function () {
+            // check if there are outstanding changes
+            handleOutstandingChanges().done(function () {
+                // close the shell
+                $('#shell-dialog').modal('hide');
+
+                // show the component in question
+                nfProcessGroupConfiguration.showConfiguration(referencingControllerService.processGroupId).done(function () {
+                    nfProcessGroupConfiguration.selectControllerService(referencingControllerService.id);
+                });
+            });
+        }).appendTo(referencingControllerServiceContainer);
+    };
+
+    /**
+     * Populates the referencing components for the specified parameter context.
+     *
+     * @param {object} referencingComponents
+     */
+    var populateReferencingComponents = 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();
+            }
+        };
+
+        var referencingProcessors = [];
+        var referencingControllerServices = [];
+        var unauthorizedReferencingComponents = [];
+
+        // clear the referencing components from the previous selection
+        resetUsage();
+
+        var parameterReferencingComponentsContainer = $('#parameter-referencing-components-container');
+
+        // 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 {
+            var referencingComponentsForBulletinRetrieval = [];
+
+            // bin the referencing components according to their type
+            $.each(referencingComponents, function (_, referencingComponentEntity) {
+                if (referencingComponentEntity.permissions.canRead === true && referencingComponentEntity.permissions.canWrite === true) {
+                    referencingComponentsForBulletinRetrieval.push(referencingComponentEntity.id);
+
+                    if (referencingComponentEntity.component.referenceType === 'PROCESSOR') {
+                        referencingProcessors.push(referencingComponentEntity);
+                    } else {
+                        referencingControllerServices.push(referencingComponentEntity);
+                    }
+                } else {
+                    // if we're unauthorized only because the user is lacking write permissions, we can still query for bulletins
+                    if (referencingComponentEntity.permissions.canRead === true) {
+                        referencingComponentsForBulletinRetrieval.push(referencingComponentEntity.id);
+                    }
+
+                    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 = $('#parameter-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]);
+                }
+            }
+
+            //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 = $('#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('.parameter-context-referencing-processors');
+                            nfCommon.cleanUpTooltips(processorContainer, 'div.referencing-component-state');
+                            nfCommon.cleanUpTooltips(processorContainer, 'div.referencing-component-bulletins');
+                            processorContainer.empty();
+
+                            var controllerServiceContainer = groupTwist.find('.parameter-context-referencing-controller-services');
+                            nfCommon.cleanUpTooltips(controllerServiceContainer, 'div.referencing-component-state');
+                            nfCommon.cleanUpTooltips(controllerServiceContainer, 'div.referencing-component-bulletins');
+                            controllerServiceContainer.empty();
+
+                            var unauthorizedComponentsContainer = groupTwist.find('.parameter-context-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="parameter-context-referencing-component-name link ellipsis"></span>').prop('title', unauthorizedReferencingComponentEntity.id).text(unauthorizedReferencingComponentEntity.id).on('click', function () {
+                                            // check if there are outstanding changes
+                                            handleOutstandingChanges().done(function () {
+                                                // close the shell
+                                                $('#shell-dialog').modal('hide');
+
+                                                // show the component in question
+                                                nfCanvasUtils.showComponent(unauthorizedReferencingComponentEntity.processGroup.id, unauthorizedReferencingComponentEntity.id);
+                                            });
+                                        }).appendTo(referencingUnauthorizedComponentContainer);
+                                    }
+                                });
+                            }
+
+                            // query for the bulletins
+                            if (referencingComponentsForBulletinRetrieval.length > 0) {
+                                nfCanvasUtils.queryBulletins(referencingComponentsForBulletinRetrieval).done(function (response) {
+                                    var bulletins = response.bulletinBoard.bulletins;
+
+                                    var bulletinsBySource = d3.nest()
+                                        .key(function (d) {
+                                            return d.sourceId;
+                                        })
+                                        .map(bulletins, d3.map);
+
+                                    bulletinsBySource.each(function (sourceBulletins, sourceId) {
+                                        $('div.' + sourceId + '-referencing-bulletins').each(function () {
+                                            var bulletinIcon = $(this);
+
+                                            // if there are bulletins update them
+                                            if (sourceBulletins.length > 0) {
+                                                // format the new bulletins
+                                                var formattedBulletins = nfCommon.getFormattedBulletins(sourceBulletins);
+
+                                                var list = nfCommon.formatUnorderedList(formattedBulletins);
+
+                                                // update existing tooltip or initialize a new one if appropriate
+                                                bulletinIcon.addClass('has-bulletins').show().qtip($.extend({},
+                                                    nfCanvasUtils.config.systemTooltipConfig,
+                                                    {
+                                                        content: list
+                                                    }));
+                                            }
+                                        });
+                                    });
+                                });
+                            }
+                        } else {
+                            groupTwist.find('.referencing-components-template').remove();
+                        }
+
+                        // toggle this block
+                        toggle(twist, list);
+
+                        // update the border if necessary
+                        updateReferencingComponentsBorder($('#parameter-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);
+            });
+        }
+    };
+
+    /**
+     * Sorts the specified entities based on the name.
+     *
+     * @param {object} a
+     * @param {object} b
+     * @returns {number}
+     */
+    var nameComparator = function (a, b) {
+        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());
+
+        // ensure the parameter name is specified
+        if (parameterName !== '' && parameterKeyRegex.test(parameterName)) {
+            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 isSensitive = $('#parameter-dialog').find('input[name="sensitive"]:checked').val() === "sensitive" ? true : false;
+            var isChecked = $('#parameter-set-empty-string-field').hasClass('checkbox-checked');
+
+            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;
+                    }
+                }
+
+                // 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
+                    });
+
+                    var value = $('#parameter-value-field').val();
+                    if (!nfCommon.isBlank(value)) {
+                        parameter.value = value;
+                    } else {
+                        if (isChecked) {
+                            parameter.value = '';
+                        } else {
+                            parameter.value = null;
+                        }
+                    }
+
+                    parameterData.updateItem(matchingParameter.id, parameter);
+
+                    // sort the data
+                    parameterData.reSort();
+
+                    // 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);
+                }
+            }
+
+            // close the new parameter dialog
+            $('#parameter-dialog').modal('hide');
+
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-context-dialog').modal('refreshButtons');
+
+        } else if (!parameterKeyRegex.test(parameterName)) {
+            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.'
+            });
+        } else {
+            nfDialog.showOkDialog({
+                headerText: 'Configuration Error',
+                dialogContent: 'The name of the parameter must be specified.'
+            });
+        }
+    };
+
+    var serializeValue = function (input, parameter, isChecked) {
+        var serializedValue;
+        var hasChanged = true;
+
+        var value = input.val();
+        if (!nfCommon.isBlank(value)) {
+            // if the value is sensitive and the user has not made a change
+            if (parameter.sensitive === true && input.hasClass('sensitive') && parameter.isNew === false) {
+                serializedValue = parameter.previousValue;
+                hasChanged = false;
+            } else {
+                // value is not sensitive or it is sensitive and the user has changed it then always take the current value
+                serializedValue = value;
+            }
+        } else {
+            if (isChecked) {
+                serializedValue = '';
+            } else {
+                serializedValue = null;
+            }
+        }
+
+        return {
+            value: serializedValue,
+            hasChanged: hasChanged
+        };
+    };
+
+    /**
+     * Update a parameter.
+     */
+    var updateParameter = function () {
+        var parameterName = $('#parameter-name').val();
+
+        // ensure the parameter name is specified
+        if (parameterName !== '') {
+            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;
+                }
+            });
+
+            if (matchingParameter !== null) {
+                var isChecked = $('#parameter-set-empty-string-field').hasClass('checkbox-checked');
+
+                var parameter = {
+                    id: matchingParameter.id,
+                    hidden: false,
+                    type: 'Parameter',
+                    sensitive: matchingParameter.sensitive,
+                    name: parameterName,
+                    description: $('#parameter-description-field').val(),
+                    referencingComponents: matchingParameter.referencingComponents,
+                    previousValue: matchingParameter.value,
+                    previousDescription: matchingParameter.description,
+                    isEditable: matchingParameter.isEditable,
+                    isEmptyStringSet: isChecked,
+                    isNew: matchingParameter.isNew
+                };
+
+                var input = $('#parameter-value-field');
+                var serializedValue = serializeValue(input, parameter, isChecked);
+                parameter.value = serializedValue.value;
+                parameter.isModified = serializedValue.hasChanged || parameter.description !== parameter.previousDescription;
+                parameter.hasValueChanged = serializedValue.hasChanged;
+
+                // update row for the parameter
+                parameterData.updateItem(matchingParameter.id, parameter);
+
+                // sort the data
+                parameterData.reSort();
+
+                // select the parameter row
+                var row = parameterData.getRowById(matchingParameter.id);
+                parameterGrid.setActiveCell(row, parameterGrid.getColumnIndex('value'));
+            } else {
+                nfDialog.showOkDialog({
+                    headerText: 'Parameter Does Not Exists',
+                    dialogContent: 'A parameter with this name does not exist.'
+                });
+            }
+
+            // close the new parameter dialog
+            $('#parameter-dialog').modal('hide');
+
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-context-dialog').modal('refreshButtons');
+        } else {
+            nfDialog.showOkDialog({
+                headerText: 'Create Parameter Error',
+                dialogContent: 'The name of the parameter must be specified.'
+            });
+        }
+    };
+
+    /**
+     * Updates parameter contexts by issuing an update request and polling until it's completion.
+     *
+     * @param parameterContextEntity
+     * @returns {*}
+     */
+    var updateParameterContext = function (parameterContextEntity) {
+        var parameters = marshalParameters();
+
+        if (parameters.length === 0) {
+            // nothing to update
+            parameterContextEntity.component.parameters = [];
+            if ($('#parameter-context-name').val() === parameterContextEntity.component.name &&
+                $('#parameter-context-description-field').val() === parameterContextEntity.component.description) {
+                close();
+
+                return;
+            }
+        } else {
+            parameterContextEntity.component.parameters = parameters;
+        }
+
+        parameterContextEntity.component.name = $('#parameter-context-name').val();
+        parameterContextEntity.component.description = $('#parameter-context-description-field').val();
+
+        return $.Deferred(function (deferred) {
+            // updates the button model to show the close button
+            var updateToCloseButtonModel = function () {
+                $('#parameter-context-dialog').modal('setButtonModel', [{
+                    buttonText: 'Close',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            deferred.resolve();
+                            close();
+                        }
+                    }
+                }]);
+            };
+
+            var updateToApplyOrCancelButtonModel = function () {
+                $('#parameter-context-dialog').modal('setButtonModel', [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    disabled: function () {
+                        if ($('#parameter-context-name').val() !== '') {
+                            return false;
+                        }
+                        return true;
+                    },
+                    handler: {
+                        click: function () {
+                            if ($('#parameter-referencing-components-container').is(':visible')) {
+                                updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+                            }
+
+                            updateParameterContext(parameterContextEntity);
+                        }
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            deferred.resolve();
+
+                            if ($('#parameter-referencing-components-container').is(':visible')) {
+                                updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+                            }
+
+                            close();
+                        }
+                    }
+                }]);
+            };
+
+            var cancelled = false;
+
+            // update the button model to show the cancel button
+            $('#parameter-context-dialog').modal('setButtonModel', [{
+                buttonText: 'Cancel',
+                color: {
+                    base: '#E3E8EB',
+                    hover: '#C7D2D7',
+                    text: '#004849'
+                },
+                handler: {
+                    click: function () {
+                        cancelled = true;
+
+                        if ($('#parameter-referencing-components-container').is(':visible')) {
+                            updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+                        }
+
+                        updateToCloseButtonModel();
+                    }
+                }
+            }]);
+
+            var requestId;
+            var handleAjaxFailure = function (xhr, status, error) {
+                // delete the request if possible
+                if (nfCommon.isDefinedAndNotNull(requestId)) {
+                    deleteUpdateRequest(parameterContextEntity.id, requestId);
+                }
+
+                // update the step status
+                $('#parameter-context-update-steps').find('div.parameter-context-step.ajax-loading').removeClass('ajax-loading').addClass('ajax-error');
+
+                if ($('#parameter-referencing-components-container').is(':visible')) {
+                    updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+                }
+
+                // update the button model
+                updateToApplyOrCancelButtonModel();
+            };
+
+            submitUpdateRequest(parameterContextEntity).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 referencing components
+                    populateReferencingComponents(updateRequest.referencingComponents);
+
+                    // get the updated parameter names
+                    var parameterNames = [];
+                    $.each(parameters, function (_, parameterEntity) {
+                        parameterNames.push(parameterEntity.parameter.name);
+                    });
+                    $('#parameter-referencing-components-context').removeClass('unset').text(parameterNames.join(', '));
+
+                    // update the progress/steps
+                    populateParameterContextUpdateStep(updateRequest.updateSteps, cancelled, errored);
+
+                    // if this request was cancelled, remove the update request
+                    if (cancelled) {
+                        deleteUpdateRequest(parameterContextEntity.id, requestId);
+                    } else {
+                        if (updateRequest.complete === true) {
+                            if (errored) {
+                                nfDialog.showOkDialog({
+                                    headerText: 'Parameter Context Update Error',
+                                    dialogContent: 'Unable to complete parameter context 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 parameter context table if displayed
+                            var parameterContextGrid = $('#parameter-contexts-table').data('gridInstance');
+                            if (nfCommon.isDefinedAndNotNull(parameterContextGrid)) {
+                                var parameterContextData = parameterContextGrid.getData();
+
+                                $.extend(parameterContextEntity, {
+                                    revision: updateRequestEntity.parameterContextRevision,
+                                    component: updateRequestEntity.request.parameterContext
+                                });
+
+                                var item = parameterContextData.getItem(parameterContextEntity.id);
+                                if (nfCommon.isDefinedAndNotNull(item)) {
+                                    parameterContextData.updateItem(parameterContextEntity.id, parameterContextEntity);
+                                }
+                            }
+
+                            // delete the update request
+                            deleteUpdateRequest(parameterContextEntity.id, requestId);
+
+                            // update the button model
+                            updateToCloseButtonModel();
+
+                            // check if border is necessary
+                            updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+                        } else {
+                            // wait to get an updated status
+                            setTimeout(function () {
+                                getUpdateRequest(parameterContextEntity.id, requestId).done(function (getResponse) {
+                                    pollUpdateRequest(getResponse);
+                                }).fail(handleAjaxFailure);
+                            }, 2000);
+                        }
+                    }
+                };
+
+                // update the visibility
+                $('#parameter-table, #add-parameter').hide();
+                $('#parameter-context-tabs').find('.tab')[1].click();
+                $('#parameter-context-tabs').hide();
+                $('#parameter-context-update-status').show();
+
+                pollUpdateRequest(response);
+            }).fail(handleAjaxFailure);
+        }).promise();
+    };
+
+    /**
+     * Obtains the current state of the updateRequest using the specified update request id.
+     *
+     * @param {string} updateRequestId
+     * @returns {deferred} update request xhr
+     */
+    var getUpdateRequest = function (parameterContextId, updateRequestId) {
+        return $.ajax({
+            type: 'GET',
+            url: config.urls.parameterContexts + '/' + encodeURIComponent(parameterContextId) + '/update-requests/' + encodeURIComponent(updateRequestId),
+            dataType: 'json'
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Deletes an updateRequest using the specified update request id.
+     *
+     * @param {string} updateRequestId
+     * @returns {deferred} update request xhr
+     */
+    var deleteUpdateRequest = function (parameterContextId, updateRequestId) {
+        return $.ajax({
+            type: 'DELETE',
+            url: config.urls.parameterContexts + '/' + encodeURIComponent(parameterContextId) + '/update-requests/' + encodeURIComponent(updateRequestId) + '?' + $.param({
+                'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged()
+            }),
+            dataType: 'json'
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Submits an parameter context update request.
+     *
+     * @param {object} parameterContextEntity
+     * @returns {deferred} update request xhr
+     */
+    var submitUpdateRequest = function (parameterContextEntity) {
+        return $.ajax({
+            type: 'POST',
+            data: JSON.stringify(parameterContextEntity),
+            url: config.urls.parameterContexts + '/' + encodeURIComponent(parameterContextEntity.id) + '/update-requests',
+            dataType: 'json',
+            contentType: 'application/json'
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Populates the parameter update steps.
+     *
+     * @param {array} updateSteps
+     * @param {boolean} whether this request has been cancelled
+     * @param {boolean} whether this request has errored
+     */
+    var populateParameterContextUpdateStep = function (updateSteps, cancelled, errored) {
+        var updateStatusContainer = $('#parameter-context-update-steps').empty();
+
+        // go through each step
+        $.each(updateSteps, function (_, updateStep) {
+            var stepItem = $('<li></li>').text(updateStep.description).appendTo(updateStatusContainer);
+
+            $('<div class="parameter-context-step"></div>').addClass(function () {
+                if (nfCommon.isDefinedAndNotNull(updateStep.failureReason)) {
+                    return 'ajax-error';
+                } else {
+                    if (updateStep.complete === true) {
+                        return 'ajax-complete';
+                    } else {
+                        return cancelled === true || errored === true ? 'ajax-error' : 'ajax-loading';
+                    }
+                }
+            }).appendTo(stepItem);
+
+            $('<div class="clear"></div>').appendTo(stepItem);
+        });
+    };
+
+    var parameterCount = 0;
+    var parameterIndex = 0;
+
+    /**
+     * Loads the specified parameter registry.
+     *
+     * @param {object} parameterContext
+     * @param {string} parameterToSelect to select
+     */
+    var loadParameters = function (parameterContext, parameterToSelect) {
+        if (nfCommon.isDefinedAndNotNull(parameterContext)) {
+
+            var parameterGrid = $('#parameter-table').data('gridInstance');
+            var parameterData = parameterGrid.getData();
+
+            // begin the update
+            parameterData.beginUpdate();
+
+            var parameters = [];
+            $.each(parameterContext.component.parameters, function (i, parameterEntity) {
+                var parameter = {
+                    id: parameterCount++,
+                    hidden: false,
+                    type: 'Parameter',
+                    isNew: false,
+                    isModified: false,
+                    hasValueChanged: false,
+                    name: parameterEntity.parameter.name,
+                    value: parameterEntity.parameter.value,
+                    sensitive: parameterEntity.parameter.sensitive,
+                    description: parameterEntity.parameter.description,
+                    previousValue: parameterEntity.parameter.value,
+                    previousDescription: parameterEntity.parameter.description,
+                    isEditable: parameterEntity.canWrite,
+                    referencingComponents: parameterEntity.parameter.referencingComponents
+                };
+
+                parameters.push({
+                    parameter: parameter
+                });
+
+                parameterData.addItem(parameter);
+            });
+
+            // complete the update
+            parameterData.endUpdate();
+            parameterData.reSort();
+
+            // if we are pre-selecting a specific parameter, get it's parameterIndex
+            if (nfCommon.isDefinedAndNotNull(parameterToSelect)) {
+                $.each(parameters, function (i, parameterEntity) {
+                    if (parameterEntity.parameter.name === parameterToSelect) {
+                        parameterIndex = parameterData.getRowById(parameterEntity.parameter.id);
+                        return false;
+                    }
+                });
+            }
+
+            if (parameters.length === 0) {
+                resetUsage();
+            } else {
+                // select the desired row
+                parameterGrid.setSelectedRows([parameterIndex]);
+            }
+        }
+    };
+
+    var resetUsage = function () {
+        // empty the containers
+        var processorContainer = $('.parameter-context-referencing-processors');
+        nfCommon.cleanUpTooltips(processorContainer, 'div.referencing-component-state');
+        nfCommon.cleanUpTooltips(processorContainer, 'div.referencing-component-bulletins');
+        processorContainer.empty();
+
+        var controllerServiceContainer = $('.parameter-context-referencing-controller-services');
+        nfCommon.cleanUpTooltips(controllerServiceContainer, 'div.referencing-component-state');
+        nfCommon.cleanUpTooltips(controllerServiceContainer, 'div.referencing-component-bulletins');
+        controllerServiceContainer.empty();
+
+        var unauthorizedComponentsContainer = $('.parameter-context-referencing-unauthorized-components').empty();
+
+        $('#parameter-referencing-components-container').empty();
+
+        // reset the last selected parameter
+        lastSelectedId = null;
+
+        // indicate no referencing components
+        $('<li class="referencing-component-container"><span class="unset">None</span></li>').appendTo(processorContainer);
+        $('<li class="referencing-component-container"><span class="unset">None</span></li>').appendTo(controllerServiceContainer);
+        $('<li class="referencing-component-container"><span class="unset">None</span></li>').appendTo(unauthorizedComponentsContainer);
+
+        // update the selection context
+        $('#parameter-referencing-components-context').addClass('unset').text('None');
+
+        // check if border is necessary
+        updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+    };
+
+    /**
+     * Performs the filtering.
+     *
+     * @param {object} item     The item subject to filtering
+     * @param {object} args     Filter arguments
+     * @returns {Boolean}       Whether or not to include the item
+     */
+    var filter = function (item, args) {
+        return item.hidden === false;
+    };
+
+    /**
+     * Initializes the parameter table
+     */
+    var initParameterTable = function () {
+        var parameterTable = $('#parameter-table');
+
+        var nameFormatter = function (row, cell, value, columnDef, dataContext) {
+            var nameWidthOffset = 30;
+            var cellContent = $('<div></div>');
+
+            // format the contents
+            var formattedValue = $('<span/>').addClass('table-cell').text(value).appendTo(cellContent);
+            if (dataContext.type === 'required') {
+                formattedValue.addClass('required');
+            }
+
+            // show the parameter description if applicable
+            if (!nfCommon.isBlank(dataContext.description)) {
+                $('<div class="fa fa-question-circle" alt="Info" style="float: right;"></div>').appendTo(cellContent);
+                $('<span class="hidden parameter-row"></span>').text(row).appendTo(cellContent);
+                nameWidthOffset = 46; // 10 + icon width (10) + icon margin (6) + padding (20)
+            }
+
+            // adjust the width accordingly
+            formattedValue.width(columnDef.width - nameWidthOffset).ellipsis();
+
+            // return the cell content
+            return cellContent.html();
+        };
+
+        var valueFormatter = function (row, cell, value, columnDef, dataContext) {
+            if (dataContext.sensitive === true) {
+                return '<span class="table-cell sensitive">Sensitive value set</span>';
+            } else if (value === '') {
+                return '<span class="table-cell blank">Empty string set</span>';
+            } else if (nfCommon.isNull(value)) {
+                return '<span class="unset">No value set</span>';
+            } else {
+                return nfCommon.escapeHtml(value);
+            }
+        };
+
+        var parameterActionFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            if (dataContext.isEditable === true) {
+                markup += '<div title="Edit" class="edit-parameter pointer fa fa-pencil"></div>';
+                markup += '<div title="Delete" class="delete-parameter pointer fa fa-trash"></div>';
+            }
+
+            return markup;
+        };
+
+        // define the column model for the controller services table
+        var parameterColumns = [
+            {
+                id: 'name',
+                name: 'Name',
+                field: 'name',
+                formatter: nameFormatter,
+                sortable: true,
+                resizable: true,
+                rerenderOnResize: true
+            },
+            {
+                id: 'value',
+                name: 'Value',
+                field: 'value',
+                formatter: valueFormatter,
+                sortable: false,
+                resizable: true
+            },
+            {
+                id: 'actions',
+                name: '&nbsp;',
+                resizable: false,
+                rerenderOnResize: true,
+                formatter: parameterActionFormatter,
+                sortable: false,
+                width: 90,
+                maxWidth: 90
+            }
+        ];
+
+        // initialize the dataview
+        var parameterData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        parameterData.setFilterArgs({
+            searchString: '',
+            property: 'hidden'
+        });
+        parameterData.setFilter(filter);
+
+        // initialize the sort
+        sortParameters({
+            columnId: 'name',
+            sortAsc: true
+        }, parameterData);
+
+        // initialize the grid
+        var parametersGrid = new Slick.Grid(parameterTable, parameterData, parameterColumns, parametersGridOptions);
+        parametersGrid.setSelectionModel(new Slick.RowSelectionModel());
+        parametersGrid.registerPlugin(new Slick.AutoTooltips());
+        parametersGrid.setSortColumn('name', true);
+        parametersGrid.onSort.subscribe(function (e, args) {
+            sortParameters({
+                columnId: args.sortCol.id,
+                sortAsc: args.sortAsc
+            }, parameterData);
+        });
+        parametersGrid.onClick.subscribe(function (e, args) {
+            // get the parameter at this row
+            var parameter = parameterData.getItem(args.row);
+
+            if (parametersGrid.getColumns()[args.cell].id === 'actions') {
+                var target = $(e.target);
+
+                // determine the desired action
+                if (target.hasClass('delete-parameter')) {
+                    // mark the property in question for removal and refresh the table
+                    parameterData.updateItem(parameter.id, $.extend(parameter, {
+                        hidden: true
+                    }));
+
+                    // reset the selection if necessary
+                    var selectedRows = parametersGrid.getSelectedRows();
+                    if (selectedRows.length === 0) {
+                        parametersGrid.setSelectedRows([0]);
+                    }
+
+                    var rows = parameterData.getItems();
+
+                    if (rows.length === 0) {
+                        // clear usages
+                        resetUsage();
+                    } else {
+                        var reset = true;
+                        $.each(rows, function (_, parameter) {
+                            if (!parameter.hidden) {
+                                reset = false;
+                            }
+                        });
+
+                        if (reset) {
+                            // clear usages
+                            resetUsage();
+                        }
+                    }
+
+                    // update the buttons to possibly trigger the disabled state
+                    $('#parameter-context-dialog').modal('refreshButtons');
+
+                    // prevents standard edit logic
+                    e.stopImmediatePropagation();
+                } else if (target.hasClass('edit-parameter')) {
+                    var closeHandler = function () {
+                        $('#parameter-name').val('');
+                        $('#parameter-value-field').val('');
+                        $('#parameter-description-field').val('');
+                        $('#parameter-sensitive-radio-button').prop('checked', false);
+                        $('#parameter-not-sensitive-radio-button').prop('checked', false);
+                        $('#parameter-name').prop('disabled', false);
+                        $('#parameter-sensitive-radio-button').prop('disabled', false);
+                        $('#parameter-not-sensitive-radio-button').prop('disabled', false);
+                        $('#parameter-set-empty-string-field').removeClass('checkbox-checked').addClass('checkbox-unchecked');
+                    };
+
+                    var openHandler = function () {
+                        $('#parameter-sensitive-radio-button').prop('checked', false);
+                        $('#parameter-not-sensitive-radio-button').prop('checked', true);
+                        $('#parameter-name').focus();
+
+                        $('#parameter-name').val(parameter.name);
+                        $('#parameter-name').prop('disabled', true);
+                        $('#parameter-sensitive-radio-button').prop('disabled', true);
+                        $('#parameter-not-sensitive-radio-button').prop('disabled', true);
+                        if (parameter.value === '') {
+                            $('#parameter-set-empty-string-field').removeClass('checkbox-unchecked').addClass('checkbox-checked');
+                        } else {
+                            $('#parameter-set-empty-string-field').removeClass('checkbox-checked').addClass('checkbox-unchecked');
+                        }
+
+                        if (parameter.sensitive) {
+                            $('#parameter-sensitive-radio-button').prop('checked', true);
+                            $('#parameter-not-sensitive-radio-button').prop('checked', false);
+                            $('#parameter-value-field').addClass('sensitive').val(nfCommon.config.sensitiveText).select();
+                        } else {
+                            $('#parameter-sensitive-radio-button').prop('checked', false);
+                            $('#parameter-not-sensitive-radio-button').prop('checked', true);
+                            $('#parameter-value-field').val(parameter.value);
+                        }
+                        $('#parameter-description-field').val(parameter.description);
+
+                        // update the buttons to possibly trigger the disabled state
+                        $('#parameter-dialog').modal('refreshButtons');
+                    };
+
+                    $('#parameter-dialog')
+                        .modal('setHeaderText', 'Edit Parameter')
+                        .modal('setOpenHandler', openHandler)
+                        .modal('setCloseHandler', closeHandler)
+                        .modal('setButtonModel', [{
+                            buttonText: 'Apply',
+                            color: {
+                                base: '#728E9B',
+                                hover: '#004849',
+                                text: '#ffffff'
+                            },
+                            disabled: function () {
+                                var input = $('#parameter-value-field');
+                                var isChecked = $('#parameter-set-empty-string-field').hasClass('checkbox-checked');
+                                var serializedValue = serializeValue(input, parameter, isChecked);
+
+                                var description = $('#parameter-description-field').val();
+
+                                var hasChanged = serializedValue.hasChanged || description !== parameter.previousDescription;
+
+                                return !hasChanged;
+                            },
+                            handler: {
+                                click: function () {
+                                    updateParameter();
+                                }
+                            }
+                        }, {
+                            buttonText: 'Cancel',
+                            color: {
+                                base: '#E3E8EB',
+                                hover: '#C7D2D7',
+                                text: '#004849'
+                            },
+                            handler: {
+                                click: function () {
+                                    $(this).modal('hide');
+                                }
+                            }
+                        }]).modal('show');
+
+                    // prevents standard edit logic
+                    e.stopImmediatePropagation();
+                }
+            }
+        });
+        parametersGrid.onSelectedRowsChanged.subscribe(function (e, args) {
+            if ($.isArray(args.rows) && args.rows.length === 1) {
+                // show the referencing components for the selected parameter
+                if (parametersGrid.getDataLength() > 0) {
+                    var parameterIndex = args.rows[0];
+                    var parameter = parametersGrid.getDataItem(parameterIndex);
+
+                    // only populate referencing components if this parameter is different than the last selected
+                    if (lastSelectedId === null || lastSelectedId !== parameter.id) {
+                        populateReferencingComponents(parameter.referencingComponents);
+
+                        // update the details for this parameter
+                        $('#parameter-referencing-components-context').removeClass('unset').text(parameter.name);
+
+                        updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+
+                        // update the last selected id
+                        lastSelectedId = parameter.id;
+                    }
+                }
+            }
+        });
+        parametersGrid.onBeforeCellEditorDestroy.subscribe(function (e, args) {
+            setTimeout(function () {
+                parametersGrid.resizeCanvas();
+            }, 50);
+        });
+
+        // wire up the dataview to the grid
+        parameterData.onRowCountChanged.subscribe(function (e, args) {
+            parametersGrid.updateRowCount();
+            parametersGrid.render();
+        });
+        parameterData.onRowsChanged.subscribe(function (e, args) {
+            parametersGrid.invalidateRows(args.rows);
+            parametersGrid.render();
+        });
+        parameterData.syncGridSelection(parametersGrid, true);
+
+        // hold onto an instance of the grid and create parameter description tooltip
+        parameterTable.data('gridInstance', parametersGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var infoIcon = $(this).find('div.fa-question-circle');
+            if (infoIcon.length) {
+                if (infoIcon.data('qtip')) {
+                    infoIcon.qtip('destroy', true);
+                }
+
+                var row = $(this).find('span.parameter-row').text();
+
+                // get the parameter
+                var parameter = parameterData.getItem(row);
+
+                if (nfCommon.isDefinedAndNotNull(parameter.description)) {
+                    infoIcon.qtip($.extend({},
+                        nfCommon.config.tooltipConfig,
+                        {
+                            content: parameter.description
+                        }));
+                }
+            }
+        });
+    };
+
+    /**
+     * Initializes the new parameter context dialog.
+     */
+    var initNewParameterContextDialog = function () {
+        // initialize the parameter context tabs
+        $('#parameter-context-tabs').tabbs({
+            tabStyle: 'tab',
+            selectedTabStyle: 'selected-tab',
+            scrollableTabContentStyle: 'scrollable',
+            tabs: [{
+                name: 'Settings',
+                tabContentId: 'parameter-context-standard-settings-tab-content'
+            }, {
+                name: 'Parameters',
+                tabContentId: 'parameter-context-parameters-tab-content'
+            }],
+            select: function () {
+                // update the parameters table size in case this is the first time its rendered
+                if ($(this).text() === 'Parameters') {
+                    var parameterGrid = $('#parameter-table').data('gridInstance');
+                    if (nfCommon.isDefinedAndNotNull(parameterGrid)) {
+                        parameterGrid.resizeCanvas();
+                    }
+                }
+            }
+        });
+
+        // initialize the parameter context dialog
+        $('#parameter-context-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            handler: {
+                close: function () {
+                    resetDialog();
+                }
+            }
+        });
+
+        $('#parameter-dialog').modal();
+
+        $('#parameter-value-field').on('keydown', function () {
+            var sensitiveInput = $(this);
+            if (sensitiveInput.hasClass('sensitive')) {
+                sensitiveInput.removeClass('sensitive');
+                if (sensitiveInput.val() === nfCommon.config.sensitiveText) {
+                    sensitiveInput.val('');
+                }
+            }
+        });
+
+        $('#parameter-set-empty-string-field').on('click', function () {
+            var sensitiveInput = $('#parameter-value-field');
+            if (sensitiveInput.hasClass('sensitive')) {
+                sensitiveInput.removeClass('sensitive');
+                if (sensitiveInput.val() === nfCommon.config.sensitiveText) {
+                    sensitiveInput.val('');
+                }
+            }
+        });
+
+        $('#parameter-name').on('keydown', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+            if (code === $.ui.keyCode.ENTER) {
+                addNewParameter();
+
+                // prevents the enter from propagating into the field for editing the new parameter value
+                e.stopImmediatePropagation();
+                e.preventDefault();
+            }
+        });
+
+        $('#add-parameter').on('click', function () {
+            var closeHandler = function () {
+                $('#parameter-name').val('');
+                $('#parameter-value-field').val('');
+                $('#parameter-description-field').val('');
+                $('#parameter-sensitive-radio-button').prop('checked', false);
+                $('#parameter-not-sensitive-radio-button').prop('checked', false);
+                $('#parameter-name').prop('disabled', false);
+                $('#parameter-sensitive-radio-button').prop('disabled', false);
+                $('#parameter-not-sensitive-radio-button').prop('disabled', false);
+                $('#parameter-set-empty-string-field').removeClass('checkbox-checked').addClass('checkbox-unchecked');
+            };
+
+            var openHandler = function () {
+                $('#parameter-sensitive-radio-button').prop('checked', false);
+                $('#parameter-not-sensitive-radio-button').prop('checked', true);
+                $('#parameter-name').focus();
+            };
+
+            $('#parameter-dialog')
+                .modal('setHeaderText', 'Add Parameter')
+                .modal('setOpenHandler', openHandler)
+                .modal('setCloseHandler', closeHandler)
+                .modal('setButtonModel', [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    disabled: function () {
+                        if ($('#parameter-name').val() !== '') {
+                            return false;
+                        }
+                        return true;
+                    },
+                    handler: {
+                        click: function () {
+                            addNewParameter();
+                        }
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }]).modal('show');
+            $('#parameter-dialog').modal('show');
+        });
+
+        $('#parameter-context-name').on('keyup', function (evt) {
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-context-dialog').modal('refreshButtons');
+        });
+
+        $('#parameter-name').on('keyup', function (evt) {
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-dialog').modal('refreshButtons');
+        });
+
+        $('#parameter-value-field').on('keyup', function (evt) {
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-dialog').modal('refreshButtons');
+        });
+
+        $('#parameter-description-field').on('keyup', function (evt) {
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-dialog').modal('refreshButtons');
+        });
+
+        $('#parameter-set-empty-string-field').on('change', function (evt) {
+            // update the buttons to possibly trigger the disabled state
+            $('#parameter-dialog').modal('refreshButtons');
+        });
+
+        initParameterTable();
+    };
+
+    /**
+     * Loads the parameter contexts.
+     */
+    var loadParameterContexts = function () {
+        var parameterContexts = $.Deferred(function (deferred) {
+            $.ajax({
+                type: 'GET',
+                url: '../nifi-api/flow/parameter-contexts',
+                dataType: 'json'
+            }).done(function (response) {
+                deferred.resolve(response);
+            }).fail(function (xhr, status, error) {
+                deferred.reject(xhr, status, error);
+            });
+        }).promise();
+
+        // return a deferred for all parts of the parameter contexts
+        return $.when(parameterContexts).done(function (response) {
+            $('#parameter-contexts-last-refreshed').text(response.currentTime);
+
+            var contexts = [];
+            $.each(response.parameterContexts, function (_, parameterContext) {
+                contexts.push($.extend({
+                    type: 'ParameterContext'
+                }, parameterContext));
+            });
+
+            // update the parameter contexts
+            var parameterContextsGrid = $('#parameter-contexts-table').data('gridInstance');
+            var parameterContextsData = parameterContextsGrid.getData();
+            parameterContextsData.setItems(contexts);
+            parameterContextsData.reSort();
+            parameterContextsGrid.invalidate();
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Shows the parameter contexts.
+     */
+    var showParameterContexts = function (response) {
+        // show the parameter contexts dialog
+        nfShell.showContent('#parameter-contexts');
+
+        // adjust the table size
+        nfParameterContexts.resetTableSize();
+    };
+
+    /**
+     * Initializes the parameter contexts.
+     */
+    var initParameterContexts = function () {
+        var parameterContextActionFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            var canWrite = dataContext.permissions.canWrite;
+            var canRead = dataContext.permissions.canRead;
+
+            if (canRead && canWrite) {
+                markup += '<div title="Edit" class="pointer edit-parameter-context fa fa-pencil"></div>';
+            }
+
+            if (canRead && canWrite && nfCommon.canModifyParameterContexts()) {
+                markup += '<div title="Remove" class="pointer delete-parameter-context fa fa-trash"></div>';
+            }
+
+            // allow policy configuration conditionally
+            if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) {
+                markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key"></div>';
+            }
+
+            return markup;
+        };
+
+        var descriptionFormatter = function (row, cell, value, columnDef, dataContext) {
+            if (!dataContext.permissions.canRead) {
+                return '';
+            }
+
+            return nfCommon.escapeHtml(dataContext.component.description);
+        };
+
+        // define the column model for the parameter contexts table
+        var parameterContextsColumnModel = [
+            {
+                id: 'name',
+                name: 'Name',
+                sortable: true,
+                resizable: true,
+                formatter: nameFormatter
+            },
+            {
+                id: 'description',
+                name: 'Description',
+                sortable: true,
+                resizable: true,
+                formatter: descriptionFormatter
+            }
+        ];
+
+        // action column should always be last
+        parameterContextsColumnModel.push({
+            id: 'actions',
+            name: '&nbsp;',
+            resizable: false,
+            formatter: parameterContextActionFormatter,
+            sortable: false,
+            width: 90,
+            maxWidth: 90
+        });
+
+        // initialize the dataview
+        var parameterContextsData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+
+        parameterContextsData.setItems([]);
+
+        // initialize the sort
+        sort({
+            columnId: 'name',
+            sortAsc: true
+        }, parameterContextsData);
+
+        // initialize the grid
+        var parameterContextsGrid = new Slick.Grid('#parameter-contexts-table', parameterContextsData, parameterContextsColumnModel, parameterContextsGridOptions);
+        parameterContextsGrid.setSelectionModel(new Slick.RowSelectionModel());
+        parameterContextsGrid.registerPlugin(new Slick.AutoTooltips());
+        parameterContextsGrid.setSortColumn('name', true);
+        parameterContextsGrid.onSort.subscribe(function (e, args) {
+            sort({
+                columnId: args.sortCol.id,
+                sortAsc: args.sortAsc
+            }, parameterContextsData);
+        });
+
+        // configure a click listener
+        parameterContextsGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+
+            // get the context at this row
+            var parameterContextEntity = parameterContextsData.getItem(args.row);
+
+            // determine the desired action
+            if (parameterContextsGrid.getColumns()[args.cell].id === 'actions') {
+                if (target.hasClass('edit-parameter-context')) {
+                    nfParameterContexts.showParameterContext(parameterContextEntity.id);
+                } else if (target.hasClass('delete-parameter-context')) {
+                    nfParameterContexts.promptToDeleteParameterContext(parameterContextEntity);
+                } else if (target.hasClass('edit-access-policies')) {
+                    nfPolicyManagement.showParameterContextPolicy(parameterContextEntity);
+
+                    // close the settings dialog
+                    $('#shell-close-button').click();
+                }
+            }
+        });
+
+        // wire up the dataview to the grid
+        parameterContextsData.onRowCountChanged.subscribe(function (e, args) {
+            parameterContextsGrid.updateRowCount();
+            parameterContextsGrid.render();
+        });
+        parameterContextsData.onRowsChanged.subscribe(function (e, args) {
+            parameterContextsGrid.invalidateRows(args.rows);
+            parameterContextsGrid.render();
+        });
+        parameterContextsData.syncGridSelection(parameterContextsGrid, true);
+
+        // hold onto an instance of the grid
+        $('#parameter-contexts-table').data('gridInstance', parameterContextsGrid).on('mouseenter', 'div.slick-cell', function (e) {
+            var errorIcon = $(this).find('div.has-errors');
+            if (errorIcon.length && !errorIcon.data('qtip')) {
+                var contextId = $(this).find('span.row-id').text();
+
+                // get the task item
+                var parameterContextEntity = parameterContextsData.getItemById(contextId);
+
+                // format the errors
+                var tooltip = nfCommon.formatUnorderedList(parameterContextEntity.component.validationErrors);
+
+                // show the tooltip
+                if (nfCommon.isDefinedAndNotNull(tooltip)) {
+                    errorIcon.qtip($.extend({},
+                        nfCommon.config.tooltipConfig,
+                        {
+                            content: tooltip,
+                            position: {
+                                target: 'mouse',
+                                viewport: $('#shell-container'),
+                                adjust: {
+                                    x: 8,
+                                    y: 8,
+                                    method: 'flipinvert flipinvert'
+                                }
+                            }
+                        }));
+                }
+            }
+        });
+    };
+
+    var currentParameterContextEntity = null;
+
+    var nfParameterContexts = {
+        /**
+         * Initializes the parameter contexts page.
+         */
+        init: function () {
+            // parameter context refresh button
+            $('#parameter-contexts-refresh-button').on('click', function () {
+                loadParameterContexts();
+            });
+
+            // create a new parameter context
+            $('#new-parameter-context').on('click', function () {
+                resetUsage();
+
+                $('#parameter-context-dialog').modal('setHeaderText', 'Add Parameter Context').modal('setButtonModel', [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    disabled: function () {
+                        if ($('#parameter-context-name').val() !== '') {
+                            return false;
+                        }
+                        return true;
+                    },
+                    handler: {
+                        click: function () {
+                            nfParameterContexts.addParameterContext();
+                        }
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }]).modal('show');
+
+                // set the initial focus
+                $('#parameter-context-name').focus();
+            });
+
+            // initialize the new parameter context dialog
+            initNewParameterContextDialog();
+
+            initParameterContexts();
+
+            $(window).on('resize', function (e) {
+                if ($('#parameter-referencing-components-container').is(':visible')) {
+                    updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+                }
+            })
+        },
+
+        /**
+         * Adds a new parameter context.
+         *
+         * @param {object} parameterContextCreatedDeferred          The parameter context created callback.
+         */
+        addParameterContext: function (parameterContextCreatedDeferred) {
+            // build the parameter context entity
+            var parameterContextEntity = {
+                "component": {
+                    "name": $('#parameter-context-name').val(),
+                    "description": $('#parameter-context-description-field').val(),
+                    "parameters": marshalParameters()
+                },
+                'revision': nfClient.getRevision({
+                    'revision': {
+                        'version': 0
+                    }
+                })
+            };
+
+            var addContext = $.ajax({
+                type: 'POST',
+                url: config.urls.parameterContexts,
+                data: JSON.stringify(parameterContextEntity),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (parameterContextEntity) {
+                // add the item
+                var parameterContextGrid = $('#parameter-contexts-table').data('gridInstance');
+
+                if (nfCommon.isDefinedAndNotNull(parameterContextGrid)) {
+                    var parameterContextData = parameterContextGrid.getData();
+                    parameterContextData.addItem(parameterContextEntity);
+
+                    // resort
+                    parameterContextData.reSort();
+                    parameterContextGrid.invalidate();
+
+                    // select the new parameter context
+                    var row = parameterContextData.getRowById(parameterContextEntity.id);
+                    nfFilteredDialogCommon.choseRow(parameterContextGrid, row);
+                    parameterContextGrid.scrollRowIntoView(row);
+                }
+
+                // invoke callback if necessary
+                if (typeof parameterContextCreatedDeferred === 'function') {
+                    parameterContextCreatedDeferred(parameterContextEntity);
+                }
+            }).fail(nfErrorHandler.handleAjaxError);
+
+            // hide the dialog
+            $('#parameter-context-dialog').modal('hide');
+
+            return addContext;
+        },
+
+        /**
+         * Update the size of the grid based on its container's current size.
+         */
+        resetTableSize: function () {
+            var parameterContextsGrid = $('#parameter-contexts-table').data('gridInstance');
+            if (nfCommon.isDefinedAndNotNull(parameterContextsGrid)) {
+                parameterContextsGrid.resizeCanvas();
+            }
+        },
+
+        /**
+         * Shows the parameter context dialog.
+         */
+        showParameterContexts: function () {
+            // conditionally allow creation of new parameter contexts
+            $('#new-parameter-context').prop('disabled', !nfCommon.canModifyParameterContexts());
+
+            // load the parameter contexts
+            return loadParameterContexts().done(showParameterContexts);
+        },
+
+        /**
+         * Shows the dialog for the specified parameter context.
+         *
+         * @argument id      The parameter context id
+         */
+        showParameterContext: function (id) {
+            parameterCount = 0;
+
+            // reload the parameter context in case the parameters have changed
+            var reloadContext = $.ajax({
+                type: 'GET',
+                url: config.urls.parameterContexts + '/' + encodeURIComponent(id),
+                dataType: 'json'
+            });
+
+            // once everything is loaded, show the dialog
+            reloadContext.done(function (parameterContextEntity) {
+                currentParameterContextEntity = parameterContextEntity;
+                $('#parameter-context-name').val(parameterContextEntity.component.name);
+                $('#parameter-context-description-field').val(parameterContextEntity.component.description);
+
+                loadParameters(parameterContextEntity);
+
+                // show the context
+                $('#parameter-context-dialog').modal('setHeaderText', 'Update Parameter Context').modal('setButtonModel', [{
+                    buttonText: 'Apply',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    disabled: function () {
+                        if ($('#parameter-context-name').val() !== '') {
+                            return false;
+                        }
+                        return true;
+                    },
+                    handler: {
+                        click: function () {
+                            updateParameterContext(currentParameterContextEntity);
+                        }
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }]).modal('show');
+
+                // select the parameters tab
+                $('#parameter-context-tabs').find('li:last').click();
+
+                // check if border is necessary
+                updateReferencingComponentsBorder($('#parameter-referencing-components-container'));
+            }).fail(nfErrorHandler.handleAjaxError);
+        },
+
+        /**
+         * Prompts the user before attempting to delete the specified parameter context.
+         *
+         * @param {object} parameterContextEntity
+         */
+        promptToDeleteParameterContext: function (parameterContextEntity) {
+            // prompt for deletion
+            nfDialog.showYesNoDialog({
+                headerText: 'Delete Parameter Context',
+                dialogContent: 'Delete parameter context \'' + nfCommon.escapeHtml(parameterContextEntity.component.name) + '\'?',
+                yesHandler: function () {
+                    nfParameterContexts.remove(parameterContextEntity);
+                }
+            });
+        },
+
+        /**
+         * Deletes the specified parameter context.
+         *
+         * @param {object} parameterContextEntity
+         */
+        remove: function (parameterContextEntity) {
+            $.ajax({
+                type: 'DELETE',
+                url: config.urls.parameterContexts + '/' + encodeURIComponent(parameterContextEntity.id) + '?' + $.param({
+                    'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
+                    'clientId': parameterContextEntity.revision.clientId,
+                    'version': parameterContextEntity.revision.version
+                }),
+                dataType: 'json'
+            }).done(function (response) {
+                // remove the parameter context
+                var parameterContextGrid = $('#parameter-contexts-table').data('gridInstance');
+                var parameterContextData = parameterContextGrid.getData();
+                parameterContextData.deleteItem(parameterContextEntity.id);
+            }).fail(nfErrorHandler.handleAjaxError);
+        }
+    };
+
+    return nfParameterContexts;
+}));
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
index 548ee20..5bcc4b6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
@@ -58,7 +58,7 @@
     }
 }(this, function ($, Slick, nfErrorHandler, nfCommon, nfClient, nfStorage, nfCanvasUtils, nfNgBridge, nfDialog, nfShell) {
     'use strict';
-    
+
     var config = {
         urls: {
             api: '../nifi-api',
@@ -68,6 +68,7 @@
 
     var initialized = false;
     var initializedComponentRestrictions = false;
+    var initializingComponentPolicy = false;
 
     var initAddTenantToPolicyDialog = function () {
         $('#new-policy-user-button').on('click', function () {
@@ -337,7 +338,7 @@
      * @returns {boolean} whether the policy supports read/write options
      */
     var globalPolicySupportsReadWrite = function (policyType) {
-        return policyType === 'controller' || policyType === 'counters' || policyType === 'policies' || policyType === 'tenants';
+        return policyType === 'controller' || policyType === 'parameter-contexts' || policyType === 'counters' || policyType === 'policies' || policyType === 'tenants';
     };
 
     /**
@@ -407,6 +408,7 @@
             options: [
                 nfCommon.getPolicyTypeListing('flow'),
                 nfCommon.getPolicyTypeListing('controller'),
+                nfCommon.getPolicyTypeListing('parameter-contexts'),
                 nfCommon.getPolicyTypeListing('provenance'),
                 nfCommon.getPolicyTypeListing('restricted-components'),
                 nfCommon.getPolicyTypeListing('policies'),
@@ -518,7 +520,7 @@
                 }
             }
         });
-        
+
         // component policy target
         $('#component-policy-target').combo({
             options: [{
@@ -601,8 +603,10 @@
                     // set the resource
                     $('#selected-policy-type').text(resource);
 
-                    // reload the policy
-                    loadPolicy();
+                    // reload the policy if we are finished loading
+                    if (!initializingComponentPolicy) {
+                        loadPolicy();
+                    }
                 }
             }
         });
@@ -810,7 +814,7 @@
     var deletePolicy = function () {
         var currentEntity = $('#policy-table').data('policy');
         var revision = nfClient.getRevision(currentEntity);
-        
+
         if (nfCommon.isDefinedAndNotNull(currentEntity)) {
             $.ajax({
                 type: 'DELETE',
@@ -908,6 +912,8 @@
             return $('<span>Showing effective policy inherited from all policies.</span>');
         } else if (resource === '/controller') {
             return $('<span>Showing effective policy inherited from the controller.</span>');
+        } else if (resource === '/parameter-contexts') {
+            return $('<span>Showing effective policy inherited from global parameter context policy.</span>');
         } else {
             // extract the group id
             var processGroupId = nfCommon.substringAfterLast(resource, '/');
@@ -1319,7 +1325,7 @@
                     'userGroups': userGroups
                 }
             };
-    
+
             $.ajax({
                 type: 'PUT',
                 url: currentEntity.uri,
@@ -1406,7 +1412,7 @@
         $('#selected-policy-action').text('');
         $('#selected-policy-component-id').text('');
         $('#selected-policy-component-type').text('');
-        
+
         // clear the selected component details
         $('div.policy-selected-component-container').hide();
     };
@@ -1454,6 +1460,8 @@
          * @param d
          */
         showControllerServicePolicy: function (d) {
+            initializingComponentPolicy = true;
+
             // reset the policy message
             resetPolicyMessage();
 
@@ -1492,6 +1500,8 @@
                     value: 'read-component'
                 });
 
+            initializingComponentPolicy = false;
+
             return loadPolicy().always(showPolicy);
         },
 
@@ -1501,6 +1511,8 @@
          * @param d
          */
         showReportingTaskPolicy: function (d) {
+            initializingComponentPolicy = true;
+
             // reset the policy message
             resetPolicyMessage();
 
@@ -1515,7 +1527,7 @@
                 $('#policy-selected-reporting-task-container div.policy-selected-component-name').text(d.id);
             }
             $('#policy-selected-reporting-task-container').show();
-            
+
             // populate the initial resource
             $('#selected-policy-component-id').text(d.id);
             $('#selected-policy-component-type').text('reporting-tasks');
@@ -1539,6 +1551,8 @@
                     value: 'read-component'
                 });
 
+            initializingComponentPolicy = false;
+
             return loadPolicy().always(showPolicy);
         },
 
@@ -1548,6 +1562,8 @@
          * @param d
          */
         showTemplatePolicy: function (d) {
+            initializingComponentPolicy = true;
+
             // reset the policy message
             resetPolicyMessage();
 
@@ -1568,6 +1584,63 @@
             $('#selected-policy-component-type').text('templates');
             $('#component-policy-target')
                 .combo('setOptionEnabled', {
+                    value: 'operate-component'
+                }, false)
+                .combo('setOptionEnabled', {
+                    value: 'write-receive-data'
+                }, false)
+                .combo('setOptionEnabled', {
+                    value: 'write-send-data'
+                }, false)
+                .combo('setOptionEnabled', {
+                    value: 'read-data'
+                }, false)
+                .combo('setOptionEnabled', {
+                    value: 'read-provenance'
+                }, false)
+                .combo('setOptionEnabled', {
+                    value: 'write-data'
+                }, false)
+                .combo('setSelectedOption', {
+                    value: 'read-component'
+                });
+
+            initializingComponentPolicy = false;
+
+            return loadPolicy().always(showPolicy);
+        },
+
+        /**
+         * Shows the parameter context policy.
+         *
+         * @param d
+         */
+        showParameterContextPolicy: function (d) {
+            initializingComponentPolicy = true;
+
+            // reset the policy message
+            resetPolicyMessage();
+
+            // update the policy controls visibility
+            $('#component-policy-controls').show();
+            $('#global-policy-controls').hide();
+
+            // update the visibility
+            if (d.permissions.canRead === true) {
+                $('#policy-selected-parameter-context-container div.policy-selected-component-name').text(d.component.name);
+            } else {
+                $('#policy-selected-parameter-context-container div.policy-selected-component-name').text(d.id);
+            }
+            $('#policy-selected-parameter-context-container').show();
+
+            // populate the initial resource
+            $('#selected-policy-component-id').text(d.id);
+            $('#selected-policy-component-type').text('parameter-contexts');
+            $('#component-policy-target')
+                .combo('setOptionEnabled', {
+                    value: 'operate-component'
+                }, false)
+                .combo('setOptionEnabled', {
                     value: 'write-receive-data'
                 }, false)
                 .combo('setOptionEnabled', {
@@ -1586,6 +1659,8 @@
                     value: 'read-component'
                 });
 
+            initializingComponentPolicy = false;
+
             return loadPolicy().always(showPolicy);
         },
 
@@ -1593,6 +1668,8 @@
          * Shows the component policy dialog.
          */
         showComponentPolicy: function (selection) {
+            initializingComponentPolicy = true;
+
             // reset the policy message
             resetPolicyMessage();
 
@@ -1602,7 +1679,7 @@
 
             // update the visibility
             $('#policy-selected-component-container').show();
-            
+
             var resource;
             if (selection.empty()) {
                 $('#selected-policy-component-id').text(nfCanvasUtils.getGroupId());
@@ -1611,6 +1688,9 @@
                 // disable site to site option
                 $('#component-policy-target')
                     .combo('setOptionEnabled', {
+                        value: 'operate-component'
+                    }, true)
+                    .combo('setOptionEnabled', {
                         value: 'write-receive-data'
                     }, false)
                     .combo('setOptionEnabled', {
@@ -1648,6 +1728,9 @@
                 // enable site to site option
                 $('#component-policy-target')
                     .combo('setOptionEnabled', {
+                        value: 'operate-component'
+                    }, !nfCanvasUtils.isLabel(selection))
+                    .combo('setOptionEnabled', {
                         value: 'write-receive-data'
                     }, nfCanvasUtils.isInputPort(selection) && d.allowRemoteAccess === true)
                     .combo('setOptionEnabled', {
@@ -1667,6 +1750,8 @@
                 value: 'read-component'
             });
 
+            initializingComponentPolicy = false;
+
             return loadPolicy().always(showPolicy);
         },
 
@@ -1698,4 +1783,4 @@
     };
 
     return nfPolicyManagement;
-}));
\ 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/nf/canvas/nf-process-group-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
index d354096..9650d32 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
@@ -60,10 +60,12 @@
     'use strict';
 
     var nfControllerServices;
+    var nfParameterContexts;
 
     var config = {
         urls: {
-            api: '../nifi-api'
+            api: '../nifi-api',
+            parameterContexts: '../nifi-api/flow/parameter-contexts'
         }
     };
 
@@ -100,7 +102,10 @@
             'component': {
                 'id': groupId,
                 'name': $('#process-group-name').val(),
-                'comments': $('#process-group-comments').val()
+                'comments': $('#process-group-comments').val(),
+                'parameterContext': {
+                    'id': $('#process-group-parameter-context-combo').combo('getSelectedOption').value
+                }
             }
         };
 
@@ -128,6 +133,25 @@
                 saveConfiguration(response.revision.version, groupId);
             });
 
+            var controllerServicesUri = config.urls.api + '/flow/process-groups/' + encodeURIComponent(groupId) + '/controller-services';
+
+            $.ajax({
+                type: 'GET',
+                url: controllerServicesUri,
+                dataType: 'json'
+            }).done(function (response) {
+                var serviceTable = getControllerServicesTable();
+
+                nfCommon.cleanUpTooltips(serviceTable, 'div.has-errors');
+
+                var controllerServicesGrid = serviceTable.data('gridInstance');
+                var controllerServicesData = controllerServicesGrid.getData();
+
+                $.each(response.controllerServices, function (_, controllerServiceEntity) {
+                    controllerServicesData.updateItem(controllerServiceEntity.id, controllerServiceEntity);
+                });
+            });
+
             nfCanvasUtils.reload();
         }).fail(nfErrorHandler.handleConfigurationUpdateAjaxError);
     };
@@ -139,8 +163,8 @@
      */
     var loadConfiguration = function (groupId) {
         var setUnauthorizedText = function () {
-            $('#read-only-process-group-name').addClass('unset').text('Unauthorized');
-            $('#read-only-process-group-comments').addClass('unset').text('Unauthorized');
+            $('#read-only-process-group-name').text('Unauthorized');
+            $('#read-only-process-group-comments').text('Unauthorized');
         };
 
         var setEditable = function (editable) {
@@ -201,8 +225,8 @@
                 } else {
                     if (response.permissions.canRead) {
                         // populate the process group settings
-                        $('#read-only-process-group-name').removeClass('unset').text(processGroup.name);
-                        $('#read-only-process-group-comments').removeClass('unset').text(processGroup.comments);
+                        $('#read-only-process-group-name').text(processGroup.name);
+                        $('#read-only-process-group-comments').text(processGroup.comments);
 
                         // populate the header
                         $('#process-group-configuration-header-text').text(processGroup.name + ' Configuration');
@@ -212,23 +236,25 @@
 
                     setEditable(false);
                 }
-                deferred.resolve();
+                deferred.resolve(response);
             }).fail(function (xhr, status, error) {
                 if (xhr.status === 403) {
+                    var unauthorizedGroup;
                     if (groupId === nfCanvasUtils.getGroupId()) {
-                        $('#process-group-configuration').data('process-group', {
+                        unauthorizedGroup = {
                             'permissions': {
                                 canRead: false,
                                 canWrite: nfCanvasUtils.canWriteCurrentGroup()
                             }
-                        });
+                        };
                     } else {
-                        $('#process-group-configuration').data('process-group', nfProcessGroup.get(groupId));
+                        unauthorizedGroup = nfProcessGroup.get(groupId);
                     }
+                    $('#process-group-configuration').data('process-group', unauthorizedGroup);
 
                     setUnauthorizedText();
                     setEditable(false);
-                    deferred.resolve();
+                    deferred.resolve(unauthorizedGroup);
                 } else {
                     deferred.reject(xhr, status, error);
                 }
@@ -239,12 +265,127 @@
         var controllerServicesUri = config.urls.api + '/flow/process-groups/' + encodeURIComponent(groupId) + '/controller-services';
         var controllerServices = nfControllerServices.loadControllerServices(controllerServicesUri, getControllerServicesTable());
 
+        var parameterContexts = $.ajax({
+                type: 'GET',
+                url: config.urls.parameterContexts,
+                dataType: 'json'
+            });
+
         // wait for everything to complete
-        return $.when(processGroup, controllerServices).done(function (processGroupResult, controllerServicesResult) {
+        return $.when(processGroup, controllerServices, parameterContexts).done(function (processGroupResult, controllerServicesResult, parameterContextsResult) {
             var controllerServicesResponse = controllerServicesResult[0];
+            var parameterContextsResponse = parameterContextsResult[0];
 
             // update the current time
             $('#process-group-configuration-last-refreshed').text(controllerServicesResponse.currentTime);
+
+            var parameterContexts = parameterContextsResponse.parameterContexts;
+            var options = [{
+                text: 'No parameter context',
+                value: null
+            }];
+            parameterContexts.forEach(function (parameterContext) {
+                var option;
+                if (parameterContext.permissions.canRead) {
+                    option = {
+                        'text': parameterContext.component.name,
+                        'value': parameterContext.id,
+                        'description': parameterContext.component.description
+                    };
+                } else {
+                    option = {
+                        'text': parameterContext.id,
+                        'value': parameterContext.id
+                    }
+                }
+
+                options.push(option);
+            });
+
+            var createNewParameterContextOption = {
+                text: 'Create new parameter context...',
+                value: undefined,
+                optionClass: 'unset'
+            };
+
+            if (nfCommon.canModifyParameterContexts()) {
+                options.push(createNewParameterContextOption);
+            }
+
+            var comboOptions = {
+                options: options,
+                select: function (option) {
+                    if (typeof option.value === 'undefined') {
+                        $('#parameter-context-dialog').modal('setHeaderText', 'Add Parameter Context').modal('setButtonModel', [{
+                            buttonText: 'Apply',
+                            color: {
+                                base: '#728E9B',
+                                hover: '#004849',
+                                text: '#ffffff'
+                            },
+                            disabled: function () {
+                                if ($('#parameter-context-name').val() !== '') {
+                                    return false;
+                                }
+                                return true;
+                            },
+                            handler: {
+                                click: function () {
+                                    nfParameterContexts.addParameterContext(function (parameterContextEntity) {
+                                        options.pop();
+                                        var option = {
+                                            'text': parameterContextEntity.component.name,
+                                            'value': parameterContextEntity.component.id,
+                                            'description': parameterContextEntity.component.description
+                                        };
+                                        options.push(option);
+
+                                        if (nfCommon.canModifyParameterContexts()) {
+                                            options.push(createNewParameterContextOption);
+                                        }
+
+                                        comboOptions.selectedOption = {
+                                            value: parameterContextEntity.component.id
+                                        };
+
+                                        combo.combo('destroy').combo(comboOptions);
+                                    });
+                                }
+                            }
+                        }, {
+                            buttonText: 'Cancel',
+                            color: {
+                                base: '#E3E8EB',
+                                hover: '#C7D2D7',
+                                text: '#004849'
+                            },
+                            handler: {
+                                click: function () {
+                                    $(this).modal('hide');
+                                }
+                            }
+                        }]).modal('show');
+
+                        // set the initial focus
+                        $('#parameter-context-name').focus();
+                    }
+                }
+            };
+
+            // initialize the parameter context combo
+            var combo = $('#process-group-parameter-context-combo').combo('destroy').combo(comboOptions);
+
+            // populate the parameter context
+            if (processGroupResult.permissions.canRead) {
+                var parameterContextId = null;
+                if ($.isEmptyObject(processGroupResult.component.parameterContext) === false) {
+                    parameterContextId = processGroupResult.component.parameterContext.id;
+                }
+
+                $('#process-group-parameter-context-combo').combo('setSelectedOption', {
+                    value: parameterContextId
+                });
+            }
         }).fail(nfErrorHandler.handleAjaxError);
     };
 
@@ -288,9 +429,11 @@
          * Initialize the process group configuration.
          *
          * @param nfControllerServicesRef   The nfControllerServices module.
+         * @param nfParameterContextsRef    The nfParameterContexts module.
          */
-        init: function (nfControllerServicesRef) {
+        init: function (nfControllerServicesRef, nfParameterContextsRef) {
             nfControllerServices = nfControllerServicesRef;
+            nfParameterContexts = nfParameterContextsRef;
 
             // initialize the process group configuration tabs
             $('#process-group-configuration-tabs').tabbs({
@@ -384,4 +527,4 @@
     };
 
     return nfProcessGroupConfiguration;
-}));
\ 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/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
index d6a5796..c2533f0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
@@ -1229,11 +1229,11 @@
 
                     // open the documentation for this reporting task
                     nfShell.showPage('../nifi-docs/documentation?' + $.param({
-                            select: reportingTaskEntity.component.type,
-                            group: reportingTaskEntity.component.bundle.group,
-                            artifact: reportingTaskEntity.component.bundle.artifact,
-                            version: reportingTaskEntity.component.bundle.version
-                        })).done(function () {
+                        select: reportingTaskEntity.component.type,
+                        group: reportingTaskEntity.component.bundle.group,
+                        artifact: reportingTaskEntity.component.bundle.artifact,
+                        version: reportingTaskEntity.component.bundle.version
+                    })).done(function () {
                         nfSettings.showSettings();
                     });
                 }
@@ -1944,4 +1944,4 @@
     };
 
     return nfSettings;
-}));
\ 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/nf/canvas/nf-variable-registry.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-variable-registry.js
index 120506e..cfd18f5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-variable-registry.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-variable-registry.js
@@ -576,7 +576,7 @@
                     // only populate affected components if this variable is different than the last selected
                     if (lastSelectedId === null || lastSelectedId !== variable.id) {
                         // update the details for this variable
-                        $('#affected-components-context').removeClass('unset').text(variable.name);
+                        $('#variable-affected-components-context').removeClass('unset').text(variable.name);
                         populateAffectedComponents(variable.affectedComponents);
 
                         // update the last selected id
@@ -1150,7 +1150,7 @@
                 $('<li class="affected-component-container"><span class="unset">None</span></li>').appendTo(unauthorizedComponentsContainer);
 
                 // update the selection context
-                $('#affected-components-context').addClass('unset').text('None');
+                $('#variable-affected-components-context').addClass('unset').text('None');
             } else {
                 // select the desired row
                 variableGrid.setSelectedRows([index]);
@@ -1202,7 +1202,7 @@
         var variableNames = variables.map(function (v) {
              return v.variable.name;
         });
-        $('#affected-components-context').removeClass('unset').text(variableNames.join(', '));
+        $('#variable-affected-components-context').removeClass('unset').text(variableNames.join(', '));
 
         // get the current group id
         var processGroupId = $('#variable-registry-process-group-id').text();
@@ -1533,7 +1533,7 @@
 
         $('#process-group-variable-registry').text('');
         $('#variable-registry-process-group-id').text('').removeData('revision');
-        $('#affected-components-context').removeClass('unset').text('');
+        $('#variable-affected-components-context').removeClass('unset').text('');
 
         var variableGrid = $('#variable-registry-table').data('gridInstance');
         var variableData = variableGrid.getData();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 7e5a1c0..1b918ff 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -115,7 +115,11 @@
     }, {
         text: 'access the controller',
         value: 'controller',
-        description: 'Allows users to view/modify the controller including Reporting Tasks, Controller Services, and Nodes in the Cluster'
+        description: 'Allows users to view/modify the controller including Reporting Tasks, Controller Services, Parameter Contexts, and Nodes in the Cluster'
+    }, {
+        text: 'access parameter contexts',
+        value: 'parameter-contexts',
+        description: 'Allows users to view/modify Parameter Contexts'
     }, {
         text: 'query provenance',
         value: 'provenance',
@@ -709,6 +713,19 @@
         },
 
         /**
+         * Determines whether the current user can modify parameter contexts.
+         *
+         * @returns {boolean}
+         */
+        canModifyParameterContexts: function () {
+            if (nfCommon.isDefinedAndNotNull(nfCommon.currentUser)) {
+                return nfCommon.currentUser.parameterContextPermissions.canRead === true && nfCommon.currentUser.parameterContextPermissions.canWrite === true;
+            } else {
+                return false;
+            }
+        },
+
+        /**
          * Determines whether the current user can access counters.
          *
          * @returns {boolean}