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

[34/50] nifi git commit: NIFI-4436: - Code clean up. - Improved error handling. - Minor UX improvements. - Always showing Process Group state to complement the aggregation counts. - Adding the Process Group state to the top status bar.

NIFI-4436:
- Code clean up.
- Improved error handling.
- Minor UX improvements.
- Always showing Process Group state to complement the aggregation counts.
- Adding the Process Group state to the top status bar.


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/1266235c
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/1266235c
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/1266235c

Branch: refs/heads/master
Commit: 1266235c00345f222b34480be00ba0db951e2077
Parents: fe8b30b
Author: Matt Gilman <ma...@gmail.com>
Authored: Mon Dec 11 15:14:44 2017 -0500
Committer: Bryan Bende <bb...@apache.org>
Committed: Mon Jan 8 12:44:55 2018 -0500

----------------------------------------------------------------------
 .../web/api/dto/status/ControllerStatusDTO.java |  56 ++++
 .../web/api/entity/FlowBreadcrumbEntity.java    |  12 +
 .../nifi/web/api/entity/ProcessGroupEntity.java |  69 ++++-
 .../apache/nifi/web/api/ControllerResource.java |  41 ++-
 .../apache/nifi/web/api/VersionsResource.java   |  28 +-
 .../apache/nifi/web/api/dto/EntityFactory.java  |  17 ++
 .../nifi/web/controller/ControllerFacade.java   |   5 +
 .../WEB-INF/partials/canvas/flow-status.jsp     |   7 +
 .../canvas/new-process-group-dialog.jsp         |   5 +-
 .../src/main/webapp/css/common-ui.css           |   2 +-
 .../src/main/webapp/css/navigation.css          |  22 +-
 .../controllers/nf-ng-breadcrumbs-controller.js |   8 +-
 .../nf-ng-canvas-flow-status-controller.js      |  50 +++
 .../main/webapp/js/nf/canvas/nf-flow-version.js |  56 ++--
 .../src/main/webapp/js/nf/canvas/nf-port.js     |   5 +-
 .../webapp/js/nf/canvas/nf-process-group.js     | 301 +++++++++----------
 .../main/webapp/js/nf/canvas/nf-processor.js    |   5 +-
 .../js/nf/canvas/nf-remote-process-group.js     |  19 +-
 18 files changed, 456 insertions(+), 252 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerStatusDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerStatusDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerStatusDTO.java
index eb31597..cddf85e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerStatusDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerStatusDTO.java
@@ -38,6 +38,12 @@ public class ControllerStatusDTO implements Cloneable {
     private Integer activeRemotePortCount = 0;
     private Integer inactiveRemotePortCount = 0;
 
+    private Integer upToDateCount;
+    private Integer locallyModifiedCount;
+    private Integer staleCount;
+    private Integer locallyModifiedAndStaleCount;
+    private Integer syncFailureCount;
+
     /**
      * The active thread count.
      *
@@ -154,6 +160,51 @@ public class ControllerStatusDTO implements Cloneable {
         this.bytesQueued = bytesQueued;
     }
 
+    @ApiModelProperty("The number of up to date versioned process groups in the NiFi.")
+    public Integer getUpToDateCount() {
+        return upToDateCount;
+    }
+
+    public void setUpToDateCount(Integer upToDateCount) {
+        this.upToDateCount = upToDateCount;
+    }
+
+    @ApiModelProperty("The number of locally modified versioned process groups in the NiFi.")
+    public Integer getLocallyModifiedCount() {
+        return locallyModifiedCount;
+    }
+
+    public void setLocallyModifiedCount(Integer locallyModifiedCount) {
+        this.locallyModifiedCount = locallyModifiedCount;
+    }
+
+    @ApiModelProperty("The number of stale versioned process groups in the NiFi.")
+    public Integer getStaleCount() {
+        return staleCount;
+    }
+
+    public void setStaleCount(Integer staleCount) {
+        this.staleCount = staleCount;
+    }
+
+    @ApiModelProperty("The number of locally modified and stale versioned process groups in the NiFi.")
+    public Integer getLocallyModifiedAndStaleCount() {
+        return locallyModifiedAndStaleCount;
+    }
+
+    public void setLocallyModifiedAndStaleCount(Integer locallyModifiedAndStaleCount) {
+        this.locallyModifiedAndStaleCount = locallyModifiedAndStaleCount;
+    }
+
+    @ApiModelProperty("The number of versioned process groups in the NiFi that are unable to sync to a registry.")
+    public Integer getSyncFailureCount() {
+        return syncFailureCount;
+    }
+
+    public void setSyncFailureCount(Integer syncFailureCount) {
+        this.syncFailureCount = syncFailureCount;
+    }
+
     @Override
     public ControllerStatusDTO clone() {
         final ControllerStatusDTO other = new ControllerStatusDTO();
@@ -167,6 +218,11 @@ public class ControllerStatusDTO implements Cloneable {
         other.setDisabledCount(getDisabledCount());
         other.setActiveRemotePortCount(getActiveRemotePortCount());
         other.setInactiveRemotePortCount(getInactiveRemotePortCount());
+        other.setUpToDateCount(getUpToDateCount());
+        other.setLocallyModifiedCount(getLocallyModifiedCount());
+        other.setStaleCount(getStaleCount());
+        other.setLocallyModifiedAndStaleCount(getLocallyModifiedAndStaleCount());
+        other.setStaleCount(getStaleCount());
         return other;
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowBreadcrumbEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowBreadcrumbEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowBreadcrumbEntity.java
index b97b9b3..21f9742 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowBreadcrumbEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/FlowBreadcrumbEntity.java
@@ -30,6 +30,7 @@ public class FlowBreadcrumbEntity extends Entity {
 
     private String id;
     private PermissionsDTO permissions;
+    private String state;
     private FlowBreadcrumbDTO breadcrumb;
     private FlowBreadcrumbEntity parentBreadcrumb;
 
@@ -96,4 +97,15 @@ public class FlowBreadcrumbEntity extends Entity {
     public void setParentBreadcrumb(FlowBreadcrumbEntity parentBreadcrumb) {
         this.parentBreadcrumb = parentBreadcrumb;
     }
+
+    @ApiModelProperty(readOnly = true,
+            value = "The current state of the Process Group, as it relates to the Versioned Flow",
+            allowableValues = "LOCALLY_MODIFIED_DESCENDANT, LOCALLY_MODIFIED, STALE, LOCALLY_MODIFIED_AND_STALE, UP_TO_DATE")
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessGroupEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessGroupEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessGroupEntity.java
index 1e2a4b4..fe8d2d6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessGroupEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessGroupEntity.java
@@ -16,13 +16,12 @@
  */
 package org.apache.nifi.web.api.entity;
 
-import javax.xml.bind.annotation.XmlRootElement;
-
+import io.swagger.annotations.ApiModelProperty;
 import org.apache.nifi.registry.flow.VersionedFlowSnapshot;
 import org.apache.nifi.web.api.dto.ProcessGroupDTO;
 import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
 
-import io.swagger.annotations.ApiModelProperty;
+import javax.xml.bind.annotation.XmlRootElement;
 
 /**
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a ProcessGroupDTO.
@@ -41,6 +40,14 @@ public class ProcessGroupEntity extends ComponentEntity implements Permissible<P
     private Integer activeRemotePortCount;
     private Integer inactiveRemotePortCount;
 
+    private String state;
+
+    private Integer upToDateCount;
+    private Integer locallyModifiedCount;
+    private Integer staleCount;
+    private Integer locallyModifiedAndStaleCount;
+    private Integer syncFailureCount;
+
     private Integer inputPortCount;
     private Integer outputPortCount;
 
@@ -193,4 +200,60 @@ public class ProcessGroupEntity extends ComponentEntity implements Permissible<P
     public void setVersionedFlowSnapshot(VersionedFlowSnapshot versionedFlowSnapshot) {
         this.versionedFlowSnapshot = versionedFlowSnapshot;
     }
+
+    @ApiModelProperty(readOnly = true,
+            value = "The current state of the Process Group, as it relates to the Versioned Flow",
+            allowableValues = "LOCALLY_MODIFIED_DESCENDANT, LOCALLY_MODIFIED, STALE, LOCALLY_MODIFIED_AND_STALE, UP_TO_DATE")
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    @ApiModelProperty("The number of up to date versioned process groups in the process group.")
+    public Integer getUpToDateCount() {
+        return upToDateCount;
+    }
+
+    public void setUpToDateCount(Integer upToDateCount) {
+        this.upToDateCount = upToDateCount;
+    }
+
+    @ApiModelProperty("The number of locally modified versioned process groups in the process group.")
+    public Integer getLocallyModifiedCount() {
+        return locallyModifiedCount;
+    }
+
+    public void setLocallyModifiedCount(Integer locallyModifiedCount) {
+        this.locallyModifiedCount = locallyModifiedCount;
+    }
+
+    @ApiModelProperty("The number of stale versioned process groups in the process group.")
+    public Integer getStaleCount() {
+        return staleCount;
+    }
+
+    public void setStaleCount(Integer staleCount) {
+        this.staleCount = staleCount;
+    }
+
+    @ApiModelProperty("The number of locally modified and stale versioned process groups in the process group.")
+    public Integer getLocallyModifiedAndStaleCount() {
+        return locallyModifiedAndStaleCount;
+    }
+
+    public void setLocallyModifiedAndStaleCount(Integer locallyModifiedAndStaleCount) {
+        this.locallyModifiedAndStaleCount = locallyModifiedAndStaleCount;
+    }
+
+    @ApiModelProperty("The number of versioned process groups in the process group that are unable to sync to a registry.")
+    public Integer getSyncFailureCount() {
+        return syncFailureCount;
+    }
+
+    public void setSyncFailureCount(Integer syncFailureCount) {
+        this.syncFailureCount = syncFailureCount;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index 04c0efa..c5c40be 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -238,7 +238,6 @@ public class ControllerResource extends ApplicationResource {
                     @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
             }
     )
-
     public Response createReportingTask(
             @Context final HttpServletRequest httpServletRequest,
             @ApiParam(
@@ -383,11 +382,19 @@ public class ControllerResource extends ApplicationResource {
             throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Registry.");
         }
 
-        final RegistryDTO requestReportingTask = requestRegistryClientEntity.getComponent();
-        if (requestReportingTask.getId() != null) {
+        final RegistryDTO requestRegistryClient = requestRegistryClientEntity.getComponent();
+        if (requestRegistryClient.getId() != null) {
             throw new IllegalArgumentException("Registry ID cannot be specified.");
         }
 
+        if (StringUtils.isBlank(requestRegistryClient.getName())) {
+            throw new IllegalArgumentException("Registry name must be specified.");
+        }
+
+        if (StringUtils.isBlank(requestRegistryClient.getUri())) {
+            throw new IllegalArgumentException("Registry URL must be specified.");
+        }
+
         if (isReplicateRequest()) {
             return replicate(HttpMethod.POST, requestRegistryClientEntity);
         }
@@ -468,7 +475,7 @@ public class ControllerResource extends ApplicationResource {
      *
      * @param httpServletRequest      request
      * @param id                      The id of the controller service to update.
-     * @param requestRegsitryEntity A controllerServiceEntity.
+     * @param requestRegistryEntity A controllerServiceEntity.
      * @return A controllerServiceEntity.
      */
     @PUT
@@ -501,32 +508,40 @@ public class ControllerResource extends ApplicationResource {
             @ApiParam(
                     value = "The registry configuration details.",
                     required = true
-            ) final RegistryClientEntity requestRegsitryEntity) {
+            ) final RegistryClientEntity requestRegistryEntity) {
 
-        if (requestRegsitryEntity == null || requestRegsitryEntity.getComponent() == null) {
+        if (requestRegistryEntity == null || requestRegistryEntity.getComponent() == null) {
             throw new IllegalArgumentException("Registry details must be specified.");
         }
 
-        if (requestRegsitryEntity.getRevision() == null) {
+        if (requestRegistryEntity.getRevision() == null) {
             throw new IllegalArgumentException("Revision must be specified.");
         }
 
         // ensure the ids are the same
-        final RegistryDTO requestRegistryDTO = requestRegsitryEntity.getComponent();
-        if (!id.equals(requestRegistryDTO.getId())) {
+        final RegistryDTO requestRegistryClient = requestRegistryEntity.getComponent();
+        if (!id.equals(requestRegistryClient.getId())) {
             throw new IllegalArgumentException(String.format("The registry id (%s) in the request body does not equal the "
-                    + "registry id of the requested resource (%s).", requestRegistryDTO.getId(), id));
+                    + "registry id of the requested resource (%s).", requestRegistryClient.getId(), id));
         }
 
         if (isReplicateRequest()) {
-            return replicate(HttpMethod.PUT, requestRegsitryEntity);
+            return replicate(HttpMethod.PUT, requestRegistryEntity);
+        }
+
+        if (requestRegistryClient.getName() != null && StringUtils.isBlank(requestRegistryClient.getName())) {
+            throw new IllegalArgumentException("Registry name must be specified.");
+        }
+
+        if (requestRegistryClient.getUri() != null && StringUtils.isBlank(requestRegistryClient.getUri())) {
+            throw new IllegalArgumentException("Registry URL must be specified.");
         }
 
         // handle expects request (usually from the cluster manager)
-        final Revision requestRevision = getRevision(requestRegsitryEntity, id);
+        final Revision requestRevision = getRevision(requestRegistryEntity, id);
         return withWriteLock(
                 serviceFacade,
-                requestRegsitryEntity,
+                requestRegistryEntity,
                 requestRevision,
                 lookup -> {
                     authorizeController(RequestAction.WRITE);

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
index 950bd97..3090c6e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
@@ -175,7 +175,10 @@ public class VersionsResource extends ApplicationResource {
             + "prevent any other threads from simultaneously saving local changes to Version Control. It will not, however, actually save the local flow to the Flow Registry. A "
             + "POST to /versions/process-groups/{id} should be used to initiate saving of the local flow to the Flow Registry.",
             response = String.class,
-            notes = NON_GUARANTEED_ENDPOINT)
+        notes = NON_GUARANTEED_ENDPOINT,
+        authorizations = {
+            @Authorization(value = "Write - /process-groups/{uuid}")
+        })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
         @ApiResponse(code = 401, message = "Client could not be authenticated."),
@@ -186,14 +189,14 @@ public class VersionsResource extends ApplicationResource {
     public Response createVersionControlRequest(
             @ApiParam(value = "The versioned flow details.", required = true) final CreateActiveRequestEntity requestEntity) {
 
-        if (isReplicateRequest()) {
-            return replicate(HttpMethod.POST, requestEntity);
-        }
-
         if (requestEntity.getProcessGroupId() == null) {
             throw new IllegalArgumentException("The id of the process group that will be updated must be specified.");
         }
 
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.POST, requestEntity);
+        }
+
         final NiFiUser user = NiFiUserUtils.getNiFiUser();
 
         return withWriteLock(
@@ -234,7 +237,7 @@ public class VersionsResource extends ApplicationResource {
             response = VersionControlInformationEntity.class,
             notes = NON_GUARANTEED_ENDPOINT,
             authorizations = {
-                @Authorization(value = "Write - /process-groups/{uuid}")
+                @Authorization(value = "Only the user that submitted the request can update it")
             })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@@ -341,9 +344,12 @@ public class VersionsResource extends ApplicationResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("active-requests/{id}")
     @ApiOperation(
-        value = "Deletes the Version Control Request with the given ID. This will allow other threads to save flows to the Flow Registry. See also the documentation "
-            + "for POSTing to /versions/active-requests for information regarding why this is done.",
-            notes = NON_GUARANTEED_ENDPOINT)
+            value = "Deletes the Version Control Request with the given ID. This will allow other threads to save flows to the Flow Registry. See also the documentation "
+                + "for POSTing to /versions/active-requests for information regarding why this is done.",
+            notes = NON_GUARANTEED_ENDPOINT,
+            authorizations = {
+                @Authorization(value = "Only the user that submitted the request can remove it")
+            })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
         @ApiResponse(code = 401, message = "Client could not be authenticated."),
@@ -801,6 +807,7 @@ public class VersionsResource extends ApplicationResource {
             response = VersionedFlowUpdateRequestEntity.class,
             notes = NON_GUARANTEED_ENDPOINT,
             authorizations = {
+                @Authorization(value = "Only the user that submitted the request can get it")
             })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@@ -824,6 +831,7 @@ public class VersionsResource extends ApplicationResource {
             response = VersionedFlowUpdateRequestEntity.class,
             notes = NON_GUARANTEED_ENDPOINT,
             authorizations = {
+                @Authorization(value = "Only the user that submitted the request can get it")
             })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@@ -881,6 +889,7 @@ public class VersionsResource extends ApplicationResource {
             response = VersionedFlowUpdateRequestEntity.class,
             notes = NON_GUARANTEED_ENDPOINT,
             authorizations = {
+                @Authorization(value = "Only the user that submitted the request can remove it")
             })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@@ -904,6 +913,7 @@ public class VersionsResource extends ApplicationResource {
             response = VersionedFlowUpdateRequestEntity.class,
             notes = NON_GUARANTEED_ENDPOINT,
             authorizations = {
+                @Authorization(value = "Only the user that submitted the request can remove it")
             })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
----------------------------------------------------------------------
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 34f4997..6a73e3a 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
@@ -224,6 +224,7 @@ public final class EntityFactory {
             entity.setStatus(status);
             entity.setId(dto.getId());
             entity.setPosition(dto.getPosition());
+
             entity.setInputPortCount(dto.getInputPortCount());
             entity.setOutputPortCount(dto.getOutputPortCount());
             entity.setRunningCount(dto.getRunningCount());
@@ -232,6 +233,17 @@ public final class EntityFactory {
             entity.setDisabledCount(dto.getDisabledCount());
             entity.setActiveRemotePortCount(dto.getActiveRemotePortCount());
             entity.setInactiveRemotePortCount(dto.getInactiveRemotePortCount());
+
+            entity.setUpToDateCount(dto.getUpToDateCount());
+            entity.setLocallyModifiedCount(dto.getLocallyModifiedCount());
+            entity.setStaleCount(dto.getStaleCount());
+            entity.setLocallyModifiedAndStaleCount(dto.getLocallyModifiedAndStaleCount());
+            entity.setSyncFailureCount(dto.getSyncFailureCount());
+
+            if (dto.getVersionControlInformation() != null) {
+                entity.setState(dto.getVersionControlInformation().getState());
+            }
+
             entity.setBulletins(bulletins); // include bulletins as authorized descendant component bulletins should be available
             if (permissions != null && permissions.getCanRead()) {
                 entity.setComponent(dto);
@@ -499,6 +511,11 @@ public final class EntityFactory {
         if (dto != null) {
             entity.setPermissions(permissions);
             entity.setId(dto.getId());
+
+            if (dto.getVersionControlInformation() != null) {
+                entity.setState(dto.getVersionControlInformation().getState());
+            }
+
             if (permissions != null && permissions.getCanRead()) {
                 entity.setBreadcrumb(dto);
             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 907c8dc..6cef841 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -593,6 +593,11 @@ public class ControllerFacade implements Authorizable {
         controllerStatus.setDisabledCount(counts.getDisabledCount());
         controllerStatus.setActiveRemotePortCount(counts.getActiveRemotePortCount());
         controllerStatus.setInactiveRemotePortCount(counts.getInactiveRemotePortCount());
+        controllerStatus.setUpToDateCount(counts.getUpToDateCount());
+        controllerStatus.setLocallyModifiedCount(counts.getLocallyModifiedCount());
+        controllerStatus.setStaleCount(counts.getStaleCount());
+        controllerStatus.setLocallyModifiedAndStaleCount(counts.getLocallyModifiedAndStaleCount());
+        controllerStatus.setSyncFailureCount(counts.getSyncFailureCount());
 
         return controllerStatus;
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
index 81e9ef0..9cb0ab9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/flow-status.jsp
@@ -26,6 +26,13 @@
         <div class="fa fa-stop"><span id="controller-stopped-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerStoppedCount}}</span></div>
         <div class="fa fa-warning"><span id="controller-invalid-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerInvalidCount}}</span></div>
         <div class="icon icon-enable-false"><span id="controller-disabled-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerDisabledCount}}</span></div>
+        <div class="fa fa-check"><span id="controller-up-to-date-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerUpToDateCount}}</span></div>
+        <div class="fa fa-asterisk"><span id="controller-locally-modified-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerLocallyModifiedCount}}</span></div>
+        <div class="fa fa-arrow-circle-up"><span id="controller-stale-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerStaleCount}}</span></div>
+        <div class="fa fa-exclamation-circle">
+            <span id="controller-locally-modified-and-stale-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerLocallyModifiedAndStaleCount}}</span>
+        </div>
+        <div class="fa fa-question"><span id="controller-sync-failure-count">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.controllerSyncFailureCount}}</span></div>
         <div class="fa fa-refresh"><span id="stats-last-refreshed">{{appCtrl.serviceProvider.headerCtrl.flowStatusCtrl.statsLastRefreshed}}</span></div>
         <div id="canvas-loading-container" class="loading-container"></div>
     </div>

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-process-group-dialog.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-process-group-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-process-group-dialog.jsp
index 3f1b6a0..9066e9e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-process-group-dialog.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/new-process-group-dialog.jsp
@@ -24,7 +24,10 @@
             </div>
         </div>
         <div class="setting">
-            <span id="import-process-group-link" class="link"><i class="fa fa-cloud-download" aria-hidden="true" style="margin-left: 5px; margin-right: 5px;"></i>Import version...</span>
+            <span id="import-process-group-link" class="link" title="Import a flow from a registry">
+                <i class="fa fa-cloud-download" aria-hidden="true" style="margin-left: 5px; margin-right: 5px;"></i>
+                Import...
+            </span>
         </div>
     </div>
 </div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
index ac4cb62..a7de12f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/common-ui.css
@@ -417,7 +417,7 @@ button.fa {
     color: #004849;
     font-size: 16px;
     cursor: pointer;
-    line-height: 23px;
+    line-height: 25px;
 }
 
 button.icon {

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
index 85b499e..17200d7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/navigation.css
@@ -14,24 +14,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* styles for the graph pan/zoom controls */
-
-/*
- * 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.
- */
 
 /* general graph control styles */
 
@@ -50,7 +32,7 @@
 
 #graph-controls .fa {
     font-size: 18px;
-    margin-left: -1px;
+    margin-left: -2px;
 }
 
 .graph-control-header-icon.fa {
@@ -93,7 +75,7 @@ div.graph-control-docked {
 }
 
 div.graph-control button {
-    line-height: 28px;
+    line-height: 30px;
     border: 1px solid #CCDADB; /*tint link-color 80%*/
     background-color: rgba(249,250,251,1);
     color: #004849;

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-controller.js
----------------------------------------------------------------------
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-breadcrumbs-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-breadcrumbs-controller.js
index fc5599d..64f2117 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-breadcrumbs-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-breadcrumbs-controller.js
@@ -103,7 +103,7 @@
              * @returns {*}
              */
             isTracking: function (breadcrumbEntity) {
-                return nfCommon.isDefinedAndNotNull(breadcrumbEntity.breadcrumb.versionControlInformation);
+                return nfCommon.isDefinedAndNotNull(breadcrumbEntity.state);
             },
 
             /**
@@ -113,8 +113,8 @@
              * @returns {string}
              */
             getVersionControlClass: function (breadcrumbEntity) {
-                if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.breadcrumb.versionControlInformation)) {
-                    var vciState = breadcrumbEntity.breadcrumb.versionControlInformation.state;
+                if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.state)) {
+                    var vciState = breadcrumbEntity.state;
                     if (vciState === 'SYNC_FAILURE') {
                         return 'breadcrumb-version-control-gray fa fa-question'
                     } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
@@ -137,7 +137,7 @@
              * @param breadcrumbEntity
              */
             getVersionControlTooltip: function (breadcrumbEntity) {
-                if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.breadcrumb.versionControlInformation)) {
+                if (nfCommon.isDefinedAndNotNull(breadcrumbEntity.state) && breadcrumbEntity.permissions.canRead) {
                     return nfCommon.getVersionControlTooltip(breadcrumbEntity.breadcrumb.versionControlInformation);
                 } else {
                     return 'This Process Group is not under version control.'

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/controllers/nf-ng-canvas-flow-status-controller.js
----------------------------------------------------------------------
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-flow-status-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-flow-status-controller.js
index 16f40f1..038a07b 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-flow-status-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-flow-status-controller.js
@@ -74,6 +74,11 @@
             this.controllerStoppedCount = "-";
             this.controllerInvalidCount = "-";
             this.controllerDisabledCount = "-";
+            this.controllerUpToDateCount = "-";
+            this.controllerLocallyModifiedCount = "-";
+            this.controllerStaleCount = "-";
+            this.controllerLocallyModifiedAndStaleCount = "-";
+            this.controllerSyncFailureCount = "-";
             this.statsLastRefreshed = "-";
 
             /**
@@ -502,6 +507,51 @@
                     $('#flow-status-container').find('.icon-enable-false').removeClass('disabled').addClass('zero');
                 }
 
+                this.controllerUpToDateCount =
+                    nfCommon.isDefinedAndNotNull(status.upToDateCount) ? status.upToDateCount : '-';
+
+                if (this.controllerUpToDateCount > 0) {
+                    $('#flow-status-container').find('.fa-check').removeClass('zero').addClass('up-to-date');
+                } else {
+                    $('#flow-status-container').find('.fa-check').removeClass('up-to-date').addClass('zero');
+                }
+
+                this.controllerLocallyModifiedCount =
+                    nfCommon.isDefinedAndNotNull(status.locallyModifiedCount) ? status.locallyModifiedCount : '-';
+
+                if (this.controllerLocallyModifiedCount > 0) {
+                    $('#flow-status-container').find('.fa-asterisk').removeClass('zero').addClass('locally-modified');
+                } else {
+                    $('#flow-status-container').find('.fa-asterisk').removeClass('locally-modified').addClass('zero');
+                }
+
+                this.controllerStaleCount =
+                    nfCommon.isDefinedAndNotNull(status.staleCount) ? status.staleCount : '-';
+
+                if (this.controllerStaleCount > 0) {
+                    $('#flow-status-container').find('.fa-arrow-circle-up').removeClass('zero').addClass('stale');
+                } else {
+                    $('#flow-status-container').find('.fa-arrow-circle-up').removeClass('stale').addClass('zero');
+                }
+
+                this.controllerLocallyModifiedAndStaleCount =
+                    nfCommon.isDefinedAndNotNull(status.locallyModifiedAndStaleCount) ? status.locallyModifiedAndStaleCount : '-';
+
+                if (this.controllerLocallyModifiedAndStaleCount > 0) {
+                    $('#flow-status-container').find('.fa-exclamation-circle').removeClass('zero').addClass('locally-modified-and-stale');
+                } else {
+                    $('#flow-status-container').find('.fa-exclamation-circle').removeClass('locally-modified-and-stale').addClass('zero');
+                }
+
+                this.controllerSyncFailureCount =
+                    nfCommon.isDefinedAndNotNull(status.syncFailureCount) ? status.syncFailureCount : '-';
+
+                if (this.controllerSyncFailureCount > 0) {
+                    $('#flow-status-container').find('.fa-question').removeClass('zero').addClass('sync-failure');
+                } else {
+                    $('#flow-status-container').find('.fa-question').removeClass('sync-failure').addClass('zero');
+                }
+
             },
 
             /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js
index f1b9a45..01a0a07 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-flow-version.js
@@ -90,7 +90,7 @@
         $('#save-flow-version-bucket').text('').hide();
 
         $('#save-flow-version-name').text('').hide();
-        $('#save-flow-version-description').text('').hide();
+        $('#save-flow-version-description').removeClass('unset blank').text('').hide();
 
         $('#save-flow-version-name-field').val('').hide();
         $('#save-flow-version-description-field').val('').hide();
@@ -277,14 +277,17 @@
                     optionClass: 'unset',
                     disabled: true
                 });
-                flowCombo.combo('destroy').combo({
-                    options: [{
-                        text: 'No available flows',
-                        value: null,
-                        optionClass: 'unset',
-                        disabled: true
-                    }]
-                });
+
+                if (nfCommon.isDefinedAndNotNull(flowCombo)) {
+                    flowCombo.combo('destroy').combo({
+                        options: [{
+                            text: 'No available flows',
+                            value: null,
+                            optionClass: 'unset',
+                            disabled: true
+                        }]
+                    });
+                }
             }
 
             // load the buckets
@@ -315,14 +318,17 @@
                     disabled: true
                 }]
             });
-            flowCombo.combo('destroy').combo({
-                options: [{
-                    text: 'No available flows',
-                    value: null,
-                    optionClass: 'unset',
-                    disabled: true
-                }]
-            });
+
+            if (nfCommon.isDefinedAndNotNull(flowCombo)) {
+                flowCombo.combo('destroy').combo({
+                    options: [{
+                        text: 'No available flows',
+                        value: null,
+                        optionClass: 'unset',
+                        disabled: true
+                    }]
+                });
+            }
 
             dialog.modal('refreshButtons');
         };
@@ -390,8 +396,6 @@
                 registryId: versionControlInformation.registryId,
                 bucketId: versionControlInformation.bucketId,
                 flowId: versionControlInformation.flowId,
-                flowName: $('#save-flow-version-name').text(),
-                description: $('#save-flow-version-description').text(),
                 comments: $('#save-flow-version-change-comments').val()
             }
         } else {
@@ -1598,9 +1602,10 @@
                             var processGroupId = $('#save-flow-version-process-group-id').text();
                             saveFlowVersion().done(function (response) {
                                 updateVersionControlInformation(processGroupId, response.versionControlInformation);
-                            });
 
-                            $(this).modal('hide');
+                                // only hide the dialog if the flow version was successfully saved
+                                $(this).modal('hide');
+                            });
                         }
                     }
                 }, {
@@ -1729,9 +1734,6 @@
 
             return $.Deferred(function (deferred) {
                 getVersionControlInformation(processGroupId).done(function (groupVersionControlInformation) {
-                    // record the revision
-                    $('#save-flow-version-process-group-id').data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId);
-
                     if (nfCommon.isDefinedAndNotNull(groupVersionControlInformation.versionControlInformation)) {
                         var versionControlInformation = groupVersionControlInformation.versionControlInformation;
 
@@ -1741,7 +1743,8 @@
                         $('#save-flow-version-label').text(versionControlInformation.version + 1);
 
                         $('#save-flow-version-name').text(versionControlInformation.flowName).show();
-                        $('#save-flow-version-description').text(versionControlInformation.flowDescription).show();
+                        nfCommon.populateField('save-flow-version-description', versionControlInformation.flowDescription);
+                        $('#save-flow-version-description').show();
 
                         // record the versionControlInformation
                         $('#save-flow-version-process-group-id').data('versionControlInformation', versionControlInformation);
@@ -1787,6 +1790,9 @@
                             deferred.reject();
                         });
                     }
+
+                    // record the revision
+                    $('#save-flow-version-process-group-id').data('revision', groupVersionControlInformation.processGroupRevision).text(processGroupId);
                 }).fail(nfErrorHandler.handleAjaxError);
             }).done(function () {
                 $('#save-flow-version-dialog').modal('show');

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
index 66c5542..1799a86 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
@@ -368,7 +368,10 @@
                     port.select('text.port-name').text(null);
 
                     // clear the port comments
-                    port.select('path.component-comments').style('visibility', false);
+                    port.select('path.component-comments').style('visibility', 'hidden');
+
+                    // clear tooltips
+                    port.call(removeTooltips);
                 }
 
                 // populate the stats

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
----------------------------------------------------------------------
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.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
index 27004b4..433c59f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
@@ -91,7 +91,7 @@
      * @param d
      */
     var isUnderVersionControl = function (d) {
-        return nfCommon.isDefinedAndNotNull(d.component.versionControlInformation);
+        return nfCommon.isDefinedAndNotNull(d.state);
     };
 
     /**
@@ -953,48 +953,152 @@
                         return d.disabledCount;
                     });
 
-                if (processGroupData.permissions.canRead) {
-                    // update version control information
-                    var versionControl = processGroup.select('text.version-control')
-                        .style({
-                            'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden',
-                            'fill': function () {
-                                if (isUnderVersionControl(processGroupData)) {
-                                    var vciState = processGroupData.component.versionControlInformation.state;
-                                    if (vciState === 'SYNC_FAILURE') {
-                                        return '#666666';
-                                    } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
-                                        return '#BA554A';
-                                    } else if (vciState === 'STALE') {
-                                        return '#BA554A';
-                                    } else if (vciState === 'LOCALLY_MODIFIED') {
-                                        return '#666666';
-                                    } else {
-                                        return '#1A9964';
-                                    }
-                                } else {
-                                    return '#000';
-                                }
-                            }
-                        })
-                        .text(function () {
+                // up to date current
+                var upToDate = details.select('text.process-group-up-to-date')
+                    .classed('up-to-date', function (d) {
+                        return d.permissions.canRead && d.component.upToDateCount > 0;
+                    })
+                    .classed('zero', function (d) {
+                        return d.permissions.canRead && d.component.upToDateCount === 0;
+                    });
+                var upToDateCount = details.select('text.process-group-up-to-date-count')
+                    .attr('x', function () {
+                        var updateToDateCountX = parseInt(upToDate.attr('x'), 10);
+                        return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+                    })
+                    .text(function (d) {
+                        return d.upToDateCount;
+                    });
+
+                // update locally modified
+                var locallyModified = details.select('text.process-group-locally-modified')
+                    .classed('locally-modified', function (d) {
+                        return d.permissions.canRead && d.component.locallyModifiedCount > 0;
+                    })
+                    .classed('zero', function (d) {
+                        return d.permissions.canRead && d.component.locallyModifiedCount === 0;
+                    })
+                    .attr('x', function () {
+                        var upToDateX = parseInt(upToDateCount.attr('x'), 10);
+                        return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+                    });
+                var locallyModifiedCount = details.select('text.process-group-locally-modified-count')
+                    .attr('x', function () {
+                        var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10);
+                        return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+                    })
+                    .text(function (d) {
+                        return d.locallyModifiedCount;
+                    });
+
+                // update stale
+                var stale = details.select('text.process-group-stale')
+                    .classed('stale', function (d) {
+                        return d.permissions.canRead && d.component.staleCount > 0;
+                    })
+                    .classed('zero', function (d) {
+                        return d.permissions.canRead && d.component.staleCount === 0;
+                    })
+                    .attr('x', function () {
+                        var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10);
+                        return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+                    });
+                var staleCount = details.select('text.process-group-stale-count')
+                    .attr('x', function () {
+                        var staleCountX = parseInt(stale.attr('x'), 10);
+                        return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+                    })
+                    .text(function (d) {
+                        return d.staleCount;
+                    });
+
+                // update locally modified and stale
+                var locallyModifiedAndStale = details.select('text.process-group-locally-modified-and-stale')
+                    .classed('locally-modified-and-stale', function (d) {
+                        return d.permissions.canRead && d.component.locallyModifiedAndStaleCount > 0;
+                    })
+                    .classed('zero', function (d) {
+                        return d.permissions.canRead && d.component.locallyModifiedAndStaleCount === 0;
+                    })
+                    .attr('x', function () {
+                        var staleX = parseInt(staleCount.attr('x'), 10);
+                        return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER;
+                    });
+                var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count')
+                    .attr('x', function () {
+                        var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10);
+                        return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+                    })
+                    .text(function (d) {
+                        return d.locallyModifiedAndStaleCount;
+                    });
+
+                // update sync failure
+                var syncFailure = details.select('text.process-group-sync-failure')
+                    .classed('sync-failure', function (d) {
+                        return d.permissions.canRead && d.component.syncFailureCount > 0;
+                    })
+                    .classed('zero', function (d) {
+                        return d.permissions.canRead && d.component.syncFailureCount === 0;
+                    })
+                    .attr('x', function () {
+                        var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10);
+                        return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2;
+                    });
+                details.select('text.process-group-sync-failure-count')
+                    .attr('x', function () {
+                        var syncFailureCountX = parseInt(syncFailure.attr('x'), 10);
+                        return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
+                    })
+                    .text(function (d) {
+                        return d.syncFailureCount;
+                    });
+
+                // update version control information
+                var versionControl = processGroup.select('text.version-control')
+                    .style({
+                        'visibility': isUnderVersionControl(processGroupData) ? 'visible' : 'hidden',
+                        'fill': function () {
                             if (isUnderVersionControl(processGroupData)) {
-                                var vciState = processGroupData.component.versionControlInformation.state;
+                                var vciState = processGroupData.state;
                                 if (vciState === 'SYNC_FAILURE') {
-                                    return '\uf128'
+                                    return '#666666';
                                 } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
-                                    return '\uf06a';
+                                    return '#BA554A';
                                 } else if (vciState === 'STALE') {
-                                    return '\uf0aa';
+                                    return '#BA554A';
                                 } else if (vciState === 'LOCALLY_MODIFIED') {
-                                    return '\uf069';
+                                    return '#666666';
                                 } else {
-                                    return '\uf00c';
+                                    return '#1A9964';
                                 }
                             } else {
-                                return '';
+                                return '#000';
                             }
-                        }).each(function () {
+                        }
+                    })
+                    .text(function () {
+                        if (isUnderVersionControl(processGroupData)) {
+                            var vciState = processGroupData.state;
+                            if (vciState === 'SYNC_FAILURE') {
+                                return '\uf128'
+                            } else if (vciState === 'LOCALLY_MODIFIED_AND_STALE') {
+                                return '\uf06a';
+                            } else if (vciState === 'STALE') {
+                                return '\uf0aa';
+                            } else if (vciState === 'LOCALLY_MODIFIED') {
+                                return '\uf069';
+                            } else {
+                                return '\uf00c';
+                            }
+                        } else {
+                            return '';
+                        }
+                    });
+
+                if (processGroupData.permissions.canRead) {
+                    // version control tooltip
+                    versionControl.each(function () {
                             // get the tip
                             var tip = d3.select('#version-control-tip-' + processGroupData.id);
 
@@ -1092,136 +1196,10 @@
                         .text(function (d) {
                             return d.component.name;
                         });
-
-                    // up to date current
-                    var upToDate = details.select('text.process-group-up-to-date')
-                        .style('visibility', 'visible')
-                        .classed('up-to-date', function (d) {
-                            return d.component.upToDateCount > 0;
-                        })
-                        .classed('zero', function (d) {
-                            return d.component.upToDateCount === 0;
-                        });
-                    var upToDateCount = details.select('text.process-group-up-to-date-count')
-                        .style('visibility', 'visible')
-                        .attr('x', function () {
-                            var updateToDateCountX = parseInt(upToDate.attr('x'), 10);
-                            return updateToDateCountX + Math.round(upToDate.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
-                        })
-                        .text(function (d) {
-                            return d.component.upToDateCount;
-                        });
-
-                    // update locally modified
-                    var locallyModified = details.select('text.process-group-locally-modified')
-                        .style('visibility', 'visible')
-                        .classed('locally-modified', function (d) {
-                            return d.component.locallyModifiedCount > 0;
-                        })
-                        .classed('zero', function (d) {
-                            return d.component.locallyModifiedCount === 0;
-                        })
-                        .attr('x', function () {
-                            var upToDateX = parseInt(upToDateCount.attr('x'), 10);
-                            return upToDateX + Math.round(upToDateCount.node().getComputedTextLength()) + CONTENTS_SPACER;
-                        });
-                    var locallyModifiedCount = details.select('text.process-group-locally-modified-count')
-                        .style('visibility', 'visible')
-                        .attr('x', function () {
-                            var locallyModifiedCountX = parseInt(locallyModified.attr('x'), 10);
-                            return locallyModifiedCountX + Math.round(locallyModified.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
-                        })
-                        .text(function (d) {
-                            return d.component.locallyModifiedCount;
-                        });
-
-                    // update stale
-                    var stale = details.select('text.process-group-stale')
-                        .style('visibility', 'visible')
-                        .classed('stale', function (d) {
-                            return d.component.staleCount > 0;
-                        })
-                        .classed('zero', function (d) {
-                            return d.component.staleCount === 0;
-                        })
-                        .attr('x', function () {
-                            var locallyModifiedX = parseInt(locallyModifiedCount.attr('x'), 10);
-                            return locallyModifiedX + Math.round(locallyModifiedCount.node().getComputedTextLength()) + CONTENTS_SPACER;
-                        });
-                    var staleCount = details.select('text.process-group-stale-count')
-                        .style('visibility', 'visible')
-                        .attr('x', function () {
-                            var staleCountX = parseInt(stale.attr('x'), 10);
-                            return staleCountX + Math.round(stale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
-                        })
-                        .text(function (d) {
-                            return d.component.staleCount;
-                        });
-
-                    // update locally modified and stale
-                    var locallyModifiedAndStale = details.select('text.process-group-locally-modified-and-stale')
-                        .style('visibility', 'visible')
-                        .classed('locally-modified-and-stale', function (d) {
-                            return d.component.locallyModifiedAndStaleCount > 0;
-                        })
-                        .classed('zero', function (d) {
-                            return d.component.locallyModifiedAndStaleCount === 0;
-                        })
-                        .attr('x', function () {
-                            var staleX = parseInt(staleCount.attr('x'), 10);
-                            return staleX + Math.round(staleCount.node().getComputedTextLength()) + CONTENTS_SPACER;
-                        });
-                    var locallyModifiedAndStaleCount = details.select('text.process-group-locally-modified-and-stale-count')
-                        .style('visibility', 'visible')
-                        .attr('x', function () {
-                            var locallyModifiedAndStaleCountX = parseInt(locallyModifiedAndStale.attr('x'), 10);
-                            return locallyModifiedAndStaleCountX + Math.round(locallyModifiedAndStale.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
-                        })
-                        .text(function (d) {
-                            return d.component.locallyModifiedAndStaleCount;
-                        });
-
-                    // update sync failure
-                    var syncFailure = details.select('text.process-group-sync-failure')
-                        .style('visibility', 'visible')
-                        .classed('sync-failure', function (d) {
-                            return d.component.syncFailureCount > 0;
-                        })
-                        .classed('zero', function (d) {
-                            return d.component.syncFailureCount === 0;
-                        })
-                        .attr('x', function () {
-                            var syncFailureX = parseInt(locallyModifiedAndStaleCount.attr('x'), 10);
-                            return syncFailureX + Math.round(locallyModifiedAndStaleCount.node().getComputedTextLength()) + CONTENTS_SPACER - 2;
-                        });
-                    details.select('text.process-group-sync-failure-count')
-                        .style('visibility', 'visible')
-                        .attr('x', function () {
-                            var syncFailureCountX = parseInt(syncFailure.attr('x'), 10);
-                            return syncFailureCountX + Math.round(syncFailure.node().getComputedTextLength()) + CONTENTS_VALUE_SPACER;
-                        })
-                        .text(function (d) {
-                            return d.component.syncFailureCount;
-                        });
                 } else {
-                    // update version control information
-                    processGroup.select('text.version-control').style('visibility', 'hidden');
-
                     // clear the process group comments
                     processGroup.select('path.component-comments').style('visibility', 'hidden');
 
-                    // clear the encapsulate versioned pg counts
-                    details.select('text.process-group-up-to-date').style('visibility', 'hidden');
-                    details.select('text.process-group-up-to-date-count').style('visibility', 'hidden');
-                    details.select('text.process-group-locally-modified').style('visibility', 'hidden');
-                    details.select('text.process-group-locally-modified-count').style('visibility', 'hidden');
-                    details.select('text.process-group-stale').style('visibility', 'hidden');
-                    details.select('text.process-group-stale-count').style('visibility', 'hidden');
-                    details.select('text.process-group-locally-modified-and-stale').style('visibility', 'hidden');
-                    details.select('text.process-group-locally-modified-and-stale-count').style('visibility', 'hidden');
-                    details.select('text.process-group-sync-failure').style('visibility', 'hidden');
-                    details.select('text.process-group-sync-failure-count').style('visibility', 'hidden');
-
                     // clear the process group name
                     processGroup.select('text.process-group-name')
                         .attr({
@@ -1229,6 +1207,9 @@
                             'width': 316
                         })
                         .text(null);
+
+                    // clear tooltips
+                    processGroup.call(removeTooltips);
                 }
 
                 // populate the stats

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
index 77b9af8..8b991a3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
@@ -661,7 +661,10 @@
                     processor.select('text.processor-bundle').text(null);
 
                     // clear the processor comments
-                    processor.select('path.component-comments').style('visibility', false);
+                    processor.select('path.component-comments').style('visibility', 'hidden');
+
+                    // clear tooltips
+                    processor.call(removeTooltips);
                 }
 
                 // populate the stats

http://git-wip-us.apache.org/repos/asf/nifi/blob/1266235c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
index 5de0164..a58d8db 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
@@ -288,18 +288,6 @@
                             'fill': '#ffffff'
                         });
 
-                    // border
-                    details.append('rect')
-                        .attr({
-                            'width': function () {
-                                return remoteProcessGroupData.dimensions.width;
-                            },
-                            'height': 1,
-                            'x': 0,
-                            'y': 103,
-                            'fill': '#c7d2d7'
-                        });
-
                     // -----
                     // stats
                     // -----
@@ -499,7 +487,7 @@
                             'x': function () {
                                 return remoteProcessGroupData.dimensions.width - 17;
                             },
-                            'y': 50
+                            'y': 49
                         })
                         .text('\uf24a');
                 }
@@ -625,13 +613,16 @@
                     details.select('text.remote-process-group-transmission-secure').text(null);
 
                     // clear the comments
-                    details.select('path.component-comments').style('visibility', false);
+                    details.select('path.component-comments').style('visibility', 'hidden');
 
                     // clear the last refresh
                     details.select('text.remote-process-group-last-refresh').text(null);
 
                     // clear the name
                     remoteProcessGroup.select('text.remote-process-group-name').text(null);
+
+                    // clear tooltips
+                    remoteProcessGroup.call(removeTooltips);
                 }
 
                 // populate the stats