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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1bec905  NIFI-9288: - Allowing the user to submit a verification 
request for Processors, Controller Services, and Reporting Tasks. - Tracking 
progress of verification requests. - Showing the verification results.
1bec905 is described below

commit 1bec905890b73972d93fac6c3c0d087bb79a69a3
Author: Matt Gilman <[email protected]>
AuthorDate: Fri Oct 8 16:50:14 2021 -0400

    NIFI-9288:
    - Allowing the user to submit a verification request for Processors, 
Controller Services, and Reporting Tasks.
    - Tracking progress of verification requests.
    - Showing the verification results.
    
    NIFI-9288:
    - Fixing class name which prevented styles from being applied.
    
    NIFI-9288:
    - Ensuring that previously entered referenced attribute values take 
precedence.
    
    NIFI-9288:
    - Positioning the property listing and verification results based on 
percentages instead of fixed values.
    - Removing the additional dialog height.
    
    NIFI-9288:
    - Allowing attribute value entry to be skipped when appropriate.
    
    NIFI-9288:
    - Working around an issue caused by css minification.
    
    NIFI-9288:
    - Adding some padding to the verifying progress dialog.
    
    This closes #5461
    
    Signed-off-by: Scott Aslan <[email protected]>
---
 .../nifi-framework/nifi-web/nifi-web-ui/pom.xml    |   1 +
 .../src/main/resources/filters/canvas.properties   |   1 +
 .../src/main/webapp/WEB-INF/pages/canvas.jsp       |   2 +
 .../canvas/controller-service-configuration.jsp    |   6 +-
 .../partials/canvas/processor-configuration.jsp    |   4 +
 .../canvas/referenced-attributes-configuration.jsp |  47 ++
 .../canvas/reporting-task-configuration.jsp        |   6 +-
 .../canvas/verification-request-status-dialog.jsp  |  29 +
 .../nifi-web-ui/src/main/webapp/css/dialog.css     |  92 +++
 .../main/webapp/css/processor-configuration.css    |   2 +-
 .../jquery/propertytable/jquery.propertytable.css  |  12 +-
 .../jquery/propertytable/jquery.propertytable.js   |  62 +-
 .../webapp/js/nf/canvas/nf-canvas-bootstrap.js     |  10 +-
 .../webapp/js/nf/canvas/nf-controller-service.js   |  48 +-
 .../js/nf/canvas/nf-processor-configuration.js     |  57 +-
 .../main/webapp/js/nf/canvas/nf-reporting-task.js  |  55 +-
 .../src/main/webapp/js/nf/canvas/nf-verify.js      | 847 +++++++++++++++++++++
 17 files changed, 1231 insertions(+), 50 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
index e927043..446ad6e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/pom.xml
@@ -501,6 +501,7 @@
                                                 
<include>${staging.dir}/js/nf/canvas/nf-canvas-utils.js</include>
                                                 
<include>${staging.dir}/js/nf/canvas/nf-go-to.js</include>
                                                 
<include>${staging.dir}/js/nf/canvas/nf-snippet.js</include>
+                                                
<include>${staging.dir}/js/nf/canvas/nf-verify.js</include>
                                                 
<include>${staging.dir}/js/nf/canvas/nf-connection.js</include>
                                                 
<include>${staging.dir}/js/nf/canvas/nf-funnel.js</include>
                                                 
<include>${staging.dir}/js/nf/canvas/nf-label.js</include>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
index b509eae..ae9b533 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/resources/filters/canvas.properties
@@ -31,6 +31,7 @@ nf.canvas.script.tags=<script type="text/javascript" 
src="js/nf/nf-ng-bridge.js?
 <script type="text/javascript" 
src="js/nf/canvas/nf-canvas-utils.js?${project.version}"></script>\n\
 <script type="text/javascript" 
src="js/nf/canvas/nf-go-to.js?${project.version}"></script>\n\
 <script type="text/javascript" 
src="js/nf/canvas/nf-snippet.js?${project.version}"></script>\n\
+<script type="text/javascript" 
src="js/nf/canvas/nf-verify.js?${project.version}"></script>\n\
 <script type="text/javascript" 
src="js/nf/canvas/nf-connection.js?${project.version}"></script>\n\
 <script type="text/javascript" 
src="js/nf/canvas/nf-funnel.js?${project.version}"></script>\n\
 <script type="text/javascript" 
src="js/nf/canvas/nf-label.js?${project.version}"></script>\n\
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 f75ef5b..3ad16c0 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
@@ -158,6 +158,8 @@
         <jsp:include 
page="/WEB-INF/partials/canvas/drop-request-status-dialog.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/flowfile-details-dialog.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/listing-request-status-dialog.jsp"/>
+        <jsp:include 
page="/WEB-INF/partials/canvas/verification-request-status-dialog.jsp"/>
+        <jsp:include 
page="/WEB-INF/partials/canvas/referenced-attributes-configuration.jsp"/>
         <jsp:include page="/WEB-INF/partials/canvas/queue-listing.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/component-state-dialog.jsp"/>
         <jsp:include 
page="/WEB-INF/partials/canvas/component-version-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/controller-service-configuration.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/controller-service-configuration.jsp
index 951631e..b645425 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/controller-service-configuration.jsp
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/controller-service-configuration.jsp
@@ -66,6 +66,10 @@
             </div>
             <div id="controller-service-properties-tab-content" 
class="configuration-tab">
                 <div id="controller-service-properties"></div>
+                <div id="controller-service-properties-verification-results" 
class="verification-results">
+                    <div class="verification-results-header">Verification 
Results</div>
+                    <div 
id="controller-service-properties-verification-results-listing" 
class="verification-results-listing"></div>
+                </div>
             </div>
             <div id="controller-service-comments-tab-content" 
class="configuration-tab">
                 <textarea cols="30" rows="4" id="controller-service-comments" 
name="controller-service-comments" class="controller-service-editable 
setting-input"></textarea>
@@ -79,4 +83,4 @@
         </div>
     </div>
 </div>
-<div id="new-controller-service-property-container"></div>
\ No newline at end of file
+<div id="new-controller-service-property-container"></div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/processor-configuration.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/processor-configuration.jsp
index 9f0494d..19ca51e 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/processor-configuration.jsp
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/processor-configuration.jsp
@@ -218,6 +218,10 @@
             </div>
             <div id="processor-properties-tab-content" 
class="configuration-tab">
                 <div id="processor-properties"></div>
+                <div id="processor-properties-verification-results" 
class="verification-results">
+                    <div class="verification-results-header">Verification 
Results</div>
+                    <div 
id="processor-properties-verification-results-listing" 
class="verification-results-listing"></div>
+                </div>
             </div>
             <div id="processor-comments-tab-content" class="configuration-tab">
                 <textarea cols="30" rows="4" id="processor-comments" 
name="processor-comments" class="setting-input"></textarea>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/referenced-attributes-configuration.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/referenced-attributes-configuration.jsp
new file mode 100644
index 0000000..510f500
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/referenced-attributes-configuration.jsp
@@ -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.
+--%>
+<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
+<div id="referenced-attributes-dialog" class="hidden medium-dialog">
+    <div class="dialog-content">
+        <div class="setting">
+            <div id="referenced-attributes-header">
+                <div id="referenced-attributes-title" class="setting-name">
+                    Enter Attribute Values
+                    <div class="fa fa-question-circle" alt="Info"
+                         title="Supply attribute values that should be 
considered when performing property verification."></div>
+                </div>
+                <div id="add-referenced-attribute">
+                    <button class="button fa fa-plus"></button>
+                </div>
+                <div class="clear"></div>
+            </div>
+            <div class="setting-field">
+                <div id="referenced-attributes-table"></div>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="new-referenced-attribute-dialog" class="dialog cancellable 
small-dialog hidden">
+    <div class="dialog-content">
+        <div>
+            <div class="setting-name">Attribute name</div>
+            <div class="setting-field">
+                <input id="new-referenced-attribute-name" type="text"/>
+            </div>
+        </div>
+    </div>
+</div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/reporting-task-configuration.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/reporting-task-configuration.jsp
index 6a488d0..f77776b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/reporting-task-configuration.jsp
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/reporting-task-configuration.jsp
@@ -83,6 +83,10 @@
             </div>
             <div id="reporting-task-properties-tab-content" 
class="configuration-tab">
                 <div id="reporting-task-properties"></div>
+                <div id="reporting-task-properties-verification-results" 
class="verification-results">
+                    <div class="verification-results-header">Verification 
Results</div>
+                    <div 
id="reporting-task-properties-verification-results-listing" 
class="verification-results-listing"></div>
+                </div>
             </div>
             <div id="reporting-task-comments-tab-content" 
class="configuration-tab">
                 <textarea cols="30" rows="4" id="reporting-task-comments" 
name="reporting-task-comments" class="reporting-task-editable 
setting-input"></textarea>
@@ -96,4 +100,4 @@
         </div>
     </div>
 </div>
-<div id="new-reporting-task-property-container"></div>
\ No newline at end of file
+<div id="new-reporting-task-property-container"></div>
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/verification-request-status-dialog.jsp
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/verification-request-status-dialog.jsp
new file mode 100644
index 0000000..76340a1
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/verification-request-status-dialog.jsp
@@ -0,0 +1,29 @@
+<%--
+ 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="verification-request-status-dialog" layout="column" class="hidden 
small-dialog">
+    <div class="dialog-content">
+        <div class="setting">
+            <div class="setting-field">
+                <div id="verification-request-status-message"></div>
+            </div>
+            <div class="setting-field">
+                <div id="verification-request-percent-complete"></div>
+            </div>
+        </div>
+    </div>
+</div>
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 3fa0107..e28d680 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
@@ -285,6 +285,98 @@ div.local-changes-message {
 }
 
 /*
+    Referenced Attributes
+ */
+
+#referenced-attributes-table {
+    position: relative;
+    height: 240px;
+}
+
+#add-referenced-attribute {
+    float: right;
+    margin-bottom: 4px;
+    font-size: 16px;
+    text-transform: uppercase;
+}
+
+#referenced-attributes-title {
+    font-size: 12px;
+    float: left;
+    height: 28px;
+    line-height: 28px;
+    margin-left: 6px;
+}
+
+#verification-request-percent-complete {
+    margin-top: 10px;
+    border-radius: 0;
+}
+
+div.verification-results {
+    display: none;
+    position: absolute;
+    top: calc(60% - -10px);
+    right: 0;
+    left: 0;
+    bottom: 0;
+}
+
+div.verification-results-header {
+    color: #728e9b;
+    font-size: 16px;
+    font-family: 'Roboto Slab';
+    font-style: normal;
+    font-weight: bold;
+}
+
+div.verification-results-listing {
+    border: 0 solid #CCCCCC;
+    overflow: auto;
+    position: absolute;
+    top: 20px;
+    right: 0;
+    left: 0;
+    bottom: 0;
+    padding: 10px;
+}
+
+div.verification-result {
+    margin-bottom: 8px;
+}
+
+div.verification-result-outcome {
+    float: left;
+    font-size: 14px;
+    width: 14px;
+    margin-right: 10px;
+}
+
+div.verification-result-outcome.fa-check {
+    color: #4fa885;
+}
+
+div.verification-result-outcome.fa-times {
+    color: #e21a1a;
+}
+
+div.verification-result-outcome.fa-exclamation {
+    color: #eB8f11;
+}
+
+div.verification-result-step-name {
+    float: left;
+    font-size: 14px;
+    font-weight: 500;
+}
+
+div.verification-result-explanation {
+    font-size: 12px;
+    margin-left: 24px;
+    margin-top: 2px;
+}
+
+/*
     Variable Registry
  */
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/processor-configuration.css
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/processor-configuration.css
index 6a78ef8..81481f5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/processor-configuration.css
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/processor-configuration.css
@@ -191,4 +191,4 @@ div.processor-relationship-container {
 /* status bar button icon */
 #processor-configuration div.dialog-status-bar 
div.button-icon.fa-hourglass-end {
     font-size : 11px !important;
-}
\ No newline at end of file
+}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.css
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.css
index bf0d1fb..38ceef1 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.css
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.css
@@ -28,28 +28,20 @@ div.required-property-note {
     margin-left: 6px;
 }
 
-div.add-property {
+div.add-property, div.verify-properties {
     float: right;
     margin-bottom: 4px;
+    margin-left: 4px;
     font-size: 16px;
     text-transform: uppercase;
 }
 
-div.add-property-text {
-    float: left;
-    margin-left: 5px;
-    height: 19px;
-    line-height: 19px;
-    color: #262626;
-}
-
 div.property-table {
     position: absolute;
     top: 42px;
     bottom: 0px;
     left: 0px;
     right: 0px;
-    min-height: 150px;
 }
 
 .property-table .fa {
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
index b307882..148bb78 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/jquery/propertytable/jquery.propertytable.js
@@ -105,6 +105,7 @@
                   _) {
 
     var groupId = null;
+    var propertyVerificationCallback = null;
     var COMBO_MIN_WIDTH = 212;
     var EDITOR_MIN_WIDTH = 212;
     var EDITOR_MIN_HEIGHT = 100;
@@ -1784,6 +1785,22 @@
         }
     };
 
+    var marshalProperties = function (table) {
+        var properties = {};
+        var propertyGrid = table.data('gridInstance');
+        var propertyData = propertyGrid.getData();
+        $.each(propertyData.getItems(), function () {
+            if (this.hidden === true && !(this.dependent === true)) {
+                // hidden properties were removed by the user, clear the value
+                properties[this.property] = null;
+            } else if (this.value !== this.previousValue) {
+                // the value has changed
+                properties[this.property] = this.value;
+            }
+        });
+        return properties;
+    };
+
     /**
      * Performs the filtering.
      *
@@ -1954,7 +1971,8 @@
          *      return $.Deferred(function (deferred) {
          *          deferred.resolve();
          *      }).promise;
-         *   }
+         *   },
+         *   propertyVerificationCallback: function () {}
          * }
          *
          * @argument {object} options The options for the tag cloud
@@ -2125,7 +2143,7 @@
                         newPropertyDialog.on('click', 'div.new-property-ok', 
add).on('click', 'div.new-property-cancel', cancel);
 
                         // build the control to open the new property dialog
-                        var addProperty = $('<div 
class="add-property"></div>').appendTo(header);
+                        var addProperty = $('<div class="add-property" 
title="Add Property"></div>').appendTo(header);
                         $('<button class="button fa 
fa-plus"></button>').on('click', function () {
                             // close all fields currently being edited
                             saveRow(table);
@@ -2139,6 +2157,21 @@
                             // set the initial focus
                             newPropertyNameField.focus();
                         }).appendTo(addProperty);
+
+                        // build the control to trigger verification
+                        var verifyProperties = $('<div 
class="verify-properties hidden" title="Verify 
Properties"></div>').appendTo(header);
+                        $('<button class="button fa 
fa-check-circle-o"></button>').on('click', function () {
+                            // close all fields currently being edited
+                            saveRow(table);
+
+                            // invoke the verification callback with the 
current properties
+                            
propertyVerificationCallback(marshalProperties(table));
+                        }).appendTo(verifyProperties);
+
+                        // if there is a verification callback registered show 
the verify button
+                        propertyVerificationCallback = 
options.propertyVerificationCallback;
+                        var supportsVerification = typeof 
propertyVerificationCallback === 'function';
+                        verifyProperties.toggleClass('hidden', 
!supportsVerification);
                     }
                     $('<div class="clear"></div>').appendTo(header);
 
@@ -2265,18 +2298,7 @@
             this.each(function () {
                 // get the property grid data
                 var table = $(this).find('div.property-table');
-                var propertyGrid = table.data('gridInstance');
-                var propertyData = propertyGrid.getData();
-                $.each(propertyData.getItems(), function () {
-                    if (this.hidden === true && !(this.dependent === true)) {
-                        // hidden properties were removed by the user, clear 
the value
-                        properties[this.property] = null;
-                    } else if (this.value !== this.previousValue) {
-                        // the value has changed
-                        properties[this.property] = this.value;
-                    }
-                });
-
+                properties = marshalProperties(table);
                 return false;
             });
 
@@ -2291,6 +2313,18 @@
             return this.each(function () {
                 groupId = currentGroupId;
             });
+        },
+
+        /**
+         * Sets the property verification callback.
+         */
+        setPropertyVerificationCallback: function 
(currentPropertyVerificationCallback) {
+            return this.each(function () {
+                propertyVerificationCallback = 
currentPropertyVerificationCallback;
+
+                var supportsVerification = typeof 
currentPropertyVerificationCallback === 'function';
+                $(this).find('div.verify-properties').toggleClass('hidden', 
!supportsVerification);
+            });
         }
     };
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
index 485d290..e51f363 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
@@ -37,6 +37,7 @@
                 'nf.Snippet',
                 'nf.Actions',
                 'nf.QueueListing',
+                'nf.Verify',
                 'nf.VariableRegistry',
                 'nf.ComponentState',
                 'nf.FlowVersion',
@@ -83,8 +84,8 @@
                 'nf.ng.Canvas.OperateCtrl',
                 'nf.ng.BreadcrumbsDirective',
                 'nf.ng.DraggableDirective'],
-            function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, 
nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, 
nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, 
nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, 
nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, 
nfConnectionConfiguration, nfControllerService, nfReportingTask, 
nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupCo [...]
-                return factory($, angular, nfCommon, nfCanvasUtils, 
nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, 
nfContextMenu, nfQuickSelect, nfShell, nfParameterContexts, nfSettings, 
nfActions, nfSnippet, nfQueueListing, nfVariableRegistry, nfComponentState, 
nfFlowVersion, nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, 
nfBirdseye, nfConnectionConfiguration, nfControllerService, nfReportingTask, 
nfPolicyManagement, nfProcessorConfiguration, nfProce [...]
+            function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, 
nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, 
nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, 
nfVerify, nfVariableRegistry, nfComponentState, nfFlowVersion, 
nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, 
nfConnectionConfiguration, nfControllerService, nfReportingTask, 
nfPolicyManagement, nfProcessorConfiguration, nfProc [...]
+                return factory($, angular, nfCommon, nfCanvasUtils, 
nfErrorHandler, nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, 
nfContextMenu, nfQuickSelect, nfShell, nfParameterContexts, nfSettings, 
nfActions, nfSnippet, nfQueueListing, nfVerify, nfVariableRegistry, 
nfComponentState, nfFlowVersion, nfComponentVersion, nfDraggable, 
nfConnectable, nfStatusHistory, nfBirdseye, nfConnectionConfiguration, 
nfControllerService, nfReportingTask, nfPolicyManagement, 
nfProcessorConfiguratio [...]
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = factory(require('jquery'),
@@ -105,6 +106,7 @@
             require('nf.Actions'),
             require('nf.Snippet'),
             require('nf.QueueListing'),
+            require('nf.Verify'),
             require('nf.VariableRegistry'),
             require('nf.ComponentState'),
             require('nf.FlowVersion'),
@@ -170,6 +172,7 @@
             root.nf.Actions,
             root.nf.Snippet,
             root.nf.QueueListing,
+            root.nf.Verify,
             root.nf.VariableRegistry,
             root.nf.ComponentState,
             root.nf.FlowVersion,
@@ -217,7 +220,7 @@
             root.nf.ng.BreadcrumbsDirective,
             root.nf.ng.DraggableDirective);
     }
-}(this, function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, 
nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, 
nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, 
nfVariableRegistry, nfComponentState, nfFlowVersion, nfComponentVersion, 
nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, 
nfConnectionConfiguration, nfControllerService, nfReportingTask, 
nfPolicyManagement, nfProcessorConfiguration, nfProcessGroupConfig [...]
+}(this, function ($, angular, nfCommon, nfCanvasUtils, nfErrorHandler, 
nfClient, nfDialog, nfStorage, nfCanvas, nfGraph, nfContextMenu, nfQuickSelect, 
nfShell, nfParameterContexts, nfSettings, nfActions, nfSnippet, nfQueueListing, 
nfVerify, nfVariableRegistry, nfComponentState, nfFlowVersion, 
nfComponentVersion, nfDraggable, nfConnectable, nfStatusHistory, nfBirdseye, 
nfConnectionConfiguration, nfControllerService, nfReportingTask, 
nfPolicyManagement, nfProcessorConfiguration, nfProcessG [...]
 
     var config = {
         urls: {
@@ -349,6 +352,7 @@
                     nfParameterContexts.init();
                     nfActions.init();
                     nfQueueListing.init();
+                    nfVerify.init();
                     nfVariableRegistry.init();
                     nfComponentState.init();
                     nfFlowVersion.init(configDetails.timeOffset);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
index 5aa2365..186c0ea 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
@@ -29,10 +29,11 @@
                 'nf.Settings',
                 'nf.UniversalCapture',
                 'nf.CustomUi',
+                'nf.Verify',
                 'nf.CanvasUtils',
                 'nf.Processor'],
-            function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfSettings, nfUniversalCapture, nfCustomUi, nfCanvasUtils, 
nfProcessor) {
-                return (nf.ControllerService = factory($, d3, nfErrorHandler, 
nfCommon, nfDialog, nfStorage, nfClient, nfSettings, nfUniversalCapture, 
nfCustomUi, nfCanvasUtils, nfProcessor));
+            function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfSettings, nfUniversalCapture, nfCustomUi, nfVerify, nfCanvasUtils, 
nfProcessor) {
+                return (nf.ControllerService = factory($, d3, nfErrorHandler, 
nfCommon, nfDialog, nfStorage, nfClient, nfSettings, nfUniversalCapture, 
nfVerify, nfCustomUi, nfCanvasUtils, nfProcessor));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ControllerService =
@@ -46,6 +47,7 @@
                 require('nf.Settings'),
                 require('nf.UniversalCapture'),
                 require('nf.CustomUi'),
+                require('nf.Verify'),
                 require('nf.CanvasUtils'),
                 require('nf.Processor')));
     } else {
@@ -59,10 +61,11 @@
             root.nf.Settings,
             root.nf.UniversalCapture,
             root.nf.CustomUi,
+            root.nf.Verify,
             root.nf.CanvasUtils,
             root.nf.Processor);
     }
-}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfSettings, nfUniversalCapture, nfCustomUi, nfCanvasUtils, 
nfProcessor) {
+}(this, function ($, d3, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfSettings, nfUniversalCapture, nfCustomUi, nfVerify, nfCanvasUtils, 
nfProcessor) {
     'use strict';
 
     var nfControllerServices, nfReportingTask;
@@ -77,6 +80,9 @@
         }
     };
 
+    // the last submitted referenced attributes
+    var referencedAttributes = null;
+
     /**
      * Determines whether the user has made any changes to the controller 
service configuration
      * that needs to be saved.
@@ -1697,6 +1703,29 @@
     };
 
     /**
+     * Handles verification results.
+     */
+    var handleVerificationResults = function (verificationResults, 
referencedAttributeMap) {
+        // record the most recently submitted referenced attributes
+        referencedAttributes = referencedAttributeMap;
+
+        var verificationResultsContainer = 
$('#controller-service-properties-verification-results');
+
+        // expand the dialog to make room for the verification result
+        if (verificationResultsContainer.is(':visible') === false) {
+            // show the verification results
+            $('#controller-service-properties').css('bottom', 
'40%').propertytable('resetTableSize')
+            verificationResultsContainer.show();
+        }
+
+        // show borders if appropriate
+        var verificationResultsListing = 
$('#controller-service-properties-verification-results-listing');
+        if (verificationResultsListing.get(0).scrollHeight > 
Math.round(verificationResultsListing.innerHeight())) {
+            verificationResultsListing.css('border-width', '1px');
+        }
+    };
+
+    /**
      * Track the current table
      */
     var currentTable;
@@ -1768,6 +1797,14 @@
 
                         // removed the cached controller service details
                         
$('#controller-service-configuration').removeData('controllerServiceDetails');
+
+                        // clean up an shown verification errors
+                        
$('#controller-service-properties-verification-results').hide();
+                        
$('#controller-service-properties-verification-results-listing').css('border-width',
 '0').empty();
+                        $('#controller-service-properties').css('bottom', '0');
+
+                        // clear most recently submitted referenced attributes
+                        referencedAttributes = null;
                     },
                     open: function () {
                         nfCommon.toggleScrollable($('#' + 
this.find('.tab-container').attr('id') + '-content').get(0));
@@ -2061,7 +2098,10 @@
                 // load the property table
                 $('#controller-service-properties')
                     .propertytable('setGroupId', 
controllerService.parentGroupId)
-                    .propertytable('loadProperties', 
controllerService.properties, controllerService.descriptors, 
controllerServiceHistory.propertyHistory);
+                    .propertytable('loadProperties', 
controllerService.properties, controllerService.descriptors, 
controllerServiceHistory.propertyHistory)
+                    .propertytable('setPropertyVerificationCallback', function 
(proposedProperties) {
+                        nfVerify.verify(controllerService['id'], 
controllerServiceEntity['uri'], proposedProperties, referencedAttributes, 
handleVerificationResults, 
$('#controller-service-properties-verification-results-listing'));
+                    });
 
                 // show the details
                 controllerServiceDialog.modal('show');
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
index 089f5a7..d923b3c 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
@@ -30,10 +30,11 @@
                 'nf.Processor',
                 'nf.ClusterSummary',
                 'nf.CustomUi',
+                'nf.Verify',
                 'nf.UniversalCapture',
                 'nf.Connection'],
-            function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfCanvasUtils, nfNgBridge, nfProcessor, nfClusterSummary, nfCustomUi, 
nfUniversalCapture, nfConnection) {
-                return (nf.ProcessorConfiguration = factory($, nfErrorHandler, 
nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfNgBridge, 
nfProcessor, nfClusterSummary, nfCustomUi, nfUniversalCapture, nfConnection));
+            function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfCanvasUtils, nfNgBridge, nfProcessor, nfClusterSummary, nfCustomUi, 
nfVerify, nfUniversalCapture, nfConnection) {
+                return (nf.ProcessorConfiguration = factory($, nfErrorHandler, 
nfCommon, nfDialog, nfStorage, nfClient, nfCanvasUtils, nfNgBridge, 
nfProcessor, nfClusterSummary, nfCustomUi, nfVerify, nfUniversalCapture, 
nfConnection));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ProcessorConfiguration =
@@ -48,6 +49,7 @@
                 require('nf.Processor'),
                 require('nf.ClusterSummary'),
                 require('nf.CustomUi'),
+                require('nf.Verify'),
                 require('nf.UniversalCapture'),
                 require('nf.Connection')));
     } else {
@@ -62,10 +64,11 @@
             root.nf.Processor,
             root.nf.ClusterSummary,
             root.nf.CustomUi,
+            root.nf.Verify,
             root.nf.UniversalCapture,
             root.nf.Connection);
     }
-}(this, function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, 
nfCanvasUtils, nfNgBridge, nfProcessor, nfClusterSummary, nfCustomUi, 
nfUniversalCapture, nfConnection) {
+}(this, function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, 
nfCanvasUtils, nfNgBridge, nfProcessor, nfClusterSummary, nfCustomUi, nfVerify, 
nfUniversalCapture, nfConnection) {
     'use strict';
 
     /**
@@ -81,6 +84,9 @@
         RUN_STATUS_KEY = 'status.aggregateSnapshot.runStatus',
         BULLETINS_KEY = 'bulletins';
 
+    // the last submitted referenced attributes
+    var referencedAttributes = null;
+
     /**
      * Gets the available scheduling strategies based on the specified 
processor.
      *
@@ -499,6 +505,29 @@
         }
     };
 
+    /**
+     * Handles verification results.
+     */
+    var handleVerificationResults = function (verificationResults, 
referencedAttributeMap) {
+        // record the most recently submitted referenced attributes
+        referencedAttributes = referencedAttributeMap;
+
+        var verificationResultsContainer = 
$('#processor-properties-verification-results');
+
+        // expand the dialog to make room for the verification result
+        if (verificationResultsContainer.is(':visible') === false) {
+            // show the verification results
+            $('#processor-properties').css('bottom', 
'40%').propertytable('resetTableSize')
+            verificationResultsContainer.show();
+        }
+
+        // show borders if appropriate
+        var verificationResultsListing = 
$('#processor-properties-verification-results-listing');
+        if (verificationResultsListing.get(0).scrollHeight > 
Math.round(verificationResultsListing.innerHeight())) {
+            verificationResultsListing.css('border-width', '1px');
+        }
+    };
+
     return {
         /**
          * Initializes the processor properties tab.
@@ -562,8 +591,16 @@
                         // removed the cached processor details
                         
$('#processor-configuration').removeData('processorDetails');
 
+                        // clean up an shown verification errors
+                        $('#processor-properties-verification-results').hide();
+                        
$('#processor-properties-verification-results-listing').css('border-width', 
'0').empty();
+                        $('#processor-properties').css('bottom', '0');
+
+                        // clear most recently submitted referenced attributes
+                        referencedAttributes = null;
+
                         //stop any synchronization
-                        if(config.supportsStatusBar){
+                        if (config.supportsStatusBar){
                             
$('#processor-configuration-status-bar').statusbar('disconnect');
                         }
                     },
@@ -982,7 +1019,10 @@
                     // load the property table
                     $('#processor-properties')
                         .propertytable('setGroupId', processor.parentGroupId)
-                        .propertytable('loadProperties', 
processor.config.properties, processor.config.descriptors, 
processorHistory.propertyHistory);
+                        .propertytable('loadProperties', 
processor.config.properties, processor.config.descriptors, 
processorHistory.propertyHistory)
+                        .propertytable('setPropertyVerificationCallback', 
function (proposedProperties) {
+                            nfVerify.verify(processor['id'], 
processorResponse['uri'], proposedProperties, referencedAttributes, 
handleVerificationResults, 
$('#processor-properties-verification-results-listing'));
+                        });
 
                     // show the details
                     $('#processor-configuration').modal('show');
@@ -997,13 +1037,12 @@
                     }
 
                     // Ensure the properties table has rendered correctly if 
initially selected
-                    if 
($('#processor-configuration-tabs').find('.selected-tab').text() === 
'Properties' &&
-                        
$('#processor-properties').find('.slick-viewport').height() == 0) {
+                    if 
($('#processor-configuration-tabs').find('.selected-tab').text() === 
'Properties') {
                         
$('#processor-properties').propertytable('resetTableSize');
                     }
 
-                    //execute the callback if one was provided
-                    if(typeof cb == 'function'){
+                    // execute the callback if one was provided
+                    if (typeof cb == 'function'){
                         cb();
                     }
                 }).fail(nfErrorHandler.handleAjaxError);
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
index 6ffd8e9..7b5c21b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
@@ -28,9 +28,10 @@
                 'nf.ControllerService',
                 'nf.ControllerServices',
                 'nf.UniversalCapture',
-                'nf.CustomUi'],
-            function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, 
nfCustomUi) {
-                return (nf.ReportingTask = factory($, nfErrorHandler, 
nfCommon, nfDialog, nfStorage, nfClient, nfControllerService, 
nfControllerServices, nfUniversalCapture, nfCustomUi));
+                'nf.CustomUi',
+                'nf.Verify'],
+            function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, 
nfClient, nfControllerService, nfControllerServices, nfUniversalCapture, 
nfCustomUi, nfVerify) {
+                return (nf.ReportingTask = factory($, nfErrorHandler, 
nfCommon, nfDialog, nfStorage, nfClient, nfControllerService, 
nfControllerServices, nfUniversalCapture, nfCustomUi, nfVerify));
             });
     } else if (typeof exports === 'object' && typeof module === 'object') {
         module.exports = (nf.ReportingTask =
@@ -43,7 +44,8 @@
                 require('nf.ControllerService'),
                 require('nf.ControllerServices'),
                 require('nf.UniversalCapture'),
-                require('nf.CustomUi')));
+                require('nf.CustomUi'),
+                require('nf.Verify')));
     } else {
         nf.ReportingTask = factory(root.$,
             root.nf.ErrorHandler,
@@ -54,9 +56,10 @@
             root.nf.ControllerService,
             root.nf.ControllerServices,
             root.nf.UniversalCapture,
-            root.nf.CustomUi);
+            root.nf.CustomUi,
+            root.nf.Verify);
     }
-}(this, function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, 
nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi) {
+}(this, function ($, nfErrorHandler, nfCommon, nfDialog, nfStorage, nfClient, 
nfControllerService, nfControllerServices, nfUniversalCapture, nfCustomUi, 
nfVerify) {
     'use strict';
 
     var nfSettings;
@@ -69,6 +72,9 @@
         }
     };
 
+    // the last submitted referenced attributes
+    var referencedAttributes = null;
+
     // load the controller services
     var controllerServicesUri = config.urls.api + 
'/flow/controller/controller-services';
 
@@ -320,6 +326,30 @@
         }).fail(nfErrorHandler.handleAjaxError);
     };
 
+    /**
+     * Handles verification results.
+     */
+    var handleVerificationResults = function (verificationResults, 
referencedAttributeMap) {
+        // record the most recently submitted referenced attributes
+        referencedAttributes = referencedAttributeMap;
+
+        var verificationResultsContainer = 
$('#reporting-task-properties-verification-results');
+
+        // expand the dialog to make room for the verification result
+        if (verificationResultsContainer.is(':visible') === false) {
+            // show the verification results
+            $('#reporting-task-properties').css('bottom', 
'40%').propertytable('resetTableSize')
+            verificationResultsContainer.show();
+        }
+
+        // show borders if appropriate
+        var verificationResultsListing = 
$('#reporting-task-properties-verification-results-listing');
+        if (verificationResultsListing.get(0).scrollHeight > 
Math.round(verificationResultsListing.innerHeight())) {
+            verificationResultsListing.css('border-width', '1px');
+        }
+    };
+
+
     var nfReportingTask = {
         /**
          * Initializes the reporting task configuration dialog.
@@ -375,6 +405,14 @@
 
                         // removed the cached reporting task details
                         
$('#reporting-task-configuration').removeData('reportingTaskDetails');
+
+                        // clean up an shown verification errors
+                        
$('#reporting-task-properties-verification-results').hide();
+                        
$('#reporting-task-properties-verification-results-listing').css('border-width',
 '0').empty();
+                        $('#reporting-task-properties').css('bottom', '0');
+
+                        // clear most recently submitted referenced attributes
+                        referencedAttributes = null;
                     },
                     open: function () {
                         nfCommon.toggleScrollable($('#' + 
this.find('.tab-container').attr('id') + '-content').get(0));
@@ -605,7 +643,10 @@
                 // load the property table
                 $('#reporting-task-properties')
                     .propertytable('setGroupId', null)
-                    .propertytable('loadProperties', reportingTask.properties, 
reportingTask.descriptors, reportingTaskHistory.propertyHistory);
+                    .propertytable('loadProperties', reportingTask.properties, 
reportingTask.descriptors, reportingTaskHistory.propertyHistory)
+                    .propertytable('setPropertyVerificationCallback', function 
(proposedProperties) {
+                        nfVerify.verify(reportingTask['id'], 
reportingTaskEntity['uri'], proposedProperties, referencedAttributes, 
handleVerificationResults, 
$('#reporting-task-properties-verification-results-listing'));
+                    });
 
                 // show the details
                 $('#reporting-task-configuration').modal('show');
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-verify.js
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-verify.js
new file mode 100644
index 0000000..0e50c83
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-verify.js
@@ -0,0 +1,847 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* global define, module, require, exports */
+
+(function (root, factory) {
+    if (typeof define === 'function' && define.amd) {
+        define(['jquery',
+                'nf.Common',
+                'nf.Dialog',
+                'nf.ng.Bridge',
+                'nf.ErrorHandler'],
+            function ($, nfCommon, nfDialog, nfNgBridge, nfErrorHandler) {
+                return (nf.Verify = factory($, nfCommon, nfDialog, nfNgBridge, 
nfErrorHandler));
+            });
+    } else if (typeof exports === 'object' && typeof module === 'object') {
+        module.exports = (nf.Verify =
+            factory(require('jquery'),
+                require('nf.Common'),
+                require('nf.Dialog'),
+                require('nf.ng.Bridge'),
+                require('nf.ErrorHandler')));
+    } else {
+        nf.Verify = factory(root.$,
+            root.nf.Common,
+            root.nf.Dialog,
+            root.nf.ng.Bridge,
+            root.nf.ErrorHandler);
+    }
+}(this, function ($, nfCommon, nfDialog, nfNgBridge, nfErrorHandler) {
+    'use strict';
+
+    var gridOptions = {
+        autosizeColsMode: Slick.GridAutosizeColsMode.LegacyForceFit,
+        enableTextSelectionOnCells: true,
+        enableCellNavigation: true,
+        enableColumnReorder: false,
+        editable: true,
+        enableAddRow: false,
+        autoEdit: false,
+        multiSelect: false,
+        rowHeight: 24
+    };
+
+    // text editor
+    var textEditor = function (args) {
+        var scope = this;
+        var initialValue = '';
+        var previousValue;
+        var wrapper;
+        var isEmpty;
+        var input;
+
+        this.init = function () {
+            var container = $('body');
+
+            // record the previous value
+            previousValue = args.item[args.column.field];
+
+            // create the wrapper
+            wrapper = $('<div></div>').addClass('slickgrid-editor').css({
+                'z-index': 100000,
+                'position': 'absolute',
+                'border-radius': '2px',
+                'box-shadow': 'rgba(0, 0, 0, 0.247059) 0px 2px 5px',
+                'background-color': 'rgb(255, 255, 255)',
+                'overflow': 'hidden',
+                'padding': '10px 20px',
+                'cursor': 'move',
+                'transform': 'translate3d(0px, 0px, 0px)'
+            }).appendTo(container);
+
+            // create the input field
+            input = $('<textarea hidefocus rows="5"/>').css({
+                'height': '80px',
+                'width': args.position.width + 'px',
+                'min-width': '212px',
+                'margin-bottom': '5px',
+                'margin-top': '10px',
+                'white-space': 'pre'
+            }).tab().on('keydown', scope.handleKeyDown).appendTo(wrapper);
+
+            wrapper.draggable({
+                cancel: '.button, textarea, .nf-checkbox',
+                containment: 'parent'
+            });
+
+            // create the button panel
+            var stringCheckPanel = $('<div class="string-check-container" />');
+            stringCheckPanel.appendTo(wrapper);
+
+            // build the custom checkbox
+            isEmpty = $('<div class="nf-checkbox string-check" />')
+                .on('change', function (event, args) {
+                    // if we are setting as an empty string, disable the editor
+                    if (args.isChecked) {
+                        input.prop('disabled', true).val('');
+                    } else {
+                        input.prop('disabled', false).val(previousValue);
+                    }
+                }).appendTo(stringCheckPanel);
+            $('<span class="string-check-label nf-checkbox-label">&nbsp;Set 
empty string</span>').appendTo(stringCheckPanel);
+
+            var ok = $('<div class="button">Ok</div>').css({
+                'color': '#fff',
+                'background': '#728E9B'
+            }).hover(
+                function () {
+                    $(this).css('background', '#004849');
+                }, function () {
+                    $(this).css('background', '#728E9B');
+                }).on('click', scope.save);
+            var cancel = $('<div class="secondary-button">Cancel</div>').css({
+                'color': '#004849',
+                'background': '#E3E8EB'
+            }).hover(
+                function () {
+                    $(this).css('background', '#C7D2D7');
+                }, function () {
+                    $(this).css('background', '#E3E8EB');
+                }).on('click', scope.cancel);
+            $('<div></div>').css({
+                'position': 'relative',
+                'top': '10px',
+                'left': '20px',
+                'width': '212px',
+                'clear': 'both',
+                'float': 'right'
+            }).append(ok).append(cancel).append('<div 
class="clear"></div>').appendTo(wrapper);
+
+            // position and focus
+            scope.position(args.position);
+            input.focus().select();
+        };
+
+        this.handleKeyDown = function (e) {
+            if (e.which === $.ui.keyCode.ENTER && !e.shiftKey) {
+                scope.save();
+            } else if (e.which === $.ui.keyCode.ESCAPE) {
+                scope.cancel();
+
+                // prevent further propagation or escape press and prevent 
default behavior
+                e.stopImmediatePropagation();
+                e.preventDefault();
+            }
+        };
+
+        this.save = function () {
+            args.commitChanges();
+        };
+
+        this.cancel = function () {
+            input.val(initialValue);
+            args.cancelChanges();
+        };
+
+        this.hide = function () {
+            wrapper.hide();
+        };
+
+        this.show = function () {
+            wrapper.show();
+        };
+
+        this.position = function (position) {
+            wrapper.css({
+                'top': position.top - 27,
+                'left': position.left - 20
+            });
+        };
+
+        this.destroy = function () {
+            wrapper.remove();
+        };
+
+        this.focus = function () {
+            input.focus();
+        };
+
+        this.loadValue = function (item) {
+            var isEmptyChecked = false;
+
+            // determine the value to use when populating the text field
+            if (nfCommon.isDefinedAndNotNull(item[args.column.field])) {
+                initialValue = item[args.column.field];
+                isEmptyChecked = initialValue === '';
+            }
+
+            // determine if its an empty string
+            var checkboxStyle = isEmptyChecked ? 'checkbox-checked' : 
'checkbox-unchecked';
+            isEmpty.addClass(checkboxStyle);
+
+            input.val(initialValue);
+            input.select();
+        };
+
+        this.serializeValue = function () {
+            // if the field has been cleared, set the value accordingly
+            if (input.val() === '') {
+                // if the user has checked the empty string checkbox, use 
emtpy string
+                if (isEmpty.hasClass('checkbox-checked')) {
+                    return '';
+                } else {
+                    return null;
+                }
+            } else {
+                // if there is text specified, use that value
+                return input.val();
+            }
+        };
+
+        this.applyValue = function (item, state) {
+            item[args.column.field] = state;
+        };
+
+        this.isValueChanged = function () {
+            return scope.serializeValue() !== previousValue;
+        };
+
+        this.validate = function () {
+            return {
+                valid: true,
+                msg: null
+            };
+        };
+
+        // initialize the custom long text editor
+        this.init();
+    };
+
+    /**
+     * Reset the dialog.
+     */
+    var resetDialog = function () {
+        var referencedAttributesGrid = 
$('#referenced-attributes-table').data('gridInstance');
+        var referencedAttributesData = referencedAttributesGrid.getData();
+        referencedAttributesGrid.setSelectedRows([]);
+        referencedAttributesData.setItems([]);
+    };
+
+    /**
+     * Initialized the referenced attributes dialog.
+     */
+    var initializeReferencedAttributesDialog = function () {
+        $('#referenced-attributes-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Referenced Attributes',
+            handler: {
+                close: function () {
+                    resetDialog();
+                },
+                open: function () {
+                    var referencedAttributesGrid = 
$('#referenced-attributes-table').data('gridInstance');
+                    if 
(nfCommon.isDefinedAndNotNull(referencedAttributesGrid)) {
+                        referencedAttributesGrid.resizeCanvas();
+                    }
+                }
+            }
+        });
+    };
+
+    var verify = function (componentId, componentUri, proposedProperties, 
referencedAttributeMap, handleVerificationResults, 
verificationResultsContainer) {
+        // submit verification
+        performVerification(componentId, componentUri, proposedProperties, 
referencedAttributeMap).done(function (verificationResults) {
+            // empty the previous listing
+            verificationResultsContainer.empty();
+
+            // render the verification results
+            $.each(verificationResults, function (i, result) {
+                var verificationResultContainer = $('<div 
class="verification-result"></div>').appendTo(verificationResultsContainer);
+
+                // determine the icon for this result
+                var outcomeClass;
+                switch (result.outcome) {
+                    case 'SUCCESSFUL':
+                        outcomeClass = 'fa-check';
+                        break;
+                    case 'FAILED':
+                        outcomeClass = 'fa-times';
+                        break;
+                    case 'SKIPPED':
+                        outcomeClass = 'fa-exclamation';
+                        break;
+                }
+
+                // build the header
+                var verificationHeader = $('<div 
class="verification-result-header"></div>').appendTo(verificationResultContainer);
+                $('<div class="verification-result-outcome fa ' + outcomeClass 
+ '"></div>').appendTo(verificationHeader);
+                $('<div 
class="verification-result-step-name"></div>').text(result.verificationStepName).appendTo(verificationHeader);
+                $('<div class="clear"></div>').appendTo(verificationHeader);
+
+                // build the explanation
+                $('<div 
class="verification-result-explanation"></div>').text(result.explanation).appendTo(verificationResultContainer);
+            });
+
+            // invoke the verification callback if specified
+            if (typeof handleVerificationResults === 'function') {
+                handleVerificationResults(verificationResults, 
referencedAttributeMap);
+            }
+        });
+    }
+
+    /**
+     * Updates the button model for the verification of this component and 
proposed properties.
+     *
+     * @param componentId the component id
+     * @param componentUri the component uri
+     * @param proposedProperties the proposed properties
+     * @param handleVerificationResults verification results callback
+     * @param verificationResultsContainer container where verification 
results should be rendered
+     */
+    var updateReferencedAttributesButtonModel = function (componentId, 
componentUri, proposedProperties, handleVerificationResults, 
verificationResultsContainer) {
+        $('#referenced-attributes-dialog').modal('setButtonModel', [{
+            buttonText: 'Verify',
+            color: {
+                base: '#728E9B',
+                hover: '#004849',
+                text: '#ffffff'
+            },
+            handler: {
+                click: function () {
+                    // get the referenced attributes name/values
+                    var referencedAttributesGrid = 
$('#referenced-attributes-table').data('gridInstance');
+                    var referencedAttributesData = 
referencedAttributesGrid.getData();
+                    var referencedAttributes = 
referencedAttributesData.getItems();
+
+                    // map the referenced attributes
+                    var referencedAttributeMap = 
referencedAttributes.reduce(function(map, referencedAttribute) {
+                        map[referencedAttribute.name] = 
referencedAttribute.value;
+                        return map;
+                    }, {});
+
+                    // hide the referenced attributes dialog prior to 
performing the verification
+                    $('#referenced-attributes-dialog').modal('hide');
+
+                    // verify
+                    verify(componentId, componentUri, proposedProperties, 
referencedAttributeMap, handleVerificationResults, 
verificationResultsContainer);
+                }
+            }
+        }, {
+            buttonText: 'Cancel',
+            color: {
+                base: '#E3E8EB',
+                hover: '#C7D2D7',
+                text: '#004849'
+            },
+            handler: {
+                click: function () {
+                    $('#referenced-attributes-dialog').modal('hide');
+                }
+            }
+        }]);
+    };
+
+    /**
+     * Initializes the verification request status dialog.
+     */
+    var initializeVerificationRequestStatusDialog = function () {
+        // configure the verification request status dialog
+        $('#verification-request-status-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Verifying Properties',
+            handler: {
+                close: function () {
+                    // clear the current button model
+                    
$('#verification-request-status-dialog').modal('setButtonModel', []);
+                }
+            }
+        });
+    };
+
+    /**
+     * Initialized the new referenced attribute dialog.
+     */
+    var initializeNewAttributeDialog = function () {
+        $('#new-referenced-attribute-dialog').modal({
+            headerText: 'New Attribute',
+            buttons: [{
+                buttonText: 'Ok',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                handler: {
+                    click: function () {
+                        addNewReferencedAttribute();
+                    }
+                }
+            }, {
+                buttonText: 'Cancel',
+                color: {
+                    base: '#E3E8EB',
+                    hover: '#C7D2D7',
+                    text: '#004849'
+                },
+                handler: {
+                    click: function () {
+                        $('#new-referenced-attribute-dialog').modal('hide');
+                    }
+                }
+            }],
+            handler: {
+                close: function () {
+                    $('#new-referenced-attribute-name').val('');
+                },
+                open: function () {
+                    $('#new-referenced-attribute-name').focus();
+                }
+            }
+        });
+
+        $('#new-referenced-attribute-name').on('keydown', function (e) {
+            var code = e.keyCode ? e.keyCode : e.which;
+            if (code === $.ui.keyCode.ENTER) {
+                addNewReferencedAttribute();
+
+                // prevents the enter from propagating into the field for 
editing the new property value
+                e.stopImmediatePropagation();
+                e.preventDefault();
+            }
+        });
+
+        $('#add-referenced-attribute').on('click', function () {
+            $('#new-referenced-attribute-dialog').modal('show');
+        });
+    }
+
+    /**
+     * Adds a new referenced attribute.
+     */
+    var addNewReferencedAttribute = function () {
+        var attributeName = $.trim($('#new-referenced-attribute-name').val());
+
+        // ensure the property name is specified
+        if (attributeName !== '') {
+            var referencedAttributeGrid = 
$('#referenced-attributes-table').data('gridInstance');
+            var referencedAttributeData = referencedAttributeGrid.getData();
+
+            // ensure the property name is unique
+            var matchingAttribute = null;
+            $.each(referencedAttributeData.getItems(), function (_, item) {
+                if (attributeName === item.name) {
+                    matchingAttribute = item;
+                }
+            });
+
+            if (matchingAttribute === null) {
+                referencedAttributeData.beginUpdate();
+
+                // add a row for the new attribute
+                referencedAttributeData.addItem({
+                    id: attributeName,
+                    name: attributeName,
+                    value: null,
+                });
+
+                referencedAttributeData.endUpdate();
+
+                // sort the data
+                referencedAttributeData.reSort();
+
+                // select the new variable row
+                var row = referencedAttributeData.getRowById(attributeName);
+                referencedAttributeGrid.setActiveCell(row, 
referencedAttributeGrid.getColumnIndex('value'));
+                referencedAttributeGrid.editActiveCell();
+            } else {
+                // if this row is currently hidden, clear the value and show it
+                nfDialog.showOkDialog({
+                    headerText: 'Attribute Exists',
+                    dialogContent: 'An attribute with this name already 
exists.'
+                });
+
+                // select the existing properties row
+                var matchingRow = 
referencedAttributeData.getRowById(matchingAttribute.id);
+                referencedAttributeGrid.setSelectedRows([matchingRow]);
+                referencedAttributeGrid.scrollRowIntoView(matchingRow);
+            }
+
+            // close the new variable dialog
+            $('#new-referenced-attribute-dialog').modal('hide');
+        } else {
+            nfDialog.showOkDialog({
+                headerText: 'Attribute Error',
+                dialogContent: 'The name of the attribute must be specified.'
+            });
+        }
+    };
+
+    /**
+     * Sorts the specified data using the specified sort details.
+     *
+     * @param {object} sortDetails
+     * @param {object} data
+     */
+    var sortReferencedAttributes = function (sortDetails, data) {
+        // defines a function for sorting
+        var comparer = function (a, b) {
+            var aString = 
nfCommon.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] 
: '';
+            var bString = 
nfCommon.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] 
: '';
+            return aString === bString ? 0 : aString > bString ? 1 : -1;
+        };
+
+        // perform the sort
+        data.sort(comparer, sortDetails.sortAsc);
+    };
+
+    /**
+     * Initializes the referenced attributes table
+     */
+    var initializeReferencedAttributesTable = function () {
+        var referencedAttributesGridElement = 
$('#referenced-attributes-table');
+
+        var nameFormatter = function (row, cell, value, columnDef, 
dataContext) {
+            return nfCommon.escapeHtml(value);
+        };
+
+        var valueFormatter = function (row, cell, value, columnDef, 
dataContext) {
+            if (value === '') {
+                return '<span class="table-cell blank">Empty string 
set</span>';
+            } else if (value === null) {
+                return '<span class="unset">No value set</span>';
+            } else {
+                return nfCommon.escapeHtml(value);
+            }
+        };
+
+        // define the column model for the referenced attributes table
+        var referencedAttributesColumns = [
+            {
+                id: 'name',
+                name: 'Name',
+                field: 'name',
+                formatter: nameFormatter,
+                sortable: true,
+                resizable: true
+            },
+            {
+                id: 'value',
+                name: 'Value',
+                field: 'value',
+                formatter: valueFormatter,
+                sortable: true,
+                resizable: true,
+                cssClass: 'pointer'
+            }
+        ];
+
+        // initialize the dataview
+        var referencedAttributesData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        referencedAttributesData.getItemMetadata = function (index) {
+            return {
+                columns: {
+                    value: {
+                        editor: textEditor
+                    }
+                }
+            };
+        };
+
+        // initialize the sort
+        sortReferencedAttributes({
+            columnId: 'name',
+            sortAsc: true
+        }, referencedAttributesData);
+
+        // initialize the grid
+        var referencedAttributesGrid = new 
Slick.Grid(referencedAttributesGridElement, referencedAttributesData, 
referencedAttributesColumns, gridOptions);
+        referencedAttributesGrid.setSelectionModel(new 
Slick.RowSelectionModel());
+        referencedAttributesGrid.registerPlugin(new Slick.AutoTooltips());
+        referencedAttributesGrid.setSortColumn('name', true);
+        referencedAttributesGrid.onSort.subscribe(function (e, args) {
+            sortReferencedAttributes({
+                columnId: args.sortCol.id,
+                sortAsc: args.sortAsc
+            }, referencedAttributesData);
+        });
+        referencedAttributesGrid.onClick.subscribe(function (e, args) {
+            if (referencedAttributesGrid.getColumns()[args.cell].id === 
'value') {
+                referencedAttributesGrid.gotoCell(args.row, args.cell, true);
+
+                // prevents standard edit logic
+                e.stopImmediatePropagation();
+            }
+        });
+        referencedAttributesGrid.onBeforeCellEditorDestroy.subscribe(function 
(e, args) {
+            setTimeout(function() {
+                referencedAttributesGrid.resizeCanvas();
+            }, 50);
+        });
+
+        // wire up the dataview to the grid
+        referencedAttributesData.onRowCountChanged.subscribe(function (e, 
args) {
+            referencedAttributesGrid.updateRowCount();
+            referencedAttributesGrid.render();
+        });
+        referencedAttributesData.onRowsChanged.subscribe(function (e, args) {
+            referencedAttributesGrid.invalidateRows(args.rows);
+            referencedAttributesGrid.render();
+        });
+        referencedAttributesData.syncGridSelection(referencedAttributesGrid, 
true);
+
+        // hold onto an instance of the grid
+        referencedAttributesGridElement.data('gridInstance', 
referencedAttributesGrid);
+    };
+
+    /**
+     * Performs verification for the specific configuration and referenced 
attributes.
+     *
+     * @param id the component id
+     * @param componentUrl the component url
+     * @param proposedProperties the proposed properties
+     * @param attributes the attributes names/values
+     */
+    var performVerification = function (id, componentUrl, proposedProperties, 
attributes) {
+        var MAX_DELAY = 4;
+        var cancelled = false;
+        var verificationRequest = null;
+        var verificationRequestTimer = null;
+
+        return $.Deferred(function (deferred) {
+            // updates the progress bar
+            var updateProgress = function (percentComplete) {
+                // remove existing labels
+                var progressBar = $('#verification-request-percent-complete');
+                progressBar.find('div.progress-label').remove();
+                progressBar.find('md-progress-linear').remove();
+
+                // update the progress
+                var label = $('<div 
class="progress-label"></div>').text(percentComplete + '%');
+                (nfNgBridge.injector.get('$compile')($('<md-progress-linear 
ng-cloak ng-value="' + percentComplete + '" class="md-hue-2" 
md-mode="determinate" aria-label="Searching 
Queue"></md-progress-linear>'))(nfNgBridge.rootScope)).appendTo(progressBar);
+                progressBar.append(label);
+            };
+
+            // update the button model of the drop request status dialog
+            $('#verification-request-status-dialog').modal('setButtonModel', [{
+                headerText: 'Verifying Properties',
+                buttonText: 'Stop',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                handler: {
+                    click: function () {
+                        cancelled = true;
+
+                        // we are waiting for the next poll attempt
+                        if (verificationRequestTimer !== null) {
+                            // cancel it
+                            clearTimeout(verificationRequestTimer);
+
+                            // cancel the verification request
+                            completeVerificationRequest();
+                        }
+                    }
+                }
+            }]);
+
+            // completes the verification request by removing it
+            var completeVerificationRequest = function () {
+                $('#verification-request-status-dialog').modal('hide');
+
+                var reject = cancelled;
+
+                // ensure the verification requests are present
+                if (nfCommon.isDefinedAndNotNull(verificationRequest)) {
+                    $.ajax({
+                        type: 'DELETE',
+                        url: verificationRequest.uri,
+                        dataType: 'json'
+                    });
+
+                    // use the verification request from when the verification 
completed
+                    if (nfCommon.isEmpty(verificationRequest.results)) {
+                        if (cancelled === false) {
+                            reject = true;
+
+                            // show the dialog
+                            nfDialog.showOkDialog({
+                                headerText: 'Verifying Properties',
+                                dialogContent: 'There are no results.'
+                            });
+                        }
+                    }
+                } else {
+                    reject = true;
+                }
+
+                if (reject) {
+                    deferred.reject();
+                } else {
+                    deferred.resolve(verificationRequest.results);
+                }
+            };
+
+            // process the verification request
+            var processVerificationRequest = function (delay) {
+                // update the percent complete
+                updateProgress(verificationRequest.percentCompleted);
+
+                // update the status of the verification request
+                
$('#verification-request-status-message').text(verificationRequest.state);
+
+                // close the dialog if the
+                if (verificationRequest.complete === true || cancelled === 
true) {
+                    completeVerificationRequest();
+                } else {
+                    // wait delay to poll again
+                    verificationRequestTimer = setTimeout(function () {
+                        // clear the verification request timer
+                        verificationRequestTimer = null;
+
+                        // schedule to poll the status again in nextDelay
+                        pollVerificationRequest(Math.min(MAX_DELAY, delay * 
2));
+                    }, delay * 1000);
+                }
+            };
+
+            // schedule for the next poll iteration
+            var pollVerificationRequest = function (nextDelay) {
+                $.ajax({
+                    type: 'GET',
+                    url: verificationRequest.uri,
+                    dataType: 'json'
+                }).done(function (response) {
+                    verificationRequest = response.request;
+                    processVerificationRequest(nextDelay);
+                
}).fail(completeVerificationRequest).fail(nfErrorHandler.handleAjaxError);
+            };
+
+            // initialize the progress bar value
+            updateProgress(0);
+
+            // show the progress dialog
+            $('#verification-request-status-dialog').modal('show');
+
+            // issue the request to verify the properties
+            $.ajax({
+                type: 'POST',
+                url: componentUrl + '/config/verification-requests',
+                data: JSON.stringify({
+                    request: {
+                        componentId: id,
+                        properties: proposedProperties,
+                        attributes: attributes
+                    }
+                }),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (response) {
+                // process the verification request
+                verificationRequest = response.request;
+                processVerificationRequest(1);
+            
}).fail(completeVerificationRequest).fail(nfErrorHandler.handleAjaxError);
+        }).promise();
+    };
+
+    return {
+
+        /**
+         * Initialize the verification component.
+         */
+        init: function () {
+            initializeVerificationRequestStatusDialog();
+            initializeReferencedAttributesDialog();
+            initializeReferencedAttributesTable();
+            initializeNewAttributeDialog();
+        },
+
+        /**
+         * Peforms verification for a given component.
+         *
+         * @param id the component id
+         * @param componentUrl the component url
+         * @param proposedProperties the proposed attributes
+         * @param referencedAttributes the most recently submitted referenced 
attributes, null if not submitted previously
+         * @param handleVerificationResults callback for handling verification 
results
+         * @param verificationResultsContainer container where verification 
results should be rendered
+         */
+        verify: function (id, componentUrl, proposedProperties, 
referencedAttributes, handleVerificationResults, verificationResultsContainer) {
+            // get the referenced attributes
+            $.ajax({
+                type: 'POST',
+                url: componentUrl + '/config/analysis',
+                data: JSON.stringify({
+                    configurationAnalysis: {
+                        componentId: id,
+                        properties: proposedProperties
+                    }
+                }),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function(response) {
+                var configurationAnalysis = response.configurationAnalysis;
+
+                // if the component does not support additional verification 
there is no need to prompt for attribute values
+                if (configurationAnalysis.supportsVerification === false) {
+                    verify(id, componentUrl, proposedProperties, {}, 
handleVerificationResults, verificationResultsContainer);
+                } else {
+                    // combine the previously entered referenced attributes 
with the updated attributes from the configuration analysis
+                    var combinedReferencedAttributes = $.extend({}, 
configurationAnalysis.referencedAttributes, referencedAttributes);
+
+                    var referencedAttributesGrid = 
$('#referenced-attributes-table').data('gridInstance');
+                    var referencedAttributesData = 
referencedAttributesGrid.getData();
+
+                    // begin the update
+                    referencedAttributesData.beginUpdate();
+
+                    $.each(combinedReferencedAttributes, function (name, 
value) {
+                        referencedAttributesData.addItem({
+                            id: name,
+                            name: name,
+                            value: value
+                        });
+                    });
+
+                    // complete the update
+                    referencedAttributesData.endUpdate();
+                    referencedAttributesData.reSort();
+
+                    // update the button model for this verification
+                    updateReferencedAttributesButtonModel(id, componentUrl, 
proposedProperties, handleVerificationResults, verificationResultsContainer);
+
+                    // show the dialog
+                    $('#referenced-attributes-dialog').modal('show');
+                }
+            }).fail(nfErrorHandler.handleAjaxError);
+        }
+    };
+}));

Reply via email to