NIFI-3575:

- Verify that when moving a snippet, we do not attempt to move a process group into itself.
- Update the UI to ensure a subsequent move event cannot be triggered while a previous move event is still in progress.

This closes #1582.

Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
Matt Gilman 2017-03-09 10:34:55 -05:00 committed by Andy LoPresto
parent 18f05856ff
commit 977aa6919c
No known key found for this signature in database
GPG Key ID: 3C6EF65B2F7DEF69
5 changed files with 57 additions and 19 deletions

View File

@ -2107,6 +2107,7 @@ public final class StandardProcessGroup implements ProcessGroup {
writeLock.lock();
try {
verifyContents(snippet);
verifyDestinationNotInSnippet(snippet, destination);
if (!isDisconnected(snippet)) {
throw new IllegalStateException("One or more components within the snippet is connected to a component outside of the snippet. Only a disconnected snippet may be moved.");
@ -2260,6 +2261,23 @@ public final class StandardProcessGroup implements ProcessGroup {
verifyAllKeysExist(snippet.getConnections().keySet(), connections, "Connection");
}
/**
* Verifies that a move request cannot attempt to move a process group into itself.
*
* @param snippet the snippet
* @param destination the destination
* @throws IllegalStateException if the snippet contains an ID that is equal to the identifier of the destination
*/
private void verifyDestinationNotInSnippet(final Snippet snippet, final ProcessGroup destination) throws IllegalStateException {
if (snippet.getProcessGroups() != null && destination != null) {
snippet.getProcessGroups().forEach((processGroupId, revision) -> {
if (processGroupId.equals(destination.getIdentifier())) {
throw new IllegalStateException("Unable to move Process Group into itself.");
}
});
}
}
/**
* <p>
* Verifies that all ID's specified by the given set exist as keys in the
@ -2497,6 +2515,7 @@ public final class StandardProcessGroup implements ProcessGroup {
}
verifyContents(snippet);
verifyDestinationNotInSnippet(snippet, newProcessGroup);
if (!isDisconnected(snippet)) {
throw new IllegalStateException("One or more components within the snippet is connected to a component outside of the snippet. Only a disconnected snippet may be moved.");

View File

@ -941,8 +941,10 @@
nfNgBridge.digest();
}).fail(nfErrorHandler.handleAjaxError);
} else {
var parentGroupId = nfCanvasUtils.getGroupId();
// create a snippet for the specified component and link to the data flow
var snippet = nfSnippet.marshal(selection);
var snippet = nfSnippet.marshal(selection, parentGroupId);
nfSnippet.create(snippet).done(function (response) {
// remove the snippet, effectively removing the components
nfSnippet.remove(response.snippet.id).done(function () {
@ -1502,7 +1504,8 @@
var templateDescription = $('#new-template-description').val();
// create a snippet
var snippet = nfSnippet.marshal(selection);
var parentGroupId = nfCanvasUtils.getGroupId();
var snippet = nfSnippet.marshal(selection, parentGroupId);
// create the snippet
nfSnippet.create(snippet).done(function (response) {
@ -1569,8 +1572,9 @@
var origin = nfCanvasUtils.getOrigin(selection);
// copy the snippet details
var parentGroupId = nfCanvasUtils.getGroupId();
nfClipboard.copy({
snippet: nfSnippet.marshal(selection),
snippet: nfSnippet.marshal(selection, parentGroupId),
origin: origin
});
},
@ -1608,6 +1612,8 @@
deferred.reject(xhr.responseText);
};
var destinationProcessGroupId = nfCanvasUtils.getGroupId();
// create a snippet from the details
nfSnippet.create(data['snippet']).done(function (createResponse) {
// determine the origin of the bounding box of the copy
@ -1622,7 +1628,7 @@
}
// copy the snippet to the new location
nfSnippet.copy(createResponse.snippet.id, origin).done(function (copyResponse) {
nfSnippet.copy(createResponse.snippet.id, origin, destinationProcessGroupId).done(function (copyResponse) {
var snippetFlow = copyResponse.flow;
// update the graph accordingly

View File

@ -21,17 +21,19 @@
define(['d3',
'jquery',
'nf.Common',
'nf.ErrorHandler',
'nf.Dialog',
'nf.Clipboard',
'nf.Storage'],
function (d3, $, nfCommon, nfDialog, nfClipboard, nfStorage) {
return (nf.CanvasUtils = factory(d3, $, nfCommon, nfDialog, nfClipboard, nfStorage));
function (d3, $, nfCommon, nfErrorHandler, nfDialog, nfClipboard, nfStorage) {
return (nf.CanvasUtils = factory(d3, $, nfCommon, nfErrorHandler, nfDialog, nfClipboard, nfStorage));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.CanvasUtils = factory(
require('d3'),
require('jquery'),
require('nf.Common'),
require('nf.ErrorHandler'),
require('nf.Dialog'),
require('nf.Clipboard'),
require('nf.Storage')));
@ -40,11 +42,12 @@
root.d3,
root.$,
root.nf.Common,
root.nf.ErrorHandler,
root.nf.Dialog,
root.nf.Clipboard,
root.nf.Storage);
}
}(this, function (d3, $, nfCommon, nfDialog, nfClipboard, nfStorage) {
}(this, function (d3, $, nfCommon, nfErrorHandler, nfDialog, nfClipboard, nfStorage) {
'use strict';
var nfCanvas;
@ -87,8 +90,10 @@
var moveComponents = function (components, groupId) {
return $.Deferred(function (deferred) {
var parentGroupId = nfCanvasUtils.getGroupId();
// create a snippet for the specified components
var snippet = nfSnippet.marshal(components);
var snippet = nfSnippet.marshal(components, parentGroupId);
nfSnippet.create(snippet).done(function (response) {
// move the snippet into the target
nfSnippet.move(response.snippet.id, groupId).done(function () {
@ -116,10 +121,10 @@
nfBirdseye.refresh();
deferred.resolve();
}).fail(nfCommon.handleAjaxError).fail(function () {
}).fail(nfErrorHandler.handleAjaxError).fail(function () {
deferred.reject();
});
}).fail(nfCommon.handleAjaxError).fail(function () {
}).fail(nfErrorHandler.handleAjaxError).fail(function () {
deferred.reject();
});
}).promise();

View File

@ -121,10 +121,12 @@
/**
* Updates the parent group of all selected components.
*
* @param {selection} the destination group
*/
var updateComponentsGroup = function () {
var selection = d3.selectAll('g.component.selected, g.connection.selected');
var group = d3.select('g.drop');
var updateComponentsGroup = function (group) {
// get the selection and deselect the components being moved
var selection = d3.selectAll('g.component.selected, g.connection.selected').classed('selected', false);
if (nfCanvasUtils.canModify(selection) === false) {
nfDialog.showOkDialog({
@ -230,11 +232,15 @@
return;
}
// get the destination group if applicable... remove the drop flag if necessary to prevent
// subsequent drop events from triggering prior to this move's completion
var group = d3.select('g.drop').classed('drop', false);
// either move or update the selections group as appropriate
if (d3.select('g.drop').empty()) {
if (group.empty()) {
updateComponentsPosition(dragSelection);
} else {
updateComponentsGroup();
updateComponentsGroup(group);
}
// remove the drag selection

View File

@ -54,10 +54,11 @@
* Marshals snippet from the specified selection.
*
* @argument {selection} selection The selection to marshal
* @argument {string} parentGroupId The parent group id
*/
marshal: function (selection) {
marshal: function (selection, parentGroupId) {
var snippet = {
parentGroupId: nfCanvasUtils.getGroupId(),
parentGroupId: parentGroupId,
processors: {},
funnels: {},
inputPorts: {},
@ -118,8 +119,9 @@
*
* @argument {string} snippetId The snippet id
* @argument {object} origin The origin
* @argument {string} destinationGroupId The destination group id
*/
copy: function (snippetId, origin) {
copy: function (snippetId, origin, destinationGroupId) {
var copySnippetRequestEntity = {
'snippetId': snippetId,
'originX': origin.x,
@ -128,7 +130,7 @@
return $.ajax({
type: 'POST',
url: config.urls.processGroups + '/' + encodeURIComponent(nfCanvasUtils.getGroupId()) + '/snippet-instance',
url: config.urls.processGroups + '/' + encodeURIComponent(destinationGroupId) + '/snippet-instance',
data: JSON.stringify(copySnippetRequestEntity),
dataType: 'json',
contentType: 'application/json'