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.
This commit is contained in:
Matt Gilman 2015-01-08 11:02:25 -05:00
parent 22596080c9
commit bda9985d6a
5 changed files with 271 additions and 142 deletions

View File

@ -113,6 +113,7 @@ import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.admin.service.UserService; import org.apache.nifi.admin.service.UserService;
import org.apache.nifi.authorization.DownloadAuthorization; import org.apache.nifi.authorization.DownloadAuthorization;
import org.apache.nifi.processor.DataUnit;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.AccessDeniedException;
@ -1254,6 +1255,28 @@ public class ControllerFacade implements ControllerServiceProvider {
for (final FlowFilePrioritizer comparator : queue.getPriorities()) { for (final FlowFilePrioritizer comparator : queue.getPriorities()) {
addIfAppropriate(searchStr, comparator.getClass().getName(), "Prioritizer", matches); 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 // search the source
final Connectable source = connection.getSource(); final Connectable source = connection.getSource();

View File

@ -35,7 +35,7 @@
</div> </div>
<div class="setting"> <div class="setting">
<div class="setting-name"> <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."/> <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>
<div class="setting-field"> <div class="setting-field">

View File

@ -75,7 +75,7 @@
</div> </div>
<div class="setting"> <div class="setting">
<div class="setting-name"> <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."/> <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>
<div class="setting-field"> <div class="setting-field">

View File

@ -311,6 +311,28 @@ nf.Canvas = (function () {
'offset': '100%', 'offset': '100%',
'stop-color': '#ffffff' '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 // create the canvas element
canvas = svg.append('g') canvas = svg.append('g')
@ -322,135 +344,135 @@ nf.Canvas = (function () {
// handle canvas events // handle canvas events
svg.on('mousedown.selection', function () { svg.on('mousedown.selection', function () {
canvasClicked = true; canvasClicked = true;
if (d3.event.button !== 0) { if (d3.event.button !== 0) {
// prevent further propagation (to parents and others handlers // prevent further propagation (to parents and others handlers
// on the same element to prevent zoom behavior) // on the same element to prevent zoom behavior)
d3.event.stopImmediatePropagation(); d3.event.stopImmediatePropagation();
return; 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 (originalPosition[1] < position[1]) {
if (d3.event.shiftKey) { d.y = originalPosition[1];
var position = d3.mouse(canvas.node()); d.height = position[1] - originalPosition[1];
canvas.append('rect') } else {
.attr('rx', 6) d.y = position[1];
.attr('ry', 6) d.height = originalPosition[1] - position[1];
.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;
} }
// reset the canvas click flag // update the selection box
canvasClicked = false; selectionBox.attr(d);
}
// hide the context menu if necessary d3.event.stopPropagation();
nf.ContextMenu.hide(); }
})
.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 // reset the canvas click flag
var selectionBox = d3.select('rect.selection'); canvasClicked = false;
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 // hide the context menu if necessary
d3.selectAll('g.component').classed('selected', function (d) { nf.ContextMenu.hide();
// 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 // get the selection box
d3.selectAll('g.connection').classed('selected', function (d) { var selectionBox = d3.select('rect.selection');
// consider all points if (!selectionBox.empty()) {
var points = [d.start].concat(d.bends, [d.end]); 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 // see if a component should be selected or not
var x = d3.extent(points, function (pt) { d3.selectAll('g.component').classed('selected', function (d) {
return pt.x; // consider it selected if its already selected or enclosed in the bounding box
}); return d3.select(this).classed('selected') ||
var y = d3.extent(points, function (pt) { d.component.position.x >= selectionBoundingBox.x && (d.component.position.x + d.dimensions.width) <= (selectionBoundingBox.x + selectionBoundingBox.width) &&
return pt.y; 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);
}
// update the toolbar
nf.CanvasToolbar.refresh();
}); });
// 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 // define a function for update the graph dimensions
var updateGraphSize = function () { var updateGraphSize = function () {
// get the location of the bottom of the graph // get the location of the bottom of the graph
@ -802,17 +824,21 @@ nf.Canvas = (function () {
}; };
return { return {
CANVAS_OFFSET: 0, CANVAS_OFFSET: 0,
/** /**
* Determines if the current broswer supports SVG. * Determines if the current broswer supports SVG.
*/ */
SUPPORTS_SVG: !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect, 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. * Hides the splash that is displayed while the application is loading.
*/ */
hideSplash: function () { hideSplash: function () {
$('#splash').fadeOut(); $('#splash').fadeOut();
}, },
/** /**
* Stop polling for revision. * Stop polling for revision.
*/ */
@ -820,6 +846,7 @@ nf.Canvas = (function () {
// set polling flag // set polling flag
revisionPolling = false; revisionPolling = false;
}, },
/** /**
* Remove the status poller. * Remove the status poller.
*/ */
@ -827,6 +854,7 @@ nf.Canvas = (function () {
// set polling flag // set polling flag
statusPolling = false; statusPolling = false;
}, },
/** /**
* Reloads the flow from the server based on the currently specified group id. * 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. * To load another group, update nf.Canvas.setGroupId and call nf.Canvas.reload.
@ -865,6 +893,7 @@ nf.Canvas = (function () {
}); });
}).promise(); }).promise();
}, },
/** /**
* Reloads the status. * Reloads the status.
*/ */
@ -878,6 +907,7 @@ nf.Canvas = (function () {
}); });
}).promise(); }).promise();
}, },
/** /**
* Initialize NiFi. * Initialize NiFi.
*/ */
@ -999,6 +1029,7 @@ nf.Canvas = (function () {
}).fail(nf.Common.handleAjaxError); }).fail(nf.Common.handleAjaxError);
}).fail(nf.Common.handleAjaxError); }).fail(nf.Common.handleAjaxError);
}, },
/** /**
* Defines the gradient colors used to render processors. * Defines the gradient colors used to render processors.
* *
@ -1007,6 +1038,7 @@ nf.Canvas = (function () {
defineProcessorColors: function (colors) { defineProcessorColors: function (colors) {
setColors(colors, 'processor'); setColors(colors, 'processor');
}, },
/** /**
* Defines the gradient colors used to render label. * Defines the gradient colors used to render label.
* *
@ -1015,6 +1047,7 @@ nf.Canvas = (function () {
defineLabelColors: function (colors) { defineLabelColors: function (colors) {
setColors(colors, 'label'); setColors(colors, 'label');
}, },
/** /**
* Return whether this instance of NiFi is clustered. * Return whether this instance of NiFi is clustered.
* *
@ -1023,12 +1056,14 @@ nf.Canvas = (function () {
isClustered: function () { isClustered: function () {
return clustered === true; return clustered === true;
}, },
/** /**
* Returns whether site to site communications is secure. * Returns whether site to site communications is secure.
*/ */
isSecureSiteToSite: function () { isSecureSiteToSite: function () {
return secureSiteToSite; return secureSiteToSite;
}, },
/** /**
* Set the group id. * Set the group id.
* *
@ -1043,6 +1078,7 @@ nf.Canvas = (function () {
getGroupId: function () { getGroupId: function () {
return groupId; return groupId;
}, },
/** /**
* Set the group name. * Set the group name.
* *
@ -1051,12 +1087,14 @@ nf.Canvas = (function () {
setGroupName: function (gn) { setGroupName: function (gn) {
groupName = gn; groupName = gn;
}, },
/** /**
* Get the group name. * Get the group name.
*/ */
getGroupName: function () { getGroupName: function () {
return groupName; return groupName;
}, },
/** /**
* Set the parent group id. * Set the parent group id.
* *
@ -1065,13 +1103,16 @@ nf.Canvas = (function () {
setParentGroupId: function (pgi) { setParentGroupId: function (pgi) {
parentGroupId = pgi; parentGroupId = pgi;
}, },
/** /**
* Get the parent group id. * Get the parent group id.
*/ */
getParentGroupId: function () { getParentGroupId: function () {
return parentGroupId; return parentGroupId;
}, },
View: (function () { View: (function () {
/** /**
* Updates component visibility based on their proximity to the screen's viewport. * Updates component visibility based on their proximity to the screen's viewport.
*/ */
@ -1138,8 +1179,8 @@ nf.Canvas = (function () {
.classed('entering', function () { .classed('entering', function () {
return visible && !wasVisible; return visible && !wasVisible;
}).classed('leaving', function () { }).classed('leaving', function () {
return !visible && wasVisible; return !visible && wasVisible;
}); });
}; };
// get the all components // get the all components

View File

@ -186,6 +186,24 @@ nf.Connection = (function () {
var isGroup = function (terminal) { var isGroup = function (terminal) {
return terminal.groupId !== nf.Canvas.getGroupId() && (isInputPortType(terminal.type) || isOutputPortType(terminal.type)); 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. * Sorts the specified connections according to the z index.
@ -325,16 +343,16 @@ nf.Connection = (function () {
return grouped; return grouped;
}) })
.classed('ghost', function (d) { .classed('ghost', function (d) {
var ghost = false; var ghost = false;
// if the connection has a relationship that is unavailable, mark it a ghost relationship // if the connection has a relationship that is unavailable, mark it a ghost relationship
if (hasUnavailableRelationship(d)) { if (hasUnavailableRelationship(d)) {
ghost = true; ghost = true;
} }
return ghost; return ghost;
}); });
} }
updated.each(function (d) { updated.each(function (d) {
@ -669,7 +687,7 @@ nf.Connection = (function () {
// update the label text // update the label text
connectionFrom.select('text.connection-from') connectionFrom.select('text.connection-from')
.each(function (d) { .each(function () {
var connectionFromLabel = d3.select(this); var connectionFromLabel = d3.select(this);
// reset the label name to handle any previous state // reset the label name to handle any previous state
@ -677,9 +695,9 @@ nf.Connection = (function () {
// apply ellipsis to the label as necessary // apply ellipsis to the label as necessary
nf.CanvasUtils.ellipsis(connectionFromLabel, d.component.source.name); nf.CanvasUtils.ellipsis(connectionFromLabel, d.component.source.name);
}).append('title').text(function (d) { }).append('title').text(function () {
return d.component.source.name; return d.component.source.name;
}); });
// update the label run status // update the label run status
connectionFrom.select('image.connection-from-run-status').attr('xlink:href', function () { 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 // apply ellipsis to the label as necessary
nf.CanvasUtils.ellipsis(connectionToLabel, d.component.destination.name); nf.CanvasUtils.ellipsis(connectionToLabel, d.component.destination.name);
}).append('title').text(function (d) { }).append('title').text(function (d) {
return d.component.destination.name; return d.component.destination.name;
}); });
// update the label run status // update the label run status
connectionTo.select('image.connection-to-run-status').attr('xlink:href', function () { connectionTo.select('image.connection-to-run-status').attr('xlink:href', function () {
@ -821,7 +839,7 @@ nf.Connection = (function () {
// update the connection name // update the connection name
connectionName.select('text.connection-name') connectionName.select('text.connection-name')
.each(function (d) { .each(function () {
var connectionToLabel = d3.select(this); var connectionToLabel = d3.select(this);
// reset the label name to handle any previous state // reset the label name to handle any previous state
@ -829,9 +847,9 @@ nf.Connection = (function () {
// apply ellipsis to the label as necessary // apply ellipsis to the label as necessary
nf.CanvasUtils.ellipsis(connectionToLabel, connectionNameValue); nf.CanvasUtils.ellipsis(connectionToLabel, connectionNameValue);
}).append('title').text(function (d) { }).append('title').text(function () {
return connectionNameValue; return connectionNameValue;
}); });
} else { } else {
// there is no connection name, but check if the name was previous // there is no connection name, but check if the name was previous
// rendered so it can be removed // rendered so it can be removed
@ -861,11 +879,49 @@ nf.Connection = (function () {
.text('Queued'); .text('Queued');
queued.append('text') queued.append('text')
.attr({
'class': 'connection-stats-value queued',
'x': 46,
'y': 10
});
var expiration = queued.append('g')
.attr({ .attr({
'class': 'connection-stats-value queued', 'class': 'expiration-icon',
'x': 46, 'transform': 'translate(167, 2)'
'y': 10
}); });
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 // update the queued vertical positioning as necessary
@ -879,6 +935,15 @@ nf.Connection = (function () {
.attr('height', function () { .attr('height', function () {
return 5 + (15 * labelCount) + 3; 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()) { if (nf.Common.isDFM()) {
// only support dragging the label when appropriate // only support dragging the label when appropriate