You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2015/01/08 17:19:53 UTC

incubator-nifi git commit: NIFI-115: - Code clean up. - Showing an expiration icon on the connection label when appropriate. - Allowing the user to search for connections that have expiration and back pressure configured.

Repository: incubator-nifi
Updated Branches:
  refs/heads/develop 22596080c -> bda9985d6


NIFI-115:
- Code clean up.
- Showing an expiration icon on the connection label when appropriate.
- Allowing the user to search for connections that have expiration and back pressure configured.

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

Branch: refs/heads/develop
Commit: bda9985d6a3f7ecb2f20829eb2f084a8bfdd56fe
Parents: 2259608
Author: Matt Gilman <ma...@gmail.com>
Authored: Thu Jan 8 11:02:25 2015 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Thu Jan 8 11:02:25 2015 -0500

----------------------------------------------------------------------
 .../nifi/web/controller/ControllerFacade.java   |  23 ++
 .../canvas/connection-configuration.jsp         |   2 +-
 .../WEB-INF/partials/connection-details.jsp     |   2 +-
 .../src/main/webapp/js/nf/canvas/nf-canvas.js   | 275 +++++++++++--------
 .../main/webapp/js/nf/canvas/nf-connection.js   | 107 ++++++--
 5 files changed, 269 insertions(+), 140 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/bda9985d/nar-bundles/framework-bundle/framework/web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nar-bundles/framework-bundle/framework/web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 008295c..dbc4b3c 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -113,6 +113,7 @@ import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.admin.service.UserService;
 import org.apache.nifi.authorization.DownloadAuthorization;
+import org.apache.nifi.processor.DataUnit;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.access.AccessDeniedException;
@@ -1254,6 +1255,28 @@ public class ControllerFacade implements ControllerServiceProvider {
         for (final FlowFilePrioritizer comparator : queue.getPriorities()) {
             addIfAppropriate(searchStr, comparator.getClass().getName(), "Prioritizer", matches);
         }
+        
+        // search expiration
+        if (StringUtils.containsIgnoreCase("expires", searchStr) || StringUtils.containsIgnoreCase("expiration", searchStr)) {
+            final int expirationMillis = connection.getFlowFileQueue().getFlowFileExpiration(TimeUnit.MILLISECONDS);
+            if (expirationMillis > 0) {
+                matches.add("FlowFile expiration: " + connection.getFlowFileQueue().getFlowFileExpiration());
+            }
+        }
+        
+        // search back pressure
+        if (StringUtils.containsIgnoreCase("back pressure", searchStr) || StringUtils.containsIgnoreCase("pressure", searchStr)) {
+            final String backPressureDataSize = connection.getFlowFileQueue().getBackPressureDataSizeThreshold();
+            final Double backPressureBytes = DataUnit.parseDataSize(backPressureDataSize, DataUnit.B);
+            if (backPressureBytes > 0) {
+                matches.add("Back pressure data size: " + backPressureDataSize);
+            }
+
+            final long backPressureCount = connection.getFlowFileQueue().getBackPressureObjectThreshold();
+            if (backPressureCount > 0) {
+                matches.add("Back pressure count: " + backPressureCount);
+            }
+        }
 
         // search the source
         final Connectable source = connection.getSource();

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/bda9985d/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp
index eaea170..045b9e9 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/connection-configuration.jsp
@@ -35,7 +35,7 @@
                     </div>
                     <div class="setting">
                         <div class="setting-name">
-                            File expiration
+                            FlowFile expiration
                             <img class="setting-icon icon-info" src="images/iconInfo.png" alt="Info" title="The maximum amount of time an object may be in the flow before it will be automatically aged out of the flow."/>
                         </div>
                         <div class="setting-field">

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/bda9985d/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp
index 5e39020..d985de1 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/WEB-INF/partials/connection-details.jsp
@@ -75,7 +75,7 @@
                     </div>
                     <div class="setting">
                         <div class="setting-name">
-                            File expiration
+                            FlowFile expiration
                             <img class="setting-icon icon-info" src="images/iconInfo.png" alt="Info" title="The maximum amount of time an object may be in the flow before it will be automatically aged out of the flow."/>
                         </div>
                         <div class="setting-field">

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/bda9985d/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index eb16ad9..42c7f45 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -311,6 +311,28 @@ nf.Canvas = (function () {
                     'offset': '100%',
                     'stop-color': '#ffffff'
                 });
+                
+        // define the gradient for the expiration icon
+        var expirationBackground = defs.append('linearGradient')
+                .attr({
+                    'id': 'expiration',
+                    'x1': '0%',
+                    'y1': '0%',
+                    'x2': '0%',
+                    'y2': '100%'
+                });
+                
+        expirationBackground.append('stop')
+                .attr({
+                    'offset': '0%',
+                    'stop-color': '#aeafb1'
+                });
+
+        expirationBackground.append('stop')
+                .attr({
+                    'offset': '100%',
+                    'stop-color': '#87888a'
+                });
 
         // create the canvas element
         canvas = svg.append('g')
@@ -322,135 +344,135 @@ nf.Canvas = (function () {
 
         // handle canvas events
         svg.on('mousedown.selection', function () {
-                    canvasClicked = true;
+            canvasClicked = true;
 
-                    if (d3.event.button !== 0) {
-                        // prevent further propagation (to parents and others handlers 
-                        // on the same element to prevent zoom behavior)
-                        d3.event.stopImmediatePropagation();
-                        return;
+            if (d3.event.button !== 0) {
+                // prevent further propagation (to parents and others handlers 
+                // on the same element to prevent zoom behavior)
+                d3.event.stopImmediatePropagation();
+                return;
+            }
+
+            // show selection box if shift is held down
+            if (d3.event.shiftKey) {
+                var position = d3.mouse(canvas.node());
+                canvas.append('rect')
+                        .attr('rx', 6)
+                        .attr('ry', 6)
+                        .attr('x', position[0])
+                        .attr('y', position[1])
+                        .attr('class', 'selection')
+                        .attr('width', 0)
+                        .attr('height', 0)
+                        .attr('stroke-width', function () {
+                            return 1 / nf.Canvas.View.scale();
+                        })
+                        .attr('stroke-dasharray', function () {
+                            return 4 / nf.Canvas.View.scale();
+                        })
+                        .datum(position);
+
+                // prevent further propagation (to parents)
+                d3.event.stopPropagation();
+            }
+        })
+        .on('mousemove.selection', function () {
+            // update selection box if shift is held down
+            if (d3.event.shiftKey) {
+                // get the selection box
+                var selectionBox = d3.select('rect.selection');
+                if (!selectionBox.empty()) {
+                    // get the original position
+                    var originalPosition = selectionBox.datum();
+                    var position = d3.mouse(canvas.node());
+
+                    var d = {};
+                    if (originalPosition[0] < position[0]) {
+                        d.x = originalPosition[0];
+                        d.width = position[0] - originalPosition[0];
+                    } else {
+                        d.x = position[0];
+                        d.width = originalPosition[0] - position[0];
                     }
 
-                    // show selection box if shift is held down
-                    if (d3.event.shiftKey) {
-                        var position = d3.mouse(canvas.node());
-                        canvas.append('rect')
-                                .attr('rx', 6)
-                                .attr('ry', 6)
-                                .attr('x', position[0])
-                                .attr('y', position[1])
-                                .attr('class', 'selection')
-                                .attr('width', 0)
-                                .attr('height', 0)
-                                .attr('stroke-width', function () {
-                                    return 1 / nf.Canvas.View.scale();
-                                })
-                                .attr('stroke-dasharray', function () {
-                                    return 4 / nf.Canvas.View.scale();
-                                })
-                                .datum(position);
-
-                        // prevent further propagation (to parents)
-                        d3.event.stopPropagation();
+                    if (originalPosition[1] < position[1]) {
+                        d.y = originalPosition[1];
+                        d.height = position[1] - originalPosition[1];
+                    } else {
+                        d.y = position[1];
+                        d.height = originalPosition[1] - position[1];
                     }
-                })
-                .on('mousemove.selection', function () {
-                    // update selection box if shift is held down
-                    if (d3.event.shiftKey) {
-                        // get the selection box
-                        var selectionBox = d3.select('rect.selection');
-                        if (!selectionBox.empty()) {
-                            // get the original position
-                            var originalPosition = selectionBox.datum();
-                            var position = d3.mouse(canvas.node());
-
-                            var d = {};
-                            if (originalPosition[0] < position[0]) {
-                                d.x = originalPosition[0];
-                                d.width = position[0] - originalPosition[0];
-                            } else {
-                                d.x = position[0];
-                                d.width = originalPosition[0] - position[0];
-                            }
 
-                            if (originalPosition[1] < position[1]) {
-                                d.y = originalPosition[1];
-                                d.height = position[1] - originalPosition[1];
-                            } else {
-                                d.y = position[1];
-                                d.height = originalPosition[1] - position[1];
-                            }
+                    // update the selection box
+                    selectionBox.attr(d);
+                }
 
-                            // update the selection box
-                            selectionBox.attr(d);
-                        }
+                d3.event.stopPropagation();
+            }
+        })
+        .on('mouseup.selection', function () {
+            // ensure this originated from clicking the canvas, not a component.
+            // when clicking on a component, the event propagation is stopped so
+            // it never reaches the canvas. we cannot do this however on up events
+            // since the drag events break down
+            if (canvasClicked === false) {
+                return;
+            }
 
-                        d3.event.stopPropagation();
-                    }
-                })
-                .on('mouseup.selection', function () {
-                    // ensure this originated from clicking the canvas, not a component.
-                    // when clicking on a component, the event propagation is stopped so
-                    // it never reaches the canvas. we cannot do this however on up events
-                    // since the drag events break down
-                    if (canvasClicked === false) {
-                        return;
-                    }
+            // reset the canvas click flag
+            canvasClicked = false;
 
-                    // reset the canvas click flag
-                    canvasClicked = false;
+            // hide the context menu if necessary
+            nf.ContextMenu.hide();
 
-                    // hide the context menu if necessary
-                    nf.ContextMenu.hide();
-
-                    // get the selection box 
-                    var selectionBox = d3.select('rect.selection');
-                    if (!selectionBox.empty()) {
-                        var selectionBoundingBox = {
-                            x: parseInt(selectionBox.attr('x'), 10),
-                            y: parseInt(selectionBox.attr('y'), 10),
-                            width: parseInt(selectionBox.attr('width'), 10),
-                            height: parseInt(selectionBox.attr('height'), 10)
-                        };
+            // get the selection box 
+            var selectionBox = d3.select('rect.selection');
+            if (!selectionBox.empty()) {
+                var selectionBoundingBox = {
+                    x: parseInt(selectionBox.attr('x'), 10),
+                    y: parseInt(selectionBox.attr('y'), 10),
+                    width: parseInt(selectionBox.attr('width'), 10),
+                    height: parseInt(selectionBox.attr('height'), 10)
+                };
 
-                        // see if a component should be selected or not
-                        d3.selectAll('g.component').classed('selected', function (d) {
-                            // consider it selected if its already selected or enclosed in the bounding box
-                            return d3.select(this).classed('selected') ||
-                                    d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
-                                    d.component.position.y >= selectionBoundingBox.y && (d.component.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height);
-                        });
-
-                        // see if a connection should be selected or not
-                        d3.selectAll('g.connection').classed('selected', function (d) {
-                            // consider all points
-                            var points = [d.start].concat(d.bends, [d.end]);
-
-                            // determine the bounding box
-                            var x = d3.extent(points, function (pt) {
-                                return pt.x;
-                            });
-                            var y = d3.extent(points, function (pt) {
-                                return pt.y;
-                            });
+                // see if a component should be selected or not
+                d3.selectAll('g.component').classed('selected', function (d) {
+                    // consider it selected if its already selected or enclosed in the bounding box
+                    return d3.select(this).classed('selected') ||
+                            d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
+                            d.component.position.y >= selectionBoundingBox.y && (d.component.position.y + d.dimensions.height) <= (selectionBoundingBox.y + selectionBoundingBox.height);
+                });
 
-                            // consider it selected if its already selected or enclosed in the bounding box
-                            return d3.select(this).classed('selected') ||
-                                    x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
-                                    y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height);
-                        });
-
-                        // remove the selection box
-                        selectionBox.remove();
-                    } else if (panning === false) {
-                        // deselect as necessary if we are not panning
-                        nf.CanvasUtils.getSelection().classed('selected', false);
-                    }
+                // see if a connection should be selected or not
+                d3.selectAll('g.connection').classed('selected', function (d) {
+                    // consider all points
+                    var points = [d.start].concat(d.bends, [d.end]);
 
-                    // update the toolbar
-                    nf.CanvasToolbar.refresh();
+                    // determine the bounding box
+                    var x = d3.extent(points, function (pt) {
+                        return pt.x;
+                    });
+                    var y = d3.extent(points, function (pt) {
+                        return pt.y;
+                    });
+
+                    // consider it selected if its already selected or enclosed in the bounding box
+                    return d3.select(this).classed('selected') ||
+                            x[0] >= selectionBoundingBox.x && x[1] <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
+                            y[0] >= selectionBoundingBox.y && y[1] <= (selectionBoundingBox.y + selectionBoundingBox.height);
                 });
 
+                // remove the selection box
+                selectionBox.remove();
+            } else if (panning === false) {
+                // deselect as necessary if we are not panning
+                nf.CanvasUtils.getSelection().classed('selected', false);
+            }
+
+            // update the toolbar
+            nf.CanvasToolbar.refresh();
+        });
+
         // define a function for update the graph dimensions
         var updateGraphSize = function () {
             // get the location of the bottom of the graph
@@ -802,17 +824,21 @@ nf.Canvas = (function () {
     };
 
     return {
+        
         CANVAS_OFFSET: 0,
+        
         /**
          * Determines if the current broswer supports SVG.
          */
         SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect,
+        
         /**
          * Hides the splash that is displayed while the application is loading.
          */
         hideSplash: function () {
             $('#splash').fadeOut();
         },
+        
         /**
          * Stop polling for revision.
          */
@@ -820,6 +846,7 @@ nf.Canvas = (function () {
             // set polling flag
             revisionPolling = false;
         },
+        
         /**
          * Remove the status poller.
          */
@@ -827,6 +854,7 @@ nf.Canvas = (function () {
             // set polling flag
             statusPolling = false;
         },
+        
         /**
          * Reloads the flow from the server based on the currently specified group id.
          * To load another group, update nf.Canvas.setGroupId and call nf.Canvas.reload.
@@ -865,6 +893,7 @@ nf.Canvas = (function () {
                 });
             }).promise();
         },
+        
         /**
          * Reloads the status.
          */
@@ -878,6 +907,7 @@ nf.Canvas = (function () {
                 });
             }).promise();
         },
+        
         /**
          * Initialize NiFi.
          */
@@ -999,6 +1029,7 @@ nf.Canvas = (function () {
                 }).fail(nf.Common.handleAjaxError);
             }).fail(nf.Common.handleAjaxError);
         },
+        
         /**
          * Defines the gradient colors used to render processors.
          * 
@@ -1007,6 +1038,7 @@ nf.Canvas = (function () {
         defineProcessorColors: function (colors) {
             setColors(colors, 'processor');
         },
+        
         /**
          * Defines the gradient colors used to render label.
          * 
@@ -1015,6 +1047,7 @@ nf.Canvas = (function () {
         defineLabelColors: function (colors) {
             setColors(colors, 'label');
         },
+        
         /**
          * Return whether this instance of NiFi is clustered.
          * 
@@ -1023,12 +1056,14 @@ nf.Canvas = (function () {
         isClustered: function () {
             return clustered === true;
         },
+        
         /**
          * Returns whether site to site communications is secure.
          */
         isSecureSiteToSite: function () {
             return secureSiteToSite;
         },
+        
         /**
          * Set the group id.
          * 
@@ -1043,6 +1078,7 @@ nf.Canvas = (function () {
         getGroupId: function () {
             return groupId;
         },
+        
         /**
          * Set the group name.
          * 
@@ -1051,12 +1087,14 @@ nf.Canvas = (function () {
         setGroupName: function (gn) {
             groupName = gn;
         },
+        
         /**
          * Get the group name.
          */
         getGroupName: function () {
             return groupName;
         },
+        
         /**
          * Set the parent group id.
          * 
@@ -1065,13 +1103,16 @@ nf.Canvas = (function () {
         setParentGroupId: function (pgi) {
             parentGroupId = pgi;
         },
+        
         /**
          * Get the parent group id.
          */
         getParentGroupId: function () {
             return parentGroupId;
         },
+        
         View: (function () {
+            
             /**
              * Updates component visibility based on their proximity to the screen's viewport.
              */
@@ -1138,8 +1179,8 @@ nf.Canvas = (function () {
                             .classed('entering', function () {
                                 return visible && !wasVisible;
                             }).classed('leaving', function () {
-                        return !visible && wasVisible;
-                    });
+                                return !visible && wasVisible;
+                            });
                 };
 
                 // get the all components

http://git-wip-us.apache.org/repos/asf/incubator-nifi/blob/bda9985d/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
----------------------------------------------------------------------
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
index 0b0c40a..8da9f6a 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-connection.js
@@ -186,6 +186,24 @@ nf.Connection = (function () {
     var isGroup = function (terminal) {
         return terminal.groupId !== nf.Canvas.getGroupId() && (isInputPortType(terminal.type) || isOutputPortType(terminal.type));
     };
+    
+    /**
+     * Determines whether expiration is configured for the specified connection.
+     * 
+     * @param {object} connection 
+     * @return {boolean} Whether expiration is configured
+     */
+    var isExpirationConfigured = function (connection) {
+        if (nf.Common.isDefinedAndNotNull(connection.flowFileExpiration)) {
+            var match = connection.flowFileExpiration.match(/^(\d+).*/);
+            if (match !== null && match.length > 0) {
+                if (parseInt(match[0], 10) > 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    };
 
     /**
      * Sorts the specified connections according to the z index.
@@ -325,16 +343,16 @@ nf.Connection = (function () {
 
                 return grouped;
             })
-                    .classed('ghost', function (d) {
-                        var ghost = false;
+            .classed('ghost', function (d) {
+                var ghost = false;
 
-                        // if the connection has a relationship that is unavailable, mark it a ghost relationship
-                        if (hasUnavailableRelationship(d)) {
-                            ghost = true;
-                        }
+                // if the connection has a relationship that is unavailable, mark it a ghost relationship
+                if (hasUnavailableRelationship(d)) {
+                    ghost = true;
+                }
 
-                        return ghost;
-                    });
+                return ghost;
+            });
         }
 
         updated.each(function (d) {
@@ -669,7 +687,7 @@ nf.Connection = (function () {
 
                         // update the label text
                         connectionFrom.select('text.connection-from')
-                                .each(function (d) {
+                                .each(function () {
                                     var connectionFromLabel = d3.select(this);
 
                                     // reset the label name to handle any previous state
@@ -677,9 +695,9 @@ nf.Connection = (function () {
 
                                     // apply ellipsis to the label as necessary
                                     nf.CanvasUtils.ellipsis(connectionFromLabel, d.component.source.name);
-                                }).append('title').text(function (d) {
-                            return d.component.source.name;
-                        });
+                                }).append('title').text(function () {
+                                    return d.component.source.name;
+                                });
 
                         // update the label run status
                         connectionFrom.select('image.connection-from-run-status').attr('xlink:href', function () {
@@ -758,8 +776,8 @@ nf.Connection = (function () {
                                     // apply ellipsis to the label as necessary
                                     nf.CanvasUtils.ellipsis(connectionToLabel, d.component.destination.name);
                                 }).append('title').text(function (d) {
-                            return d.component.destination.name;
-                        });
+                                    return d.component.destination.name;
+                                });
 
                         // update the label run status
                         connectionTo.select('image.connection-to-run-status').attr('xlink:href', function () {
@@ -821,7 +839,7 @@ nf.Connection = (function () {
 
                         // update the connection name
                         connectionName.select('text.connection-name')
-                                .each(function (d) {
+                                .each(function () {
                                     var connectionToLabel = d3.select(this);
 
                                     // reset the label name to handle any previous state
@@ -829,9 +847,9 @@ nf.Connection = (function () {
 
                                     // apply ellipsis to the label as necessary
                                     nf.CanvasUtils.ellipsis(connectionToLabel, connectionNameValue);
-                                }).append('title').text(function (d) {
-                            return connectionNameValue;
-                        });
+                                }).append('title').text(function () {
+                                    return connectionNameValue;
+                                });
                     } else {
                         // there is no connection name, but check if the name was previous
                         // rendered so it can be removed
@@ -861,11 +879,49 @@ nf.Connection = (function () {
                                 .text('Queued');
 
                         queued.append('text')
+                            .attr({
+                                'class': 'connection-stats-value queued',
+                                'x': 46,
+                                'y': 10
+                            });
+                                
+                        var expiration = queued.append('g')
                                 .attr({
-                                    'class': 'connection-stats-value queued',
-                                    'x': 46,
-                                    'y': 10
+                                    'class': 'expiration-icon',
+                                    'transform': 'translate(167, 2)'
                                 });
+                                
+                        expiration.append('circle')
+                                .attr({
+                                    'cx': 5,
+                                    'cy': 5,
+                                    'r': 4.75,
+                                    'stroke-width': 0.5,
+                                    'stroke': '#87888a',
+                                    'fill': 'url(#expiration)'
+                                });
+                                
+                        expiration.append('line')
+                                .attr({
+                                    'x1': 6,
+                                    'y1': 5,
+                                    'x2': 3,
+                                    'y2': 4,
+                                    'stroke': '#fff',
+                                    'stroke-width': 1
+                                });
+                                
+                        expiration.append('line')
+                                .attr({
+                                    'x1': 6,
+                                    'y1': 5,
+                                    'x2': 3,
+                                    'y2': 7,
+                                    'stroke': '#fff',
+                                    'stroke-width': 1
+                                });
+                                
+                        expiration.append('title');
                     }
 
                     // update the queued vertical positioning as necessary
@@ -879,6 +935,15 @@ nf.Connection = (function () {
                             .attr('height', function () {
                                 return 5 + (15 * labelCount) + 3;
                             });
+                            
+                    // determine whether or not to show the expiration icon
+                    connectionLabelContainer.select('g.expiration-icon')
+                            .classed('hidden', function () {
+                                return !isExpirationConfigured(d.component);
+                            })
+                            .select('title').text(function () {
+                                return 'Expires FlowFiles older than ' + d.component.flowFileExpiration;
+                            });
 
                     if (nf.Common.isDFM()) {
                         // only support dragging the label when appropriate