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 eb16ad90cd..42c7f454a8 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();
- }
- })
- .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);
- }
-
- 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;
+ 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];
}
- // reset the canvas click flag
- canvasClicked = false;
+ // update the selection box
+ selectionBox.attr(d);
+ }
- // hide the context menu if necessary
- nf.ContextMenu.hide();
+ 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;
+ }
- // 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)
- };
+ // reset the canvas click flag
+ canvasClicked = false;
- // 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);
- });
+ // hide the context menu if necessary
+ nf.ContextMenu.hide();
- // 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]);
+ // 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)
+ };
- // 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();
+ // 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;
+ });
+
+ // 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
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 0b0c40a18d..8da9f6a4cd 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
diff --git a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-go-to.js b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-go-to.js
index ddc501a7f7..18812c4aee 100644
--- a/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-go-to.js
+++ b/nar-bundles/framework-bundle/framework/web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-go-to.js
@@ -489,7 +489,7 @@ nf.GoTo = (function () {
}
// show the dialog
- $('#connections-dialog').modal('setHeaderText', 'Downstream Connections').modal('show');
+ $('#connections-dialog').modal('setHeaderText', 'Upstream Connections').modal('show');
}).fail(nf.Common.handleAjaxError);
},
diff --git a/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/UntrustedProxyException.java b/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/UntrustedProxyException.java
index 80d42c850c..1a993e891c 100644
--- a/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/UntrustedProxyException.java
+++ b/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/UntrustedProxyException.java
@@ -23,10 +23,6 @@ import org.springframework.security.core.AuthenticationException;
*/
public class UntrustedProxyException extends AuthenticationException {
- public UntrustedProxyException(String msg, Object extraInformation) {
- super(msg, extraInformation);
- }
-
public UntrustedProxyException(String msg) {
super(msg);
}
diff --git a/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java b/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java
index 81eb89bbe9..8d2c318670 100644
--- a/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java
+++ b/nar-bundles/framework-bundle/framework/web/web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java
@@ -302,7 +302,7 @@ public class OcspCertificateValidator {
final ClientResponse response = resource.header(CONTENT_TYPE_HEADER, OCSP_REQUEST_CONTENT_TYPE).post(ClientResponse.class, ocspRequest.getEncoded());
// ensure the request was completed successfully
- if (!ClientResponse.Status.OK.equals(response.getClientResponseStatus())) {
+ if (ClientResponse.Status.OK.getStatusCode() != response.getStatusInfo().getStatusCode()) {
logger.warn(String.format("OCSP request was unsuccessful (%s).", response.getStatus()));
return ocspStatus;
}
diff --git a/nar-bundles/hadoop-bundle/hdfs-processors/src/test/java/org/apache/nifi/processors/hadoop/GetHDFSTest.java b/nar-bundles/hadoop-bundle/hdfs-processors/src/test/java/org/apache/nifi/processors/hadoop/GetHDFSTest.java
index 4b495f43b7..d6015e0d15 100644
--- a/nar-bundles/hadoop-bundle/hdfs-processors/src/test/java/org/apache/nifi/processors/hadoop/GetHDFSTest.java
+++ b/nar-bundles/hadoop-bundle/hdfs-processors/src/test/java/org/apache/nifi/processors/hadoop/GetHDFSTest.java
@@ -16,8 +16,6 @@
*/
package org.apache.nifi.processors.hadoop;
-import org.apache.nifi.processors.hadoop.GetHDFS;
-import org.apache.nifi.processors.hadoop.PutHDFS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -25,7 +23,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
-import junit.framework.Assert;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.processor.ProcessContext;
@@ -35,6 +32,7 @@ import org.apache.nifi.util.TestRunner;
import org.apache.nifi.util.TestRunners;
import org.apache.hadoop.fs.Path;
+import org.junit.Assert;
import org.junit.Test;
public class GetHDFSTest {
diff --git a/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/NewestFirstPrioritizerTest.java b/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/NewestFirstPrioritizerTest.java
index 934c5afae7..1b7573477a 100644
--- a/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/NewestFirstPrioritizerTest.java
+++ b/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/NewestFirstPrioritizerTest.java
@@ -16,10 +16,8 @@
*/
package org.apache.nifi.prioritizer;
-import org.apache.nifi.prioritizer.NewestFlowFileFirstPrioritizer;
import java.util.concurrent.atomic.AtomicLong;
-import junit.framework.Assert;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
@@ -28,6 +26,7 @@ import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.MockProcessSession;
import org.apache.nifi.util.SharedSessionState;
+import org.junit.Assert;
import org.junit.Test;
diff --git a/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/OldestFirstPrioritizerTest.java b/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/OldestFirstPrioritizerTest.java
index 2d581dd495..e46711dfec 100644
--- a/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/OldestFirstPrioritizerTest.java
+++ b/nar-bundles/standard-bundle/standard-prioritizers/src/test/java/org/apache/nifi/prioritizer/OldestFirstPrioritizerTest.java
@@ -16,10 +16,8 @@
*/
package org.apache.nifi.prioritizer;
-import org.apache.nifi.prioritizer.OldestFlowFileFirstPrioritizer;
import java.util.concurrent.atomic.AtomicLong;
-import junit.framework.Assert;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
@@ -28,6 +26,7 @@ import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.util.MockFlowFile;
import org.apache.nifi.util.MockProcessSession;
import org.apache.nifi.util.SharedSessionState;
+import org.junit.Assert;
import org.junit.Test;
diff --git a/nifi-docs/src/main/asciidoc/images/iconFlowHistory.png b/nifi-docs/src/main/asciidoc/images/iconFlowHistory.png
new file mode 100644
index 0000000000..4ac29c0dca
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/iconFlowHistory.png differ
diff --git a/nifi-docs/src/main/asciidoc/images/iconSettings.png b/nifi-docs/src/main/asciidoc/images/iconSettings.png
new file mode 100644
index 0000000000..8fdaffc601
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/iconSettings.png differ
diff --git a/nifi-docs/src/main/asciidoc/images/iconUsers.png b/nifi-docs/src/main/asciidoc/images/iconUsers.png
new file mode 100644
index 0000000000..fc99a12aa3
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/iconUsers.png differ
diff --git a/nifi-docs/src/main/asciidoc/images/simple-flow.png b/nifi-docs/src/main/asciidoc/images/simple-flow.png
new file mode 100644
index 0000000000..85203a83e6
Binary files /dev/null and b/nifi-docs/src/main/asciidoc/images/simple-flow.png differ
diff --git a/nifi-docs/src/main/asciidoc/user-guide.adoc b/nifi-docs/src/main/asciidoc/user-guide.adoc
index 8e79bac029..9208153fb7 100644
--- a/nifi-docs/src/main/asciidoc/user-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/user-guide.adoc
@@ -20,8 +20,8 @@ Apache NiFi Team
:homepage: http://nifi.incubator.apache.org
-Overview
---------
+Introduction
+------------
Apache NiFi (Incubating) is a dataflow system based on the concepts of flow-based programming. It supports
powerful and scalable directed graphs of data routing, transformation, and system mediation logic. NiFi has
a web-based user interface for design, control, feedback, and monitoring of dataflows. It is highly configurable
@@ -144,15 +144,14 @@ image::status-bar.png["NiFi Status Bar"]
Building a DataFlow
-------------------
-A DataFlow Manager (DFM) is able to build an automated dataflow using the NiFi User Interface (UI). This is accomplished
-by dragging components from the toolbar to the canvas, configuring the components to meet specific needs, and connecting
+A DataFlow Manager (DFM) is able to build an automated dataflow using the NiFi User Interface (UI). Simply drag components from the toolbar to the canvas, configure the components to meet specific needs, and connect
the components together.
=== Adding Components to the Canvas
-In the User Interface section above, we outlined the different segments of the UI and pointed out a Components Toolbar.
-Here, we will look at each of the Components in that toolbar:
+In the User Interface section above outlined the different segments of the UI and pointed out a Components Toolbar.
+This section looks at each of the Components in that toolbar:
image::components.png["Components"]
@@ -176,13 +175,13 @@ image::add-processor-with-tag-cloud.png["Add Processor with Tag Cloud"]
Clicking the `Add` button or double-clicking on a Processor Type will add the selected Processor to the canvas at the
location that it was dropped.
-
+*Note*: For any component added to the graph, it is possible to select it with the mouse and move it anywhere on the graph. Also, it is possible to select multiple items at once by either holding down the Shift key and selecting each item or by holding down the Shift key and dragging a selection box around the desired components.
image:iconInputPort.png["Input Port", width=32]
*Input Port*: Input Ports provide a mechanism for transferring data into a Process Group. When an Input Port is dragged
onto the canvas, the DFM is prompted to name the Port. All Ports within a Process Group must have unique names.
-All components exist only within a Process Group. When a user navigates to the NiFi page, the user is placed in the
+All components exist only within a Process Group. When a user initially navigates to the NiFi page, the user is placed in the
Root Process Group. If the Input Port is dragged onto the Root Process Group, the Input Port provides a mechanism
to receive data from remote instances of NiFi. In this case, the Input Port can be configured to restrict access to
appropriate users.
@@ -204,7 +203,7 @@ that data is removed from the queues of the incoming Connections.
image:iconProcessGroup.png["Process Group", width=32]
*Process Group*: Process Groups can be used to logically group a set of components so that the dataflow is easier to understand
and maintain. When a Process Group is dragged onto the canvas, the DFM is prompted to name the Process Group. All Process
-Groups within the same parent group must have unique names.
+Groups within the same parent group must have unique names. The Process Group will then be nested within that parent group.
@@ -322,7 +321,7 @@ The first configuration option is the Scheduling Strategy. There are three optio
- *Timer driven*: This is the default mode. The Processor will be scheduled to run on a regular interval. The interval
at which the Processor is run is defined by the `Run schedule' option (see below).
- *Event driven*: When this mode is selected, the Processor will be triggered to run by an event, and that event occurs when FlowFiles enter Connections
- that have this Processor as their destination. This mode is not supported by all Processors. When this mode is
+ feeding this Processor. This mode is currently considered experimental and is not supported by all Processors. When this mode is
selected, the `Run schedule' option is not configurable, as the Processor is not triggered to run periodically but
as the result of an event. Additionally, this is the only mode for which the `Concurrent tasks'
option can be set to 0. In this case, the number of threads is limited only by the size of the Event-Driven Thread Pool that
@@ -408,7 +407,7 @@ Note that after a User-Defined property has been added, an icon will appear on t
image:iconDelete.png["Delete Icon"]
). Clicking this button will remove the User-Defined property from the Processor.
-
+Some processors also have an Advanced User Interface (UI) built into them. For example, the UpdateAttribute processor has an Advanced UI. To access the Advanced UI, click the `Advanced` button that appears at the bottom of the Configure Processor window. Only processors that have an Advanced UI will have this button.
==== Comments Tab
@@ -428,7 +427,7 @@ for all the Processors that are available. Clicking on the desired Processor in
=== Connecting Components
-Once processors have been added to the graph and configured, the next step is to connect them
+Once processors and other components have been added to the graph and configured, the next step is to connect them
to one another so that NiFi knows what to do with each FlowFile after it has been processed. This is accomplished by creating a
Connection between each component. When the user hovers the mouse over the center of a component, a new Connection icon (
image:addConnect.png["Connection Bubble"]
@@ -437,11 +436,14 @@ image:addConnect.png["Connection Bubble"]
image:processor-connection-bubble.png["Processor with Connection Bubble"]
The user drags the Connection bubble from one component to another until the second component is highlighted. When the user
-releases the mouse, a `Create Connection' dialog appears. This dialog consists of two tabs: `Details' and `Settings'.
+releases the mouse, a `Create Connection' dialog appears. This dialog consists of two tabs: `Details' and `Settings'. They are
+discussed in detail below. Note that it is possible to draw a connection so that it loops back on the same processor. This can be
+useful if the DFM wants the processor to try to re-process FlowFiles if they go down a failure Relationship. To create this type of looping
+connection, simply drag the connection bubble away and then back to the same processor until it is highlighted. Then release the mouse and the same 'Create Connection' dialog appears.
==== Details Tab
-The Details Tab provides information about the source and destination components, including the component name, the
+The Details Tab of the 'Create Connection' dialog provides information about the source and destination components, including the component name, the
component type, and the Process Group in which the component lives:
image::create-connection.png["Create Connection"]
@@ -485,7 +487,14 @@ priority. If two FlowFiles have the same value according to this prioritizer, th
FlowFile to process first, and so on. If a prioritizer is no longer desired, it can then be dragged from the `Selected
prioritizers' list to the `Available prioritizers' list.
+The following prioritizers are available:
+- *FirstInFirstOutPrioritizer*: Given two FlowFiles, the on that reached the connection first will be processed first.
+- *NewestFlowFileFirstPrioritizer*: Given two FlowFiles, the one that is newest in the dataflow will be processed first.
+- *OldestFlowFileFirstPrioritizer*: Given two FlowFiles, the on that is oldest in the dataflow will be processed first. This is the default scheme that is used if no prioritizers are selected.
+- *PriorityAttributePrioritizer*: Given two FlowFiles that both have a "priority" attribute, the one that has the highest priority value will be prprocessed first. Note that an UpdateAttribute processor should be used to add the "priority" attribute to the FlowFiles before they reach a connection that has this prioritizer set. Values for the "priority" attribute may be alphanumeric, where "a" is a higher priority than "z", and "1" is a higher priority than "9", for example.
+
+*Note*: After a connection has been drawn between two components, the connection's configuration may be changed, and the connection may be moved to a new destination; however, the processors on either side of the connection must be stopped before a configuration or destination change may be made.
=== Processor Validation
@@ -501,6 +510,28 @@ to a Stop icon, indicating that the Processor is valid and ready to be started b
image::valid-processor.png["Valid Processor"]
+=== Example Dataflow
+
+This section has described the steps required to build a dataflow. Now, to put it all together. The following example dataflow
+consists of just two processors: GenerateFlowFile and LogAttribute. These processors are normally used for testing, but they can also be used
+to build a quick flow for demonstration purposes and see NiFi in action.
+
+After you drag the GenerateFlowFile and LogAttribute processors to the graph and connect them (using the guidelines provided above), configure them as follows:
+
+* Generate FlowFile
+** On the Scheduling tab, set Run schedule to: 5 sec. Note that the GenerateFlowFile processor can create many FlowFiles very quickly; that's why setting the Run schedule is important so that this flow does not overwhelm the system NiFi is running on.
+** On the Properties tab, set File Size to: 10 kb
+
+* Log Attribute
+** On the Settings tab, under Auto-terminate relationships, select the checkbox next to Success. This will terminate FlowFiles after this processor has successfully processed them.
+** Also on the Settings tab, set the Bulletin level to Info. This way, when the dataflow is running, this processor will display the bulletin icon (see <>), and the user may hover over it with the mouse to see the attributes that the processor is logging.
+
+The dataflow should look like the following:
+
+image::simple-flow.png["Simple Flow", width=900]
+
+
+Now see the following section on how to start and stop the dataflow. When the dataflow is running, be sure to note the statistical information that is displayed on the face of each processor (see <>).
@@ -640,7 +671,11 @@ or not compression should be used when transmitting data to or from this Port.
+[[navigating]]
+== Navigating within a DataFlow
+NiFi provides various mechanisms for getting around a dataflow. The <> section discussed various ways to navigate around
+the NiFi graph; however, once a flow exists on the graph, there are additional ways to get from one component to another. The <> section showed that when multiple Process Groups exist in a flow, breadcrumbs appear under the toolbar, providing a way to navigate between them. In addition, to enter a Process Group that is currently visible on the graph, simply double-click it, thereby "drilling down" into it. Connections also provide a way to jump from one location to another within the flow. Right-click on a connection and select "Go to source" or "Go to destination" in order to jump to one end of the connection or another. This can be very useful in large, complex dataflows, where the connection lines may be long and span large areas of the graph. Finally, all components provide the ability to jump forward or backward within the flow. Right-click any component (e.g., a processor, process group, port, etc.) and select either "Upstream connections" or "Downstream connections". A dialog window will open, showing the available upstream or downstream connections that the user may jump to. This can be especially useful when trying to follow a dataflow in a backward direction. It is typically easy to follow the path of a dataflow from start to finish, drilling down into nested process groups; however, it can be more difficult to follow the dataflow in the other direction.
@@ -1043,10 +1078,7 @@ Min/Max/Mean values on the left-hand side to be recalculated. Once a selection h
rectangle over the graph, double-clicking on the selected portion will cause the selection to fully expand in the
vertical direction. I.e., it will select all values in this time range. Clicking on the bottom graph without dragging
will remove the selection.
-
-
-
-
+
[[templates]]
@@ -1240,3 +1272,15 @@ Once "Expand" is selected, the graph is re-drawn to show the children and their
image:expanded-events.png["Expanded Events", width=300]
+[[other_management_features]]
+Other Management Features
+-------------------------
+
+In addition to the Summary Page, Data Provenance Page, Template Management Page, and Bulletin Board Page, there are other tools in the Management Toolbar (See <>) that are useful to the Dataflow Manager. The Flow Configuration History, which is available by clicking on the clock icon ( image:iconFlowHistory.png["Flow History", width=28] ) in the Management Toolbar, shows all the changes that have been made to the dataflow graph. The history can aid in troubleshooting if a recent change to the dataflow has caused a problem and needs to be fixed. While NiFi does not have an "undo" feature, the DataFlow Manager can make new changes to the dataflow that will fix the problem.
+
+Two other tools in the Management Toolbar are used primarily by Administrators. These are the Flow Settings page ( image:iconSettings.png["Flow Settings", width=28] ) and the Users page ( image:iconUsers.png["Users", width=28] ). The Flow Settings page provides the ability to change the name of the NiFi instance, add comments describing the NiFi instance, set the maximum number of threads that are available to the application, and create a back-up copy of the dataflow(s) currently on the graph. The Users page is used to manage user access, which is described in the Admin Guide.
+
+
+
+
+