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:07 UTC

[19/50] nifi git commit: NIFI-4436: - Clearing bucket/flow/versions when changing the selected registry/bucket. - Using the versioned flow to get the group name when importing. - Adding menu items for viewing local changes. - Showing local changes during

NIFI-4436:
- Clearing bucket/flow/versions when changing the selected registry/bucket.
- Using the versioned flow to get the group name when importing.
- Adding menu items for viewing local changes.
- Showing local changes during revert request.


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

Branch: refs/heads/master
Commit: 3d8b1e4890d5802677e978a6a7588a97bf4177f2
Parents: f6cc5b6
Author: Matt Gilman <ma...@gmail.com>
Authored: Thu Nov 16 14:41:41 2017 -0500
Committer: Bryan Bende <bb...@apache.org>
Committed: Mon Jan 8 12:44:53 2018 -0500

----------------------------------------------------------------------
 .../web/api/dto/ComponentDifferenceDTO.java     |  13 +-
 .../apache/nifi/web/api/dto/DifferenceDTO.java  |  47 ++
 .../nifi/web/api/ProcessGroupResource.java      | 138 ++--
 .../org/apache/nifi/web/api/dto/DtoFactory.java |  66 +-
 .../web/dao/impl/StandardProcessGroupDAO.java   |   4 +-
 .../src/main/webapp/WEB-INF/pages/canvas.jsp    |   2 +
 .../canvas/revert-local-changes-dialog.jsp      |  36 +
 .../canvas/show-local-changes-dialog.jsp        |  35 +
 .../nifi-web-ui/src/main/webapp/css/dialog.css  |  17 +
 .../header/components/nf-ng-group-component.js  |  35 +-
 .../src/main/webapp/js/nf/canvas/nf-actions.js  |  12 +
 .../main/webapp/js/nf/canvas/nf-context-menu.js | 124 ++-
 .../main/webapp/js/nf/canvas/nf-flow-version.js | 800 ++++++++++++++-----
 13 files changed, 1008 insertions(+), 321 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDifferenceDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDifferenceDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDifferenceDTO.java
index 8febce0..e45867d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDifferenceDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ComponentDifferenceDTO.java
@@ -17,12 +17,11 @@
 
 package org.apache.nifi.web.api.dto;
 
-import java.util.List;
-import java.util.Objects;
+import io.swagger.annotations.ApiModelProperty;
 
 import javax.xml.bind.annotation.XmlType;
-
-import io.swagger.annotations.ApiModelProperty;
+import java.util.List;
+import java.util.Objects;
 
 @XmlType(name = "componentDifference")
 public class ComponentDifferenceDTO {
@@ -30,7 +29,7 @@ public class ComponentDifferenceDTO {
     private String componentId;
     private String componentName;
     private String processGroupId;
-    private List<String> differences;
+    private List<DifferenceDTO> differences;
 
     @ApiModelProperty("The type of component")
     public String getComponentType() {
@@ -69,11 +68,11 @@ public class ComponentDifferenceDTO {
     }
 
     @ApiModelProperty("The differences in the component between the two flows")
-    public List<String> getDifferences() {
+    public List<DifferenceDTO> getDifferences() {
         return differences;
     }
 
-    public void setDifferences(List<String> differences) {
+    public void setDifferences(List<DifferenceDTO> differences) {
         this.differences = differences;
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
new file mode 100644
index 0000000..9f06149
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/DifferenceDTO.java
@@ -0,0 +1,47 @@
+/*
+ * 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 = "difference")
+public class DifferenceDTO {
+    private String differenceType;
+    private String difference;
+
+    @ApiModelProperty("The type of difference")
+    public String getDifferenceType() {
+        return differenceType;
+    }
+
+    public void setDifferenceType(String differenceType) {
+        this.differenceType = differenceType;
+    }
+
+    @ApiModelProperty("Description of the difference")
+    public String getDifference() {
+        return difference;
+    }
+
+    public void setDifference(String difference) {
+        this.difference = difference;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.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/ProcessGroupResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
index fe57dd6..de56a4f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
@@ -16,57 +16,12 @@
  */
 package org.apache.nifi.web.api;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.GET;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedHashMap;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBElement;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Unmarshaller;
-import javax.xml.stream.XMLStreamReader;
-
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.Authorization;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.AuthorizableLookup;
 import org.apache.nifi.authorization.AuthorizeAccess;
@@ -156,12 +111,55 @@ import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContextHolder;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
-import io.swagger.annotations.Authorization;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedHashMap;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.stream.XMLStreamReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * RESTful endpoint for managing a Group.
@@ -341,9 +339,7 @@ public class ProcessGroupResource extends ApplicationResource {
         // authorize access
         serviceFacade.authorizeAccess(lookup -> {
             final ProcessGroupAuthorizable groupAuthorizable = lookup.getProcessGroup(groupId);
-            final Authorizable processGroup = groupAuthorizable.getAuthorizable();
-            processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
-            super.authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, false, false, true, false);
+            authorizeProcessGroup(groupAuthorizable, authorizer, lookup, RequestAction.READ, false, false, true, false);
         });
 
         final FlowComparisonEntity entity = serviceFacade.getLocalModifications(groupId);
@@ -1625,6 +1621,11 @@ public class ProcessGroupResource extends ApplicationResource {
             }
         }
 
+        // if the group name isn't specified, ensure the group is being imported from version control
+        if (StringUtils.isBlank(requestProcessGroupEntity.getComponent().getName()) && requestProcessGroupEntity.getComponent().getVersionControlInformation() == null) {
+            throw new IllegalArgumentException("The group name is required when the group is not imported from version control.");
+        }
+
         if (requestProcessGroupEntity.getComponent().getParentGroupId() != null && !groupId.equals(requestProcessGroupEntity.getComponent().getParentGroupId())) {
             throw new IllegalArgumentException(String.format("If specified, the parent process group id %s must be the same as specified in the URI %s",
                     requestProcessGroupEntity.getComponent().getParentGroupId(), groupId));
@@ -1688,15 +1689,22 @@ public class ProcessGroupResource extends ApplicationResource {
                         serviceFacade.verifyComponentTypes(versionedFlowSnapshot.getFlowContents());
                     }
                 },
-                processGroupGroupEntity -> {
+                processGroupEntity -> {
+                    final ProcessGroupDTO processGroup = processGroupEntity.getComponent();
+
                     // set the processor id as appropriate
-                    processGroupGroupEntity.getComponent().setId(generateUuid());
+                    processGroup.setId(generateUuid());
+
+                    // ensure the group name comes from the versioned flow
+                    final VersionedFlowSnapshot flowSnapshot = processGroupEntity.getVersionedFlowSnapshot();
+                    if (flowSnapshot != null && StringUtils.isNotBlank(flowSnapshot.getFlowContents().getName()) && StringUtils.isBlank(processGroup.getName())) {
+                        processGroup.setName(flowSnapshot.getFlowContents().getName());
+                    }
 
                     // create the process group contents
-                    final Revision revision = getRevision(processGroupGroupEntity, processGroupGroupEntity.getComponent().getId());
-                    ProcessGroupEntity entity = serviceFacade.createProcessGroup(revision, groupId, processGroupGroupEntity.getComponent());
+                    final Revision revision = getRevision(processGroupEntity, processGroup.getId());
+                    ProcessGroupEntity entity = serviceFacade.createProcessGroup(revision, groupId, processGroup);
 
-                    final VersionedFlowSnapshot flowSnapshot = requestProcessGroupEntity.getVersionedFlowSnapshot();
                     if (flowSnapshot != null) {
                         final RevisionDTO revisionDto = entity.getRevision();
                         final String newGroupId = entity.getComponent().getId();

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.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/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 1a12dcf..6077268 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
@@ -16,33 +16,6 @@
  */
 package org.apache.nifi.web.api.dto;
 
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.TimeZone;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import javax.ws.rs.WebApplicationException;
-
 import org.apache.commons.lang3.ClassUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.action.Action;
@@ -215,6 +188,32 @@ import org.apache.nifi.web.api.entity.VariableEntity;
 import org.apache.nifi.web.controller.ControllerFacade;
 import org.apache.nifi.web.revision.RevisionManager;
 
+import javax.ws.rs.WebApplicationException;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
 public final class DtoFactory {
 
     @SuppressWarnings("rawtypes")
@@ -2181,15 +2180,20 @@ public final class DtoFactory {
 
 
     public Set<ComponentDifferenceDTO> createComponentDifferenceDtos(final FlowComparison comparison) {
-        final Map<ComponentDifferenceDTO, List<String>> differencesByComponent = new HashMap<>();
+        final Map<ComponentDifferenceDTO, List<DifferenceDTO>> differencesByComponent = new HashMap<>();
 
         for (final FlowDifference difference : comparison.getDifferences()) {
             final ComponentDifferenceDTO componentDiff = createComponentDifference(difference);
-            final List<String> differences = differencesByComponent.computeIfAbsent(componentDiff, key -> new ArrayList<>());
-            differences.add(difference.getDescription());
+            final List<DifferenceDTO> differences = differencesByComponent.computeIfAbsent(componentDiff, key -> new ArrayList<>());
+
+            final DifferenceDTO dto = new DifferenceDTO();
+            dto.setDifferenceType(difference.getDifferenceType().getDescription());
+            dto.setDifference(difference.getDescription());
+
+            differences.add(dto);
         }
 
-        for (final Map.Entry<ComponentDifferenceDTO, List<String>> entry : differencesByComponent.entrySet()) {
+        for (final Map.Entry<ComponentDifferenceDTO, List<DifferenceDTO>> entry : differencesByComponent.entrySet()) {
             entry.getKey().setDifferences(entry.getValue());
         }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
----------------------------------------------------------------------
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/StandardProcessGroupDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
index 78f3e31..e3c4725 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessGroupDAO.java
@@ -61,7 +61,9 @@ public class StandardProcessGroupDAO extends ComponentDAO implements ProcessGrou
 
         // create the process group
         ProcessGroup group = flowController.createProcessGroup(processGroup.getId());
-        group.setName(processGroup.getName());
+        if (processGroup.getName() != null) {
+            group.setName(processGroup.getName());
+        }
         if (processGroup.getPosition() != null) {
             group.setPosition(new Position(processGroup.getPosition().getX(), processGroup.getPosition().getY()));
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/pages/canvas.jsp
----------------------------------------------------------------------
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 258bdad..52fc43c 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
@@ -117,6 +117,8 @@
         <jsp:include page="/WEB-INF/partials/canvas/connections-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/save-flow-version-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/import-flow-version-dialog.jsp"/>
+        <jsp:include page="/WEB-INF/partials/canvas/revert-local-changes-dialog.jsp"/>
+        <jsp:include page="/WEB-INF/partials/canvas/show-local-changes-dialog.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/registry-configuration-dialog.jsp"/>
         <div id="canvas-container" class="unselectable"></div>
         <div id="canvas-tooltips">

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/revert-local-changes-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/revert-local-changes-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/revert-local-changes-dialog.jsp
new file mode 100644
index 0000000..45d4c4c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/revert-local-changes-dialog.jsp
@@ -0,0 +1,36 @@
+<%--
+ 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="revert-local-changes-dialog" layout="column" class="hidden large-dialog">
+    <div class="dialog-content">
+        <div class="setting">
+            <div class="setting-field">
+                Are you sure you want to revert changes? All flow configuration changes detailed below will be reverted to the last version.
+            </div>
+        </div>
+        <span id="revert-local-changes-process-group-id" class="hidden"></span>
+        <div id="revert-local-changes-filter-controls">
+            <div id="revert-local-changes-filter-status" class="filter-status">
+                Displaying&nbsp;<span id="displayed-revert-local-changes-entries"></span>&nbsp;of&nbsp;<span id="total-revert-local-changes-entries"></span>
+            </div>
+            <div id="revert-local-changes-filter-container">
+                <input type="text" id="revert-local-changes-filter" placeholder="Filter"/>
+            </div>
+        </div>
+        <div id="revert-local-changes-table"></div>
+    </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/show-local-changes-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/show-local-changes-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/show-local-changes-dialog.jsp
new file mode 100644
index 0000000..9b122ae
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/show-local-changes-dialog.jsp
@@ -0,0 +1,35 @@
+<%--
+ 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="show-local-changes-dialog" layout="column" class="hidden large-dialog">
+    <div class="dialog-content">
+        <div class="setting">
+            <div class="setting-field">
+                The following changes have been made to the flow since the last version.
+            </div>
+        </div>
+        <div id="show-local-changes-filter-controls">
+            <div id="show-local-changes-filter-status" class="filter-status">
+                Displaying&nbsp;<span id="displayed-show-local-changes-entries"></span>&nbsp;of&nbsp;<span id="total-show-local-changes-entries"></span>
+            </div>
+            <div id="show-local-changes-filter-container">
+                <input type="text" id="show-local-changes-filter" placeholder="Filter"/>
+            </div>
+        </div>
+        <div id="show-local-changes-table"></div>
+    </div>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/dialog.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 29a8c0e..69d4bea 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
@@ -264,6 +264,23 @@ div.progress-label {
 }
 
 /*
+    Local changes
+ */
+
+#revert-local-changes-table, #show-local-changes-table {
+    position: absolute;
+    top: 80px;
+    left: 0px;
+    right: 0px;
+    bottom: 0px;
+    height: 225px;
+}
+
+#revert-local-changes-filter, #show-local-changes-filter {
+    width: 173px;
+}
+
+/*
     Variable Registry
  */
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-group-component.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-group-component.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-group-component.js
index ae9e228..2ce8438 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-group-component.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/header/components/nf-ng-group-component.js
@@ -25,9 +25,10 @@
                 'nf.Graph',
                 'nf.CanvasUtils',
                 'nf.ErrorHandler',
-                'nf.Common'],
-            function ($, nfClient, nfBirdseye, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon) {
-                return (nf.ng.GroupComponent = factory($, nfClient, nfBirdseye, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon));
+                'nf.Common',
+                'nf.Dialog'],
+            function ($, nfClient, nfBirdseye, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon, nfDialog) {
+                return (nf.ng.GroupComponent = factory($, nfClient, nfBirdseye, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon, nfDialog));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ng.GroupComponent =
@@ -37,7 +38,8 @@
                 require('nf.Graph'),
                 require('nf.CanvasUtils'),
                 require('nf.ErrorHandler'),
-                require('nf.Common')));
+                require('nf.Common'),
+                require('nf.Dialog')));
     } else {
         nf.ng.GroupComponent = factory(root.$,
             root.nf.Client,
@@ -45,9 +47,10 @@
             root.nf.Graph,
             root.nf.CanvasUtils,
             root.nf.ErrorHandler,
-            root.nf.Common);
+            root.nf.Common,
+            root.nf.Dialog);
     }
-}(this, function ($, nfClient, nfBirdseye, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon) {
+}(this, function ($, nfClient, nfBirdseye, nfGraph, nfCanvasUtils, nfErrorHandler, nfCommon, nfDialog) {
     'use strict';
 
     return function (serviceProvider) {
@@ -236,12 +239,22 @@
                         // hide the dialog
                         groupComponent.modal.hide();
 
-                        // create the group and resolve the deferred accordingly
-                        createGroup(groupName, pt).done(function (response) {
-                            deferred.resolve(response.component);
-                        }).fail(function () {
+                        // ensure the group name is specified
+                        if (nfCommon.isBlank(groupName)) {
+                            nfDialog.showOkDialog({
+                                headerText: 'Create Process Group',
+                                dialogContent: 'The group name is required.'
+                            });
+
                             deferred.reject();
-                        });
+                        } else {
+                            // create the group and resolve the deferred accordingly
+                            createGroup(groupName, pt).done(function (response) {
+                                deferred.resolve(response.component);
+                            }).fail(function () {
+                                deferred.reject();
+                            });
+                        }
                     };
 
                     groupComponent.modal.update('setButtonModel', [{

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
----------------------------------------------------------------------
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 9dc20b1..725b6a1 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
@@ -1266,6 +1266,18 @@
         },
 
         /**
+         * Shows local changes.
+         */
+        showLocalChanges: function (selection) {
+            if (selection.empty()) {
+                nfFlowVersion.showLocalChanges(nfCanvasUtils.getGroupId());
+            } else if (selection.size() === 1) {
+                var selectionData = selection.datum();
+                nfFlowVersion.showLocalChanges(selectionData.id)
+            }
+        },
+
+        /**
          * Changes the flow version.
          */
         changeFlowVersion: function (selection) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
----------------------------------------------------------------------
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 8656035..616e151 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
@@ -428,6 +428,123 @@
     };
 
     /**
+     * Returns whether the process group support supports commit.
+     *
+     * @param selection
+     * @returns {boolean}
+     */
+    var supportsCommitFlowVersion = function (selection) {
+        // ensure this selection supports flow versioning above
+        if (supportsFlowVersioning(selection) === false) {
+            return false;
+        }
+
+        var versionControlInformation;
+        if (selection.empty()) {
+            // check bread crumbs for version control information in the current group
+            var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
+            if (breadcrumbEntities.length > 0) {
+                var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
+                if (breadcrumbEntity.permissions.canRead) {
+                    versionControlInformation = breadcrumbEntity.breadcrumb.versionControlInformation;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        } else {
+            var processGroupData = selection.datum();
+            versionControlInformation = processGroupData.component.versionControlInformation;
+        }
+
+        if (nfCommon.isUndefinedOrNull(versionControlInformation)) {
+            return false;
+        }
+
+        // check the selection for version control information
+        return versionControlInformation.current === true && versionControlInformation.modified === true;
+    };
+
+    /**
+     * Returns whether the process group supports revert local changes.
+     *
+     * @param selection
+     * @returns {boolean}
+     */
+    var hasLocalChanges = function (selection) {
+        // ensure this selection supports flow versioning above
+        if (supportsFlowVersioning(selection) === false) {
+            return false;
+        }
+
+        var versionControlInformation;
+        if (selection.empty()) {
+            // check bread crumbs for version control information in the current group
+            var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
+            if (breadcrumbEntities.length > 0) {
+                var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
+                if (breadcrumbEntity.permissions.canRead) {
+                    versionControlInformation = breadcrumbEntity.breadcrumb.versionControlInformation;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        } else {
+            var processGroupData = selection.datum();
+            versionControlInformation = processGroupData.component.versionControlInformation;
+        }
+
+        if (nfCommon.isUndefinedOrNull(versionControlInformation)) {
+            return false;
+        }
+
+        // check the selection for version control information
+        return versionControlInformation.modified === true;
+    };
+
+    /**
+     * Returns whether the process group supports changing the flow version.
+     *
+     * @param selection
+     * @returns {boolean}
+     */
+    var supportsChangeFlowVersion = function (selection) {
+        // ensure this selection supports flow versioning above
+        if (supportsFlowVersioning(selection) === false) {
+            return false;
+        }
+
+        var versionControlInformation;
+        if (selection.empty()) {
+            // check bread crumbs for version control information in the current group
+            var breadcrumbEntities = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
+            if (breadcrumbEntities.length > 0) {
+                var breadcrumbEntity = breadcrumbEntities[breadcrumbEntities.length - 1];
+                if (breadcrumbEntity.permissions.canRead) {
+                    versionControlInformation = breadcrumbEntity.breadcrumb.versionControlInformation;
+                } else {
+                    return false;
+                }
+            } else {
+                return false;
+            }
+        } else {
+            var processGroupData = selection.datum();
+            versionControlInformation = processGroupData.component.versionControlInformation;
+        }
+
+        if (nfCommon.isUndefinedOrNull(versionControlInformation)) {
+            return false;
+        }
+
+        // check the selection for version control information
+        return versionControlInformation.modified === false;
+    };
+
+    /**
      * Determines whether the current selection supports stopping flow versioning.
      *
      * @param selection
@@ -640,9 +757,10 @@
         {id: 'version-menu-item', groupMenuItem: {clazz: 'fa', text: 'Version'}, menuItems: [
             {id: 'start-version-control-menu-item', condition: supportsStartFlowVersioning, menuItem: {clazz: 'fa fa-upload', text: 'Start version control', action: 'saveFlowVersion'}},
             {separator: true},
-            {id: 'commit-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa fa-upload', text: 'Commit local changes', action: 'saveFlowVersion'}},
-            {id: 'revert-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa fa-undo', text: 'Revert local changes', action: 'revertLocalChanges'}},
-            {id: 'change-version-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa', text: 'Change version', action: 'changeFlowVersion'}},
+            {id: 'commit-menu-item', condition: supportsCommitFlowVersion, menuItem: {clazz: 'fa fa-upload', text: 'Commit local changes', action: 'saveFlowVersion'}},
+            {id: 'local-changes-menu-item', condition: hasLocalChanges, menuItem: {clazz: 'fa', text: 'Show local changes', action: 'showLocalChanges'}},
+            {id: 'revert-menu-item', condition: hasLocalChanges, menuItem: {clazz: 'fa fa-undo', text: 'Revert local changes', action: 'revertLocalChanges'}},
+            {id: 'change-version-menu-item', condition: supportsChangeFlowVersion, menuItem: {clazz: 'fa', text: 'Change version', action: 'changeFlowVersion'}},
             {separator: true},
             {id: 'stop-version-control-menu-item', condition: supportsStopFlowVersioning, menuItem: {clazz: 'fa', text: 'Stop version control', action: 'stopVersionControl'}}
         ]},

http://git-wip-us.apache.org/repos/asf/nifi/blob/3d8b1e48/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 4eb5286..28e4303 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
@@ -30,10 +30,11 @@
                 'nf.Client',
                 'nf.CanvasUtils',
                 'nf.ProcessGroup',
+                'nf.ProcessGroupConfiguration',
                 'nf.Graph',
                 'nf.Birdseye'],
-            function ($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfGraph, nfBirdseye) {
-                return (nf.FlowVersion = factory($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfGraph, nfBirdseye));
+            function ($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) {
+                return (nf.FlowVersion = factory($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.FlowVerison =
@@ -45,6 +46,7 @@
                 require('nf.Client'),
                 require('nf.CanvasUtils'),
                 require('nf.ProcessGroup'),
+                require('nf.ProcessGroupConfiguration'),
                 require('nf.Graph'),
                 require('nf.Birdseye')));
     } else {
@@ -56,10 +58,11 @@
             root.nf.Client,
             root.nf.CanvasUtils,
             root.nf.ProcessGroup,
+            root.nf.ProcessGroupConfiguration,
             root.nf.Graph,
             root.nf.Birdseye);
     }
-}(this, function ($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfGraph, nfBirdseye) {
+}(this, function ($, nfNgBridge, nfErrorHandler, nfDialog, nfCommon, nfClient, nfCanvasUtils, nfProcessGroup, nfProcessGroupConfiguration, nfGraph, nfBirdseye) {
     'use strict';
 
     var serverTimeOffset = null;
@@ -97,6 +100,55 @@
     };
 
     /**
+     * Reset the revert local changes dialog.
+     */
+    var resetRevertLocalChangesDialog = function () {
+        $('#revert-local-changes-process-group-id').text('');
+
+        clearLocalChangesGrid($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
+    };
+
+    /**
+     * Reset the show local changes dialog.
+     */
+    var resetShowLocalChangesDialog = function () {
+        clearLocalChangesGrid($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
+    };
+
+    /**
+     * Clears the local changes grid.
+     */
+    var clearLocalChangesGrid = function (localChangesTable, filterInput, displayedLabel, totalLabel) {
+        var localChangesGrid = localChangesTable.data('gridInstance');
+        if (nfCommon.isDefinedAndNotNull(localChangesGrid)) {
+            localChangesGrid.setSelectedRows([]);
+            localChangesGrid.resetActiveCell();
+
+            var localChangesData = localChangesGrid.getData();
+            localChangesData.setItems([]);
+        }
+
+        filterInput.val('');
+
+        displayedLabel.text('0');
+        totalLabel.text('0');
+    }
+
+    /**
+     * Clears the version grid
+     */
+    var clearFlowVersionsGrid = function () {
+        var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
+        if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) {
+            importFlowVersionGrid.setSelectedRows([]);
+            importFlowVersionGrid.resetActiveCell();
+
+            var importFlowVersionData = importFlowVersionGrid.getData();
+            importFlowVersionData.setItems([]);
+        }
+    };
+
+    /**
      * Reset the import flow version dialog.
      */
     var resetImportFlowVersionDialog = function () {
@@ -110,14 +162,7 @@
         $('#import-flow-version-bucket').text('').hide();
         $('#import-flow-version-name').text('').hide();
 
-        var importFlowVersionGrid = $('#import-flow-version-table').data('gridInstance');
-        if (nfCommon.isDefinedAndNotNull(importFlowVersionGrid)) {
-            importFlowVersionGrid.setSelectedRows([]);
-            importFlowVersionGrid.resetActiveCell();
-
-            var importFlowVersionData = importFlowVersionGrid.getData();
-            importFlowVersionData.setItems([]);
-        }
+        clearFlowVersionsGrid();
 
         $('#import-flow-version-process-group-id').removeData('versionControlInformation').removeData('revision').text('');
 
@@ -131,9 +176,10 @@
      * @param dialog
      * @param registryCombo
      * @param bucketCombo
+     * @param flowCombo
      * @returns {deferred}
      */
-    var loadRegistries = function (dialog, registryCombo, bucketCombo, selectBucket) {
+    var loadRegistries = function (dialog, registryCombo, bucketCombo, flowCombo, selectBucket) {
         return $.ajax({
             type: 'GET',
             url: '../nifi-api/flow/registries',
@@ -167,7 +213,7 @@
             registryCombo.combo({
                 options: registries,
                 select: function (selectedOption) {
-                    selectRegistry(dialog, selectedOption, bucketCombo, selectBucket)
+                    selectRegistry(dialog, selectedOption, bucketCombo, flowCombo, selectBucket)
                 }
             });
         }).fail(nfErrorHandler.handleAjaxError);
@@ -178,6 +224,7 @@
      *
      * @param registryIdentifier
      * @param bucketCombo
+     * @param selectBucket
      * @returns {*}
      */
     var loadBuckets = function (registryIdentifier, bucketCombo, selectBucket) {
@@ -224,9 +271,10 @@
      * @param dialog
      * @param selectedOption
      * @param bucketCombo
+     * @param flowCombo
      * @param selectBucket
      */
-    var selectRegistry = function (dialog, selectedOption, bucketCombo, selectBucket) {
+    var selectRegistry = function (dialog, selectedOption, bucketCombo, flowCombo, selectBucket) {
         var showNoBucketsAvailable = function () {
             bucketCombo.combo('destroy').combo({
                 options: [{
@@ -243,6 +291,28 @@
         if (selectedOption.disabled === true) {
             showNoBucketsAvailable();
         } else {
+            bucketCombo.combo('destroy').combo({
+                options: [{
+                    text: 'Loading buckets...',
+                    value: null,
+                    optionClass: 'unset',
+                    disabled: true
+                }]
+            });
+
+            if (nfCommon.isDefinedAndNotNull(flowCombo)) {
+                flowCombo.combo('destroy').combo({
+                    options: [{
+                        text: 'Loading flows...',
+                        value: null,
+                        optionClass: 'unset',
+                        disabled: true
+                    }]
+                });
+
+                clearFlowVersionsGrid();
+            }
+
             loadBuckets(selectedOption.value, bucketCombo, selectBucket).fail(function () {
                 showNoBucketsAvailable();
             });
@@ -344,7 +414,7 @@
             return nfCommon.formatDateTime(date);
         };
 
-        // define the column model for the controller services table
+        // define the column model for flow versions
         var importFlowVersionColumns = [
             {
                 id: 'version',
@@ -427,6 +497,185 @@
     };
 
     /**
+     * Initializes the specified local changes table.
+     *
+     * @param localChangesTable
+     * @param filterInput
+     * @param displayedLabel
+     * @param totalLabel
+     */
+    var initLocalChangesTable = function (localChangesTable, filterInput, displayedLabel, totalLabel) {
+
+        var getFilterText = function () {
+            return filterInput.val();
+        };
+
+        var applyFilter = function () {
+            // get the dataview
+            var localChangesGrid = localChangesTable.data('gridInstance');
+
+            // ensure the grid has been initialized
+            if (nfCommon.isDefinedAndNotNull(localChangesGrid)) {
+                var localChangesData = localChangesGrid.getData();
+
+                // update the search criteria
+                localChangesData.setFilterArgs({
+                    searchString: getFilterText()
+                });
+                localChangesData.refresh();
+            }
+        };
+
+        var filter = function (item, args) {
+            if (args.searchString === '') {
+                return true;
+            }
+
+            try {
+                // perform the row filtering
+                var filterExp = new RegExp(args.searchString, 'i');
+            } catch (e) {
+                // invalid regex
+                return false;
+            }
+
+            // determine if the item matches the filter
+            var matchesId = item['componentId'].search(filterExp) >= 0;
+            var matchesComponent = item['componentName'].search(filterExp) >= 0;
+            var matchesDifferenceType = item['differenceType'].search(filterExp) >= 0;
+            var matchesDifference = item['difference'].search(filterExp) >= 0;
+
+            return matchesId || matchesComponent || matchesDifferenceType || matchesDifference;
+        };
+
+        // initialize the component state filter
+        filterInput.on('keyup', function () {
+            applyFilter();
+        });
+
+        var valueFormatter = function (row, cell, value, columnDef, dataContext) {
+            return nfCommon.escapeHtml(value);
+        };
+
+        var actionsFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            if (dataContext.differenceType !== 'Component Removed' && nfCommon.isDefinedAndNotNull(dataContext.processGroupId)) {
+                markup += '<div class="pointer go-to-component fa fa-long-arrow-right" title="Go To" style="margin-top: 2px" ></div>';
+            }
+
+            return markup;
+        };
+
+        // define the column model for local changes
+        var localChangesColumns = [
+            {
+                id: 'component',
+                name: 'Component Name',
+                field: 'componentName',
+                formatter: valueFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'differenceType',
+                name: 'Type',
+                field: 'differenceType',
+                formatter: valueFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'difference',
+                name: 'Difference',
+                field: 'difference',
+                formatter: valueFormatter,
+                sortable: true,
+                resizable: true,
+                minWidth: 300
+            },
+            {
+                id: 'actions',
+                name: '&nbsp;',
+                formatter: actionsFormatter,
+                sortable: false,
+                resizable: false,
+                width: 25
+            }
+        ];
+
+        // initialize the dataview
+        var localChangesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        localChangesData.setFilterArgs({
+            searchString: '',
+            property: 'component'
+        });
+        localChangesData.setFilter(filter);
+
+        // initialize the sort
+        sort({
+            columnId: 'version',
+            sortAsc: false
+        }, localChangesData);
+
+        // initialize the grid
+        var localChangesGrid = new Slick.Grid(localChangesTable, localChangesData, localChangesColumns, gridOptions);
+        localChangesGrid.setSelectionModel(new Slick.RowSelectionModel());
+        localChangesGrid.registerPlugin(new Slick.AutoTooltips());
+        localChangesGrid.setSortColumn('version', false);
+        localChangesGrid.onSort.subscribe(function (e, args) {
+            sort({
+                columnId: args.sortCol.id,
+                sortAsc: args.sortAsc
+            }, localChangesData);
+        });
+
+        // configure a click listener
+        localChangesGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+
+            // get the node at this row
+            var componentDifference = localChangesData.getItem(args.row);
+
+            // determine the desired action
+            if (localChangesGrid.getColumns()[args.cell].id === 'actions') {
+                if (target.hasClass('go-to-component')) {
+                    if (componentDifference.componentType === 'Controller Service') {
+                        nfProcessGroupConfiguration.showConfiguration(componentDifference.processGroupId).done(function () {
+                            nfProcessGroupConfiguration.selectControllerService(componentDifference.componentId);
+                        });
+                    } else {
+                        nfCanvasUtils.showComponent(componentDifference.processGroupId, componentDifference.componentId);
+                    }
+                }
+            }
+        });
+
+        // wire up the dataview to the grid
+        localChangesData.onRowCountChanged.subscribe(function (e, args) {
+            localChangesGrid.updateRowCount();
+            localChangesGrid.render();
+
+            // update the total number of displayed items
+            displayedLabel.text(nfCommon.formatInteger(args.current));
+        });
+        localChangesData.onRowsChanged.subscribe(function (e, args) {
+            localChangesGrid.invalidateRows(args.rows);
+            localChangesGrid.render();
+        });
+        localChangesData.syncGridSelection(localChangesGrid, true);
+
+        // hold onto an instance of the grid
+        localChangesTable.data('gridInstance', localChangesGrid);
+
+        // initialize the number of display items
+        displayedLabel.text('0');
+        totalLabel.text('0');
+    };
+
+    /**
      * Shows the import flow version dialog.
      */
     var showImportFlowVersionDialog = function () {
@@ -450,7 +699,7 @@
                 disabled: true
             }]
         }).show();
-        $('#import-flow-version-name-combo').combo('destroy').combo({
+        var flowCombo = $('#import-flow-version-name-combo').combo('destroy').combo({
             options: [{
                 text: 'Loading flows...',
                 value: null,
@@ -459,7 +708,7 @@
             }]
         }).show();
 
-        loadRegistries($('#import-flow-version-dialog'), registryCombo, bucketCombo, selectBucketImportVersion).done(function () {
+        loadRegistries($('#import-flow-version-dialog'), registryCombo, bucketCombo, flowCombo, selectBucketImportVersion).done(function () {
             // show the import dialog
             $('#import-flow-version-dialog').modal('setHeaderText', 'Import Version').modal('setButtonModel', [{
                 buttonText: 'Import',
@@ -619,6 +868,19 @@
      * @param selectedBucket
      */
     var selectBucketImportVersion = function (selectedBucket) {
+        // mark the flows as loading
+        $('#import-flow-version-name-combo').combo('destroy').combo({
+            options: [{
+                text: 'Loading flows...',
+                value: null,
+                optionClass: 'unset',
+                disabled: true
+            }]
+        });
+
+        // clear the flow versions grid
+        clearFlowVersionsGrid();
+
         var selectedRegistry = $('#import-flow-version-registry-combo').combo('getSelectedOption');
 
         // load the flows for the currently selected registry and bucket
@@ -646,7 +908,6 @@
                 }
             }),
             'component': {
-                'name': selectedFlow.text, // TODO - name from versioned PG?
                 'position': {
                     'x': pt.x,
                     'y': pt.y
@@ -966,6 +1227,240 @@
         progressBar.append(label);
     };
 
+    /**
+     * Shows local changes for the specified process group.
+     *
+     * @param processGroupId
+     * @param localChangesTable
+     * @param totalLabel
+     */
+    var loadLocalChanges = function (processGroupId, localChangesTable, totalLabel) {
+        var localChangesGrid = localChangesTable.data('gridInstance');
+        var localChangesData = localChangesGrid.getData();
+
+        // begin the update
+        localChangesData.beginUpdate();
+
+        // remove the current versions
+        localChangesGrid.setSelectedRows([]);
+        localChangesGrid.resetActiveCell();
+        localChangesData.setItems([]);
+
+        return $.ajax({
+            type: 'GET',
+            url: '../nifi-api/process-groups/' + encodeURIComponent(processGroupId) + '/local-modifications',
+            dataType: 'json'
+        }).done(function (response) {
+            if (nfCommon.isDefinedAndNotNull(response.componentDifferences) && response.componentDifferences.length > 0) {
+                var totalDifferences = 0;
+                $.each(response.componentDifferences, function (_, componentDifference) {
+                    $.each(componentDifference.differences, function (_, difference) {
+                        localChangesData.addItem({
+                            id: totalDifferences++,
+                            componentId: componentDifference.componentId,
+                            componentName: componentDifference.componentName,
+                            componentType: componentDifference.componentType,
+                            processGroupId: componentDifference.processGroupId,
+                            differenceType: difference.differenceType,
+                            difference: difference.difference
+                        });
+                    });
+                });
+
+                // end the update
+                localChangesData.endUpdate();
+
+                // resort
+                localChangesData.reSort();
+                localChangesGrid.invalidate();
+
+                // update the total displayed
+                totalLabel.text(nfCommon.formatInteger(totalDifferences));
+            } else {
+                nfDialog.showOkDialog({
+                    headerText: 'Local Changes',
+                    dialogContent: 'This Process Group does not have any local changes.'
+                });
+            }
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
+    /**
+     * Revert local changes for the specified process group.
+     *
+     * @param processGroupId
+     */
+    var revertLocalChanges = function (processGroupId) {
+        getVersionControlInformation(processGroupId).done(function (response) {
+            if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
+                var revertTimer = null;
+                var revertRequest = null;
+                var cancelled = false;
+
+                // update the button model of the revert status dialog
+                $('#change-version-status-dialog').modal('setButtonModel', [{
+                    buttonText: 'Stop',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            cancelled = true;
+
+                            $('#change-version-status-dialog').modal('setButtonModel', []);
+
+                            // we are waiting for the next poll attempt
+                            if (revertTimer !== null) {
+                                // cancel it
+                                clearTimeout(revertTimer);
+
+                                // cancel the revert request
+                                completeRevertRequest();
+                            }
+                        }
+                    }
+                }]);
+
+                // hide the import dialog immediately
+                $('#import-flow-version-dialog').modal('hide');
+
+                var submitRevertRequest = function () {
+                    var revertFlowVersionRequest = {
+                        'processGroupRevision': nfClient.getRevision({
+                            'revision': {
+                                'version': response.processGroupRevision.version
+                            }
+                        }),
+                        'versionControlInformation': response.versionControlInformation
+                    };
+
+                    return $.ajax({
+                        type: 'POST',
+                        data: JSON.stringify(revertFlowVersionRequest),
+                        url: '../nifi-api/versions/revert-requests/process-groups/' + encodeURIComponent(processGroupId),
+                        dataType: 'json',
+                        contentType: 'application/json'
+                    }).done(function () {
+                        // initialize the progress bar value
+                        updateProgress(0);
+
+                        // show the progress dialog
+                        $('#change-version-status-dialog').modal('show');
+                    }).fail(nfErrorHandler.handleAjaxError);
+                };
+
+                var pollRevertRequest = function () {
+                    getRevertRequest().done(processRevertResponse);
+                };
+
+                var getRevertRequest = function () {
+                    return $.ajax({
+                        type: 'GET',
+                        url: revertRequest.uri,
+                        dataType: 'json'
+                    }).fail(completeRevertRequest).fail(nfErrorHandler.handleAjaxError);
+                };
+
+                var completeRevertRequest = function () {
+                    if (cancelled === true) {
+                        // update the message to indicate successful completion
+                        $('#change-version-status-message').text('The revert request has been cancelled.');
+
+                        // update the button model
+                        $('#change-version-status-dialog').modal('setButtonModel', [{
+                            buttonText: 'Close',
+                            color: {
+                                base: '#728E9B',
+                                hover: '#004849',
+                                text: '#ffffff'
+                            },
+                            handler: {
+                                click: function () {
+                                    $(this).modal('hide');
+                                }
+                            }
+                        }]);
+                    }
+
+                    if (nfCommon.isDefinedAndNotNull(revertRequest)) {
+                        $.ajax({
+                            type: 'DELETE',
+                            url: revertRequest.uri,
+                            dataType: 'json'
+                        }).done(function (response) {
+                            revertRequest = response.request;
+
+                            // update the component that was changing
+                            updateProcessGroup(processGroupId);
+
+                            if (nfCommon.isDefinedAndNotNull(revertRequest.failureReason)) {
+                                // hide the progress dialog
+                                $('#change-version-status-dialog').modal('hide');
+
+                                nfDialog.showOkDialog({
+                                    headerText: 'Revert Local Changes',
+                                    dialogContent: nfCommon.escapeHtml(changeRequest.failureReason)
+                                });
+                            } else {
+                                // update the percent complete
+                                updateProgress(revertRequest.percentCompleted);
+
+                                // update the message to indicate successful completion
+                                $('#change-version-status-message').text('This Process Group version has changed.');
+
+                                // update the button model
+                                $('#change-version-status-dialog').modal('setButtonModel', [{
+                                    buttonText: 'Close',
+                                    color: {
+                                        base: '#728E9B',
+                                        hover: '#004849',
+                                        text: '#ffffff'
+                                    },
+                                    handler: {
+                                        click: function () {
+                                            $(this).modal('hide');
+                                        }
+                                    }
+                                }]);
+                            }
+                        });
+                    }
+                };
+
+                var processRevertResponse = function (response) {
+                    revertRequest = response.request;
+
+                    if (revertRequest.complete === true || cancelled === true) {
+                        completeRevertRequest();
+                    } else {
+                        // update the percent complete
+                        updateProgress(revertRequest.percentCompleted);
+
+                        // update the status of the revert request
+                        $('#change-version-status-message').text(revertRequest.state);
+
+                        revertTimer = setTimeout(function () {
+                            // clear the timer since we've been invoked
+                            revertTimer = null;
+
+                            // poll revert request
+                            pollRevertRequest();
+                        }, 2000);
+                    }
+                };
+
+                submitRevertRequest().done(processRevertResponse);
+            } else {
+                nfDialog.showOkDialog({
+                    headerText: 'Revert Changes',
+                    dialogContent: 'This Process Group is not currently under version control.'
+                });
+            }
+        }).fail(nfErrorHandler.handleAjaxError);
+    };
+
     return {
         init: function (timeOffset) {
             serverTimeOffset = timeOffset;
@@ -998,13 +1493,11 @@
                     handler: {
                         click: function () {
                             var processGroupId = $('#save-flow-version-process-group-id').text();
-                            
                             saveFlowVersion().done(function (response) {
                                 updateVersionControlInformation(processGroupId, response.versionControlInformation);
-
-                                // close the dialog
-                                $('#save-flow-version-dialog').modal('hide');
                             });
+
+                            $(this).modal('hide');
                         }
                     }
                 }, {
@@ -1049,6 +1542,69 @@
                 }
             });
 
+            // init the revert local changes dialog
+            $('#revert-local-changes-dialog').modal({
+                scrollableContentStyle: 'scrollable',
+                headerText: 'Revert Local Changes',
+                buttons: [{
+                    buttonText: 'Revert',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            var processGroupId = $('#revert-local-changes-process-group-id').text();
+                            revertLocalChanges(processGroupId);
+
+                            $(this).modal('hide');
+                        }
+                    }
+                }, {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+                handler: {
+                    close: function () {
+                        resetRevertLocalChangesDialog();
+                    }
+                }
+            });
+
+            // init the show local changes dialog
+            $('#show-local-changes-dialog').modal({
+                scrollableContentStyle: 'scrollable',
+                headerText: 'Local Changes',
+                buttons: [{
+                    buttonText: 'Close',
+                    color: {
+                        base: '#728E9B',
+                        hover: '#004849',
+                        text: '#ffffff'
+                    },
+                    handler: {
+                        click: function () {
+                            $(this).modal('hide');
+                        }
+                    }
+                }],
+                handler: {
+                    close: function () {
+                        resetShowLocalChangesDialog();
+                    }
+                }
+            });
+
             // handle the click for the process group import
             $('#import-process-group-link').on('click', function() {
                 showImportFlowVersionDialog();
@@ -1056,6 +1612,8 @@
 
             // initialize the import flow version table
             initImportFlowVersionTable();
+            initLocalChangesTable($('#revert-local-changes-table'), $('#revert-local-changes-filter'), $('#displayed-revert-local-changes-entries'), $('#total-revert-local-changes-entries'));
+            initLocalChangesTable($('#show-local-changes-table'), $('#show-local-changes-filter'), $('#displayed-show-local-changes-entries'), $('#total-show-local-changes-entries'));
         },
 
         /**
@@ -1118,7 +1676,7 @@
                         // reposition the version label
                         $('#save-flow-version-label').css('margin-top', '0');
 
-                        loadRegistries($('#save-flow-version-dialog'), registryCombo, bucketCombo, selectBucketSaveFlowVersion).done(function () {
+                        loadRegistries($('#save-flow-version-dialog'), registryCombo, bucketCombo, null, selectBucketSaveFlowVersion).done(function () {
                             deferred.resolve();
                         }).fail(function () {
                             deferred.reject();
@@ -1144,184 +1702,20 @@
          * @param processGroupId
          */
         revertLocalChanges: function (processGroupId) {
-            // TODO update to show user the ramifications of reverting for confirmation
-
-            // prompt the user before reverting
-            nfDialog.showYesNoDialog({
-                headerText: 'Revert Changes',
-                dialogContent: 'Are you sure you want to revert changes? All flow configuration changes will be reverted to the last version.',
-                noText: 'Cancel',
-                yesText: 'Revert',
-                yesHandler: function () {
-                    getVersionControlInformation(processGroupId).done(function (response) {
-                        if (nfCommon.isDefinedAndNotNull(response.versionControlInformation)) {
-                            var revertTimer = null;
-                            var revertRequest = null;
-                            var cancelled = false;
-
-                            // update the button model of the revert status dialog
-                            $('#change-version-status-dialog').modal('setButtonModel', [{
-                                buttonText: 'Stop',
-                                color: {
-                                    base: '#728E9B',
-                                    hover: '#004849',
-                                    text: '#ffffff'
-                                },
-                                handler: {
-                                    click: function () {
-                                        cancelled = true;
-
-                                        $('#change-version-status-dialog').modal('setButtonModel', []);
-
-                                        // we are waiting for the next poll attempt
-                                        if (revertTimer !== null) {
-                                            // cancel it
-                                            clearTimeout(revertTimer);
-
-                                            // cancel the revert request
-                                            completeRevertRequest();
-                                        }
-                                    }
-                                }
-                            }]);
-
-                            // hide the import dialog immediately
-                            $('#import-flow-version-dialog').modal('hide');
-
-                            var submitRevertRequest = function () {
-                                var revertFlowVersionRequest = {
-                                    'processGroupRevision': nfClient.getRevision({
-                                        'revision': {
-                                            'version': response.processGroupRevision.version
-                                        }
-                                    }),
-                                    'versionControlInformation': response.versionControlInformation
-                                };
-
-                                return $.ajax({
-                                    type: 'POST',
-                                    data: JSON.stringify(revertFlowVersionRequest),
-                                    url: '../nifi-api/versions/revert-requests/process-groups/' + encodeURIComponent(processGroupId),
-                                    dataType: 'json',
-                                    contentType: 'application/json'
-                                }).done(function () {
-                                    // initialize the progress bar value
-                                    updateProgress(0);
-
-                                    // show the progress dialog
-                                    $('#change-version-status-dialog').modal('show');
-                                }).fail(nfErrorHandler.handleAjaxError);
-                            };
-
-                            var pollRevertRequest = function () {
-                                getRevertRequest().done(processRevertResponse);
-                            };
-
-                            var getRevertRequest = function () {
-                                return $.ajax({
-                                    type: 'GET',
-                                    url: revertRequest.uri,
-                                    dataType: 'json'
-                                }).fail(completeRevertRequest).fail(nfErrorHandler.handleAjaxError);
-                            };
-
-                            var completeRevertRequest = function () {
-                                if (cancelled === true) {
-                                    // update the message to indicate successful completion
-                                    $('#change-version-status-message').text('The revert request has been cancelled.');
-
-                                    // update the button model
-                                    $('#change-version-status-dialog').modal('setButtonModel', [{
-                                        buttonText: 'Close',
-                                        color: {
-                                            base: '#728E9B',
-                                            hover: '#004849',
-                                            text: '#ffffff'
-                                        },
-                                        handler: {
-                                            click: function () {
-                                                $(this).modal('hide');
-                                            }
-                                        }
-                                    }]);
-                                }
-
-                                if (nfCommon.isDefinedAndNotNull(revertRequest)) {
-                                    $.ajax({
-                                        type: 'DELETE',
-                                        url: revertRequest.uri,
-                                        dataType: 'json'
-                                    }).done(function (response) {
-                                        revertRequest = response.request;
-
-                                        // update the component that was changing
-                                        updateProcessGroup(processGroupId);
-
-                                        if (nfCommon.isDefinedAndNotNull(revertRequest.failureReason)) {
-                                            // hide the progress dialog
-                                            $('#change-version-status-dialog').modal('hide');
-
-                                            nfDialog.showOkDialog({
-                                                headerText: 'Revert Local Changes',
-                                                dialogContent: nfCommon.escapeHtml(changeRequest.failureReason)
-                                            });
-                                        } else {
-                                            // update the percent complete
-                                            updateProgress(revertRequest.percentCompleted);
-
-                                            // update the message to indicate successful completion
-                                            $('#change-version-status-message').text('This Process Group version has changed.');
-
-                                            // update the button model
-                                            $('#change-version-status-dialog').modal('setButtonModel', [{
-                                                buttonText: 'Close',
-                                                color: {
-                                                    base: '#728E9B',
-                                                    hover: '#004849',
-                                                    text: '#ffffff'
-                                                },
-                                                handler: {
-                                                    click: function () {
-                                                        $(this).modal('hide');
-                                                    }
-                                                }
-                                            }]);
-                                        }
-                                    });
-                                }
-                            };
-
-                            var processRevertResponse = function (response) {
-                                revertRequest = response.request;
-
-                                if (revertRequest.complete === true || cancelled === true) {
-                                    completeRevertRequest();
-                                } else {
-                                    // update the percent complete
-                                    updateProgress(revertRequest.percentCompleted);
-
-                                    // update the status of the revert request
-                                    $('#change-version-status-message').text(revertRequest.state);
-
-                                    revertTimer = setTimeout(function () {
-                                        // clear the timer since we've been invoked
-                                        revertTimer = null;
-
-                                        // poll revert request
-                                        pollRevertRequest();
-                                    }, 2000);
-                                }
-                            };
+            loadLocalChanges(processGroupId, $('#revert-local-changes-table'), $('#total-revert-local-changes-entries')).done(function () {
+                $('#revert-local-changes-process-group-id').text(processGroupId);
+                $('#revert-local-changes-dialog').modal('show');
+            });
+        },
 
-                            submitRevertRequest().done(processRevertResponse);
-                        } else {
-                            nfDialog.showOkDialog({
-                                headerText: 'Revert Changes',
-                                dialogContent: 'This Process Group is not currently under version control.'
-                            });
-                        }
-                    }).fail(nfErrorHandler.handleAjaxError);
-                }
+        /**
+         * Shows local changes for the specified process group.
+         *
+         * @param processGroupId
+         */
+        showLocalChanges: function (processGroupId) {
+            loadLocalChanges(processGroupId, $('#show-local-changes-table'), $('#total-show-local-changes-entries')).done(function () {
+                $('#show-local-changes-dialog').modal('show');
             });
         },
 
@@ -1407,8 +1801,8 @@
         stopVersionControl: function (processGroupId) {
             // prompt the user before disconnecting
             nfDialog.showYesNoDialog({
-                headerText: 'Disconnect',
-                dialogContent: 'Are you sure you want to disconnect?',
+                headerText: 'Stop Version Control',
+                dialogContent: 'Are you sure you want to stop version control?',
                 noText: 'Cancel',
                 yesText: 'Disconnect',
                 yesHandler: function () {
@@ -1434,7 +1828,7 @@
 
                                 nfDialog.showOkDialog({
                                     headerText: 'Disconnect',
-                                    dialogContent: 'This Process Group has been disconnected.'
+                                    dialogContent: 'This Process Group is no longer under version control.'
                                 });
                             }).fail(nfErrorHandler.handleAjaxError);
                         } else {