NIFI-730:

- Adding progress bar for showing current status of drop request.
- Allowing the user to cancel the current drop request.
This commit is contained in:
Matt Gilman 2015-10-14 12:39:19 -04:00
parent a2ae99f899
commit cad0e7cf0f
14 changed files with 409 additions and 208 deletions

View File

@ -38,11 +38,14 @@ public class DropRequestDTO {
private Boolean finished; private Boolean finished;
private Integer currentCount; private Integer currentCount;
private String currentSize; private Long currentSize;
private String current;
private Integer originalCount; private Integer originalCount;
private String originalSize; private Long originalSize;
private String original;
private Integer droppedCount; private Integer droppedCount;
private String droppedSize; private Long droppedSize;
private String dropped;
private String state; private String state;
@ -151,19 +154,33 @@ public class DropRequestDTO {
} }
/** /**
* @return the siez of the flow files currently queued. * @return the size of the flow files currently queued in bytes.
*/ */
@ApiModelProperty( @ApiModelProperty(
value = "The size of flow files currently queued." value = "The size of flow files currently queued in bytes."
) )
public String getCurrentSize() { public Long getCurrentSize() {
return currentSize; return currentSize;
} }
public void setCurrentSize(String currentSize) { public void setCurrentSize(Long currentSize) {
this.currentSize = currentSize; this.currentSize = currentSize;
} }
/**
* @return the count and size of the currently queued flow files.
*/
@ApiModelProperty(
value = "The count and size of flow files currently queued."
)
public String getCurrent() {
return current;
}
public void setCurrent(String current) {
this.current = current;
}
/** /**
* @return the number of flow files to be dropped as a result of this request. * @return the number of flow files to be dropped as a result of this request.
*/ */
@ -179,19 +196,33 @@ public class DropRequestDTO {
} }
/** /**
* @return the size of the flow files to be dropped as a result of this request. * @return the size of the flow files to be dropped as a result of this request in bytes.
*/ */
@ApiModelProperty( @ApiModelProperty(
value = "The size of flow files to be dropped as a result of this request." value = "The size of flow files to be dropped as a result of this request in bytes."
) )
public String getOriginalSize() { public Long getOriginalSize() {
return originalSize; return originalSize;
} }
public void setOriginalSize(String originalSize) { public void setOriginalSize(Long originalSize) {
this.originalSize = originalSize; this.originalSize = originalSize;
} }
/**
* @return the count and size of flow files to be dropped as a result of this request.
*/
@ApiModelProperty(
value = "The count and size of flow files to be dropped as a result of this request."
)
public String getOriginal() {
return original;
}
public void setOriginal(String original) {
this.original = original;
}
/** /**
* @return the number of flow files that have been dropped thus far. * @return the number of flow files that have been dropped thus far.
*/ */
@ -207,19 +238,33 @@ public class DropRequestDTO {
} }
/** /**
* @return the size of the flow files that have been dropped thus far. * @return the size of the flow files that have been dropped thus far in bytes.
*/ */
@ApiModelProperty( @ApiModelProperty(
value = "The size of flow files that have been dropped thus far." value = "The size of flow files that have been dropped thus far in bytes."
) )
public String getDroppedSize() { public Long getDroppedSize() {
return droppedSize; return droppedSize;
} }
public void setDroppedSize(String droppedSize) { public void setDroppedSize(Long droppedSize) {
this.droppedSize = droppedSize; this.droppedSize = droppedSize;
} }
/**
* @return the count and size of the flow files that have been dropped thus far.
*/
@ApiModelProperty(
value = "The count and size of flow files that have been dropped thus far."
)
public String getDropped() {
return dropped;
}
public void setDropped(String dropped) {
this.dropped = dropped;
}
/** /**
* @return the current state of the drop request. * @return the current state of the drop request.
*/ */

View File

@ -552,8 +552,9 @@ public interface NiFiServiceFacade {
* @param groupId group * @param groupId group
* @param connectionId The ID of the connection * @param connectionId The ID of the connection
* @param dropRequestId The flow file drop request * @param dropRequestId The flow file drop request
* @return The DropRequest
*/ */
void deleteFlowFileDropRequest(String groupId, String connectionId, String dropRequestId); DropRequestDTO deleteFlowFileDropRequest(String groupId, String connectionId, String dropRequestId);
// ---------------------------------------- // ----------------------------------------
// InputPort methods // InputPort methods

View File

@ -810,8 +810,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
} }
@Override @Override
public void deleteFlowFileDropRequest(String groupId, String connectionId, String dropRequestId) { public DropRequestDTO deleteFlowFileDropRequest(String groupId, String connectionId, String dropRequestId) {
connectionDAO.deleteFlowFileDropRequest(groupId, connectionId, dropRequestId); return dtoFactory.createDropRequestDTO(connectionDAO.deleteFlowFileDropRequest(groupId, connectionId, dropRequestId));
} }
@Override @Override

View File

@ -1086,7 +1086,8 @@ public class ConnectionResource extends ApplicationResource {
} }
// delete the drop request // delete the drop request
serviceFacade.deleteFlowFileDropRequest(groupId, connectionId, dropRequestId); final DropRequestDTO dropRequest = serviceFacade.deleteFlowFileDropRequest(groupId, connectionId, dropRequestId);
dropRequest.setUri(generateResourceUri("controller", "process-groups", groupId, "connections", connectionId, "contents", "drop-requests", dropRequestId));
// create the revision // create the revision
final RevisionDTO revision = new RevisionDTO(); final RevisionDTO revision = new RevisionDTO();
@ -1095,6 +1096,7 @@ public class ConnectionResource extends ApplicationResource {
// create the response entity // create the response entity
final DropRequestEntity entity = new DropRequestEntity(); final DropRequestEntity entity = new DropRequestEntity();
entity.setRevision(revision); entity.setRevision(revision);
entity.setDropRequest(dropRequest);
return generateOkResponse(entity).build(); return generateOkResponse(entity).build();
} }

View File

@ -312,13 +312,16 @@ public final class DtoFactory {
final QueueSize dropped = dropRequest.getDroppedSize(); final QueueSize dropped = dropRequest.getDroppedSize();
dto.setDroppedCount(dropped.getObjectCount()); dto.setDroppedCount(dropped.getObjectCount());
dto.setDroppedSize(FormatUtils.formatDataSize(dropped.getByteCount())); dto.setDroppedSize(dropped.getByteCount());
dto.setDropped(FormatUtils.formatCount(dropped.getObjectCount()) + " / " + FormatUtils.formatDataSize(dropped.getByteCount()));
if (dropRequest.getOriginalSize() != null) { if (dropRequest.getOriginalSize() != null) {
final QueueSize original = dropRequest.getOriginalSize(); final QueueSize original = dropRequest.getOriginalSize();
dto.setOriginalCount(original.getObjectCount()); dto.setOriginalCount(original.getObjectCount());
dto.setOriginalSize(FormatUtils.formatDataSize(original.getByteCount())); dto.setOriginalSize(original.getByteCount());
dto.setPercentCompleted((dropped.getObjectCount() * 100 ) / original.getObjectCount()); dto.setOriginal(FormatUtils.formatCount(original.getObjectCount()) + " / " + FormatUtils.formatDataSize(original.getByteCount()));
dto.setPercentCompleted((dropped.getObjectCount() * 100) / original.getObjectCount());
} else { } else {
dto.setPercentCompleted(0); dto.setPercentCompleted(0);
} }
@ -326,7 +329,8 @@ public final class DtoFactory {
if (dropRequest.getCurrentSize() != null) { if (dropRequest.getCurrentSize() != null) {
final QueueSize current = dropRequest.getCurrentSize(); final QueueSize current = dropRequest.getCurrentSize();
dto.setCurrentCount(current.getObjectCount()); dto.setCurrentCount(current.getObjectCount());
dto.setCurrentSize(FormatUtils.formatDataSize(current.getByteCount())); dto.setCurrentSize(current.getByteCount());
dto.setCurrent(FormatUtils.formatCount(current.getObjectCount()) + " / " + FormatUtils.formatDataSize(current.getByteCount()));
} }
return dto; return dto;

View File

@ -134,6 +134,7 @@ public interface ConnectionDAO {
* @param groupId group id * @param groupId group id
* @param id The id of the connection * @param id The id of the connection
* @param dropRequestId The drop request id * @param dropRequestId The drop request id
* @return The drop request
*/ */
void deleteFlowFileDropRequest(String groupId, String id, String dropRequestId); DropFlowFileStatus deleteFlowFileDropRequest(String groupId, String id, String dropRequestId);
} }

View File

@ -496,10 +496,10 @@ public class StandardConnectionDAO extends ComponentDAO implements ConnectionDAO
} }
@Override @Override
public void deleteFlowFileDropRequest(String groupId, String connectionId, String dropRequestId) { public DropFlowFileStatus deleteFlowFileDropRequest(String groupId, String connectionId, String dropRequestId) {
final Connection connection = locateConnection(groupId, connectionId); final Connection connection = locateConnection(groupId, connectionId);
final FlowFileQueue queue = connection.getFlowFileQueue(); final FlowFileQueue queue = connection.getFlowFileQueue();
queue.cancelDropFlowFileRequest(dropRequestId); return queue.cancelDropFlowFileRequest(dropRequestId);
} }
/* setters */ /* setters */

View File

@ -119,6 +119,7 @@
<jsp:include page="/WEB-INF/partials/canvas/secure-port-details.jsp"/> <jsp:include page="/WEB-INF/partials/canvas/secure-port-details.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/label-configuration.jsp"/> <jsp:include page="/WEB-INF/partials/canvas/label-configuration.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/connection-configuration.jsp"/> <jsp:include page="/WEB-INF/partials/canvas/connection-configuration.jsp"/>
<jsp:include page="/WEB-INF/partials/canvas/drop-request-status-dialog.jsp"/>
<jsp:include page="/WEB-INF/partials/connection-details.jsp"/> <jsp:include page="/WEB-INF/partials/connection-details.jsp"/>
<div id="faded-background"></div> <div id="faded-background"></div>
<div id="glass-pane"></div> <div id="glass-pane"></div>

View File

@ -0,0 +1,29 @@
<%--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--%>
<%@ page contentType="text/html" pageEncoding="UTF-8" session="false" %>
<div id="drop-request-status-dialog">
<div class="dialog-content">
<div class="setting">
<div class="setting-field">
<div id="drop-request-status-message"></div>
</div>
<div class="setting-field">
<div id="drop-request-percent-complete"></div>
</div>
</div>
</div>
</div>

View File

@ -269,6 +269,31 @@ div.go-to-link {
height: 195px; height: 195px;
} }
#drop-request-status-dialog {
display: none;
width: 400px;
height: 135px;
z-index: 1301;
}
#drop-request-percent-complete {
width: 378px;
border-radius: 0;
}
#drop-request-percent-complete .ui-progressbar-value {
border-radius: 0;
}
div.progress-label {
color: #000000;
display: block;
font-weight: bold;
text-align: center;
width: 378px;
margin-top: 4px;
}
#nf-ok-dialog { #nf-ok-dialog {
z-index: 1302; z-index: 1302;
display: none; display: none;

View File

@ -24,6 +24,31 @@ nf.Actions = (function () {
controller: '../nifi-api/controller' controller: '../nifi-api/controller'
} }
}; };
/**
* Initializes the drop request status dialog.
*/
var initializeDropRequestStatusDialog = function () {
// initialize the drop requst progress bar
var dropRequestProgressBar = $('#drop-request-percent-complete').progressbar();
// configure the drop request status dialog
$('#drop-request-status-dialog').modal({
headerText: 'Emptying queue',
overlayBackground: false,
handler: {
close: function () {
// reset the progress bar
dropRequestProgressBar.find('div.progress-label').remove();
// update the progress bar
var label = $('<div class="progress-label"></div>').text('0%');
dropRequestProgressBar.progressbar('value', 0).append(label);
}
}
});
};
/** /**
* Updates the resource with the specified data. * Updates the resource with the specified data.
@ -78,6 +103,13 @@ nf.Actions = (function () {
}; };
return { return {
/**
* Initializes the actions.
*/
init: function () {
initializeDropRequestStatusDialog();
},
/** /**
* Enters the specified process group. * Enters the specified process group.
* *
@ -855,56 +887,144 @@ nf.Actions = (function () {
return; return;
} }
var MAX_DELAY = 4; // prompt the user before emptying the queue
nf.Dialog.showYesNoDialog({
dialogContent: 'Are you sure you want to empty the queue? All data enqueued at the time of the request will be removed from the dataflow.',
overlayBackground: false,
yesHandler: function () {
// get the connection data
var connection = selection.datum();
var MAX_DELAY = 4;
var cancelled = false;
var dropRequest = null;
var dropRequestTimer = null;
// process the drop request // updates the progress bar
var processDropRequest = function (dropRequest, nextDelay) { var updateProgress = function (percentComplete) {
// see if the drop request has completed // remove existing labels
if (dropRequest.finished === true) { var progressBar = $('#drop-request-percent-complete');
// request is finished so it can be removed progressBar.find('div.progress-label').remove();
deleteDropRequest(dropRequest);
} else {
// update the UI with the current status of the drop request
schedule(dropRequest, nextDelay);
}
};
// schedule for the next poll iteration // update the progress bar
var schedule = function (dropRequest, delay) { var label = $('<div class="progress-label"></div>').text(percentComplete + '%');
setTimeout(function () { if (percentComplete > 0) {
label.css('margin-top', '-19px');
}
progressBar.progressbar('value', percentComplete).append(label);
};
// update the button model of the drop request status dialog
$('#drop-request-status-dialog').modal('setButtonModel', [{
buttonText: 'Cancel',
handler: {
click: function () {
cancelled = true;
// we are waiting for the next poll attempt
if (dropRequestTimer !== null) {
// cancel it
clearTimeout(dropRequestTimer);
// cancel the provenance
completeDropRequest();
}
}
}
}]);
// completes the drop request by removing it and showing how many flowfiles were deleted
var completeDropRequest = function () {
if (nf.Common.isDefinedAndNotNull(dropRequest)) {
$.ajax({
type: 'DELETE',
url: dropRequest.uri,
dataType: 'json'
}).done(function(response) {
// report the results of this drop request
dropRequest = response.dropRequest;
// parse the dropped stats to render results
var tokens = dropRequest.dropped.split(/ \/ /);
var results = $('<div></div>').text('Successfully removed ' + tokens[0] + ' (' + tokens[1] + ') FlowFiles');
nf.Dialog.showOkDialog({
dialogContent: results,
overlayBackground: false
});
}).always(function() {
$('#drop-request-status-dialog').modal('hide');
});
} else {
// nothing was removed
nf.Dialog.showYesNoDialog({
dialogContent: 'No FlowFiles were removed.',
overlayBackground: false
});
// close the dialog
$('#drop-request-status-dialog').modal('hide');
}
};
// process the drop request
var processDropRequest = function (delay) {
// update the percent complete
updateProgress(dropRequest.percentCompleted);
// update the status of the drop request
$('#drop-request-status-message').text(dropRequest.state);
// update the current number of enqueued flowfiles
if (nf.Common.isDefinedAndNotNull(dropRequest.currentCount)) {
connection.status.queued = dropRequest.current;
nf.Connection.refresh(connection.id);
}
// close the dialog if the
if (dropRequest.finished === true || cancelled === true) {
completeDropRequest();
} else {
// wait delay to poll again
dropRequestTimer = setTimeout(function () {
// clear the drop request timer
dropRequestTimer = null;
// schedule to poll the status again in nextDelay
pollDropRequest(Math.min(MAX_DELAY, delay * 2));
}, delay * 1000);
}
};
// schedule for the next poll iteration
var pollDropRequest = function (nextDelay) {
$.ajax({
type: 'GET',
url: dropRequest.uri,
dataType: 'json'
}).done(function(response) {
dropRequest = response.dropRequest;
processDropRequest(nextDelay);
}).fail(completeDropRequest);
};
// issue the request to delete the flow files
$.ajax({ $.ajax({
type: 'GET', type: 'DELETE',
url: dropRequest.uri, url: connection.component.uri + '/contents',
dataType: 'json' dataType: 'json'
}).done(function(response) { }).done(function(response) {
var dropRequest = response.dropRequest; // initialize the progress bar value
processDropRequest(dropRequest, Math.min(MAX_DELAY, delay * 2)); updateProgress(0);
}).fail(nf.Common.handleAjaxError);
}, delay * 1000); // show the progress dialog
}; $('#drop-request-status-dialog').modal('show');
// delete the drop request // process the drop request
var deleteDropRequest = function (dropRequest) { dropRequest = response.dropRequest;
$.ajax({ processDropRequest(1);
type: 'DELETE', }).fail(completeDropRequest);
url: dropRequest.uri, }
dataType: 'json' });
}).done(function() {
// drop request has been deleted
}).fail(nf.Common.handleAjaxError);
};
// get the connection data
var connection = selection.datum();
// issue the request to delete the flow files
$.ajax({
type: 'DELETE',
url: connection.component.uri + '/contents',
dataType: 'json'
}).done(function(response) {
processDropRequest(response.dropRequest, 1);
}).fail(nf.Common.handleAjaxError);
}, },
/** /**

View File

@ -188,7 +188,7 @@ nf.Canvas = (function () {
if (!refreshContainer.is(':visible')) { if (!refreshContainer.is(':visible')) {
$('#stats-last-refreshed').addClass('alert'); $('#stats-last-refreshed').addClass('alert');
var refreshMessage = "This flow has been modified by '" + revision.lastModifier + "'. Please refresh."; var refreshMessage = "This flow has been modified by '" + revision.lastModifier + "'. Please refresh.";
// update the tooltip // update the tooltip
var refreshRequiredIcon = $('#refresh-required-icon'); var refreshRequiredIcon = $('#refresh-required-icon');
if (refreshRequiredIcon.data('qtip')) { if (refreshRequiredIcon.data('qtip')) {
@ -198,10 +198,10 @@ nf.Canvas = (function () {
content: refreshMessage content: refreshMessage
}, nf.CanvasUtils.config.systemTooltipConfig)); }, nf.CanvasUtils.config.systemTooltipConfig));
} }
refreshContainer.show(); refreshContainer.show();
} }
// insert the refresh needed text in the settings - if necessary // insert the refresh needed text in the settings - if necessary
if (!settingsRefreshIcon.is(':visible')) { if (!settingsRefreshIcon.is(':visible')) {
$('#settings-last-refreshed').addClass('alert'); $('#settings-last-refreshed').addClass('alert');
@ -333,7 +333,7 @@ nf.Canvas = (function () {
'offset': '100%', 'offset': '100%',
'stop-color': '#ffffff' 'stop-color': '#ffffff'
}); });
// define the gradient for the expiration icon // define the gradient for the expiration icon
var expirationBackground = defs.append('linearGradient') var expirationBackground = defs.append('linearGradient')
.attr({ .attr({
@ -343,7 +343,7 @@ nf.Canvas = (function () {
'x2': '0%', 'x2': '0%',
'y2': '100%' 'y2': '100%'
}); });
expirationBackground.append('stop') expirationBackground.append('stop')
.attr({ .attr({
'offset': '0%', 'offset': '0%',
@ -397,106 +397,106 @@ nf.Canvas = (function () {
// 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();
// prevents the browser from changing to a text selection cursor // prevents the browser from changing to a text selection cursor
d3.event.preventDefault(); d3.event.preventDefault();
} }
}) })
.on('mousemove.selection', function () { .on('mousemove.selection', function () {
// update selection box if shift is held down // update selection box if shift is held down
if (d3.event.shiftKey) { if (d3.event.shiftKey) {
// get the selection box // get the selection box
var selectionBox = d3.select('rect.selection'); var selectionBox = d3.select('rect.selection');
if (!selectionBox.empty()) { if (!selectionBox.empty()) {
// get the original position // get the original position
var originalPosition = selectionBox.datum(); var originalPosition = selectionBox.datum();
var position = d3.mouse(canvas.node()); var position = d3.mouse(canvas.node());
var d = {}; var d = {};
if (originalPosition[0] < position[0]) { if (originalPosition[0] < position[0]) {
d.x = originalPosition[0]; d.x = originalPosition[0];
d.width = position[0] - originalPosition[0]; d.width = position[0] - originalPosition[0];
} else { } else {
d.x = position[0]; d.x = position[0];
d.width = originalPosition[0] - 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);
// prevent further propagation (to parents)
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]) { // reset the canvas click flag
d.y = originalPosition[1]; canvasClicked = false;
d.height = position[1] - originalPosition[1];
} else { // get the selection box
d.y = position[1]; var selectionBox = d3.select('rect.selection');
d.height = originalPosition[1] - position[1]; 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;
});
// 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 selection box // update the toolbar
selectionBox.attr(d); nf.CanvasToolbar.refresh();
// prevent further propagation (to parents)
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;
// 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;
});
// 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
@ -510,7 +510,7 @@ nf.Canvas = (function () {
var top = parseInt(canvasContainer.css('top'), 10); var top = parseInt(canvasContainer.css('top'), 10);
var windowHeight = $(window).height(); var windowHeight = $(window).height();
var canvasHeight = (windowHeight - (bottom + top)); var canvasHeight = (windowHeight - (bottom + top));
// canvas/svg // canvas/svg
canvasContainer.css({ canvasContainer.css({
'height': canvasHeight + 'px', 'height': canvasHeight + 'px',
@ -536,7 +536,7 @@ nf.Canvas = (function () {
} }
}).on('keydown', function (evt) { }).on('keydown', function (evt) {
var isCtrl = evt.ctrlKey || evt.metaKey; var isCtrl = evt.ctrlKey || evt.metaKey;
// consider escape, before checking dialogs // consider escape, before checking dialogs
if (!isCtrl && evt.keyCode === 27) { if (!isCtrl && evt.keyCode === 27) {
// esc // esc
@ -552,7 +552,7 @@ nf.Canvas = (function () {
// first consider read only property detail dialog // first consider read only property detail dialog
if ($('div.property-detail').is(':visible')) { if ($('div.property-detail').is(':visible')) {
nf.Common.removeAllPropertyDetailDialogs(); nf.Common.removeAllPropertyDetailDialogs();
// prevent further bubbling as we're already handled it // prevent further bubbling as we're already handled it
evt.stopPropagation(); evt.stopPropagation();
evt.preventDefault(); evt.preventDefault();
@ -570,7 +570,7 @@ nf.Canvas = (function () {
var dialogMax = null; var dialogMax = null;
// identify the top most cancellable // identify the top most cancellable
$.each(cancellables, function(_, cancellable) { $.each(cancellables, function (_, cancellable) {
var dialog = $(cancellable); var dialog = $(cancellable);
var zIndex = dialog.css('zIndex'); var zIndex = dialog.css('zIndex');
@ -615,10 +615,10 @@ nf.Canvas = (function () {
} }
} }
} }
return; return;
} }
// if a dialog is open, disable canvas shortcuts // if a dialog is open, disable canvas shortcuts
if ($('.dialog').is(':visible')) { if ($('.dialog').is(':visible')) {
return; return;
@ -833,7 +833,7 @@ nf.Canvas = (function () {
bulletinIcon.show(); bulletinIcon.show();
} }
} }
// update controller service and reporting task bulletins // update controller service and reporting task bulletins
nf.Settings.setBulletins(controllerStatus.controllerServiceBulletins, controllerStatus.reportingTaskBulletins); nf.Settings.setBulletins(controllerStatus.controllerServiceBulletins, controllerStatus.reportingTaskBulletins);
@ -934,21 +934,17 @@ 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.
*/ */
@ -956,7 +952,6 @@ nf.Canvas = (function () {
// set polling flag // set polling flag
revisionPolling = false; revisionPolling = false;
}, },
/** /**
* Remove the status poller. * Remove the status poller.
*/ */
@ -964,7 +959,6 @@ 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.
@ -973,7 +967,7 @@ nf.Canvas = (function () {
return $.Deferred(function (deferred) { return $.Deferred(function (deferred) {
// hide the context menu // hide the context menu
nf.ContextMenu.hide(); nf.ContextMenu.hide();
// get the process group to refresh everything // get the process group to refresh everything
var processGroupXhr = reloadProcessGroup(nf.Canvas.getGroupId()); var processGroupXhr = reloadProcessGroup(nf.Canvas.getGroupId());
var statusXhr = reloadFlowStatus(); var statusXhr = reloadFlowStatus();
@ -1007,7 +1001,6 @@ nf.Canvas = (function () {
}); });
}).promise(); }).promise();
}, },
/** /**
* Reloads the status. * Reloads the status.
*/ */
@ -1021,7 +1014,6 @@ nf.Canvas = (function () {
}); });
}).promise(); }).promise();
}, },
/** /**
* Initialize NiFi. * Initialize NiFi.
*/ */
@ -1099,6 +1091,7 @@ nf.Canvas = (function () {
nf.GraphControl.init(); nf.GraphControl.init();
nf.Search.init(); nf.Search.init();
nf.Settings.init(); nf.Settings.init();
nf.Actions.init();
// initialize the component behaviors // initialize the component behaviors
nf.Draggable.init(); nf.Draggable.init();
@ -1146,7 +1139,6 @@ 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.
* *
@ -1155,7 +1147,6 @@ 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.
* *
@ -1164,7 +1155,6 @@ 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.
* *
@ -1173,14 +1163,12 @@ 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.
* *
@ -1189,14 +1177,12 @@ nf.Canvas = (function () {
setGroupId: function (gi) { setGroupId: function (gi) {
groupId = gi; groupId = gi;
}, },
/** /**
* Get the group id. * Get the group id.
*/ */
getGroupId: function () { getGroupId: function () {
return groupId; return groupId;
}, },
/** /**
* Set the group name. * Set the group name.
* *
@ -1205,14 +1191,12 @@ 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.
* *
@ -1221,16 +1205,14 @@ 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.
*/ */
@ -1297,8 +1279,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
@ -1385,7 +1367,6 @@ nf.Canvas = (function () {
// add the behavior to the canvas and disable dbl click zoom // add the behavior to the canvas and disable dbl click zoom
svg.call(behavior).on('dblclick.zoom', null); svg.call(behavior).on('dblclick.zoom', null);
}, },
/** /**
* Whether or not a component should be rendered based solely on the current scale. * Whether or not a component should be rendered based solely on the current scale.
* *
@ -1394,7 +1375,6 @@ nf.Canvas = (function () {
shouldRenderPerScale: function () { shouldRenderPerScale: function () {
return nf.Canvas.View.scale() >= MIN_SCALE_TO_RENDER; return nf.Canvas.View.scale() >= MIN_SCALE_TO_RENDER;
}, },
/** /**
* Updates component visibility based on the current translation/scale. * Updates component visibility based on the current translation/scale.
*/ */
@ -1402,7 +1382,6 @@ nf.Canvas = (function () {
updateComponentVisibility(); updateComponentVisibility();
nf.Graph.pan(); nf.Graph.pan();
}, },
/** /**
* Sets/gets the current translation. * Sets/gets the current translation.
* *
@ -1415,7 +1394,6 @@ nf.Canvas = (function () {
behavior.translate(translate); behavior.translate(translate);
} }
}, },
/** /**
* Sets/gets the current scale. * Sets/gets the current scale.
* *
@ -1428,7 +1406,6 @@ nf.Canvas = (function () {
behavior.scale(scale); behavior.scale(scale);
} }
}, },
/** /**
* Zooms in a single zoom increment. * Zooms in a single zoom increment.
*/ */
@ -1453,7 +1430,6 @@ nf.Canvas = (function () {
height: 1 height: 1
}); });
}, },
/** /**
* Zooms out a single zoom increment. * Zooms out a single zoom increment.
*/ */
@ -1478,7 +1454,6 @@ nf.Canvas = (function () {
height: 1 height: 1
}); });
}, },
/** /**
* Zooms to fit the entire graph on the canvas. * Zooms to fit the entire graph on the canvas.
*/ */
@ -1525,7 +1500,6 @@ nf.Canvas = (function () {
height: canvasHeight / newScale height: canvasHeight / newScale
}); });
}, },
/** /**
* Zooms to the actual size (1 to 1). * Zooms to the actual size (1 to 1).
*/ */
@ -1574,7 +1548,6 @@ nf.Canvas = (function () {
// center as appropriate // center as appropriate
nf.CanvasUtils.centerBoundingBox(box); nf.CanvasUtils.centerBoundingBox(box);
}, },
/** /**
* Refreshes the view based on the configured translation and scale. * Refreshes the view based on the configured translation and scale.
* *

View File

@ -278,11 +278,11 @@ nf.ContextMenu = (function () {
}; };
/** /**
* Only DFMs can delete flow files from a connection. * Only DFMs can empty a queue.
* *
* @param {selection} selection * @param {selection} selection
*/ */
var canDeleteFlowFiles = function (selection) { var canEmptyQueue = function (selection) {
return nf.Common.isDFM() && isConnection(selection); return nf.Common.isDFM() && isConnection(selection);
}; };
@ -382,7 +382,7 @@ nf.ContextMenu = (function () {
{condition: isCopyable, menuItem: {img: 'images/iconCopy.png', text: 'Copy', action: 'copy'}}, {condition: isCopyable, menuItem: {img: 'images/iconCopy.png', text: 'Copy', action: 'copy'}},
{condition: isPastable, menuItem: {img: 'images/iconPaste.png', text: 'Paste', action: 'paste'}}, {condition: isPastable, menuItem: {img: 'images/iconPaste.png', text: 'Paste', action: 'paste'}},
{condition: canMoveToParent, menuItem: {img: 'images/iconMoveToParent.png', text: 'Move to parent group', action: 'moveIntoParent'}}, {condition: canMoveToParent, menuItem: {img: 'images/iconMoveToParent.png', text: 'Move to parent group', action: 'moveIntoParent'}},
{condition: canDeleteFlowFiles, menuItem: {img: 'images/iconDelete.png', text: 'Empty queue', action: 'emptyQueue'}}, {condition: canEmptyQueue, menuItem: {img: 'images/iconEmptyQueue.png', text: 'Empty queue', action: 'emptyQueue'}},
{condition: isDeletable, menuItem: {img: 'images/iconDelete.png', text: 'Delete', action: 'delete'}} {condition: isDeletable, menuItem: {img: 'images/iconDelete.png', text: 'Delete', action: 'delete'}}
]; ];