diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java index 3400b00f61..8d8fd19138 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java @@ -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."); + } + }); + } + } + /** *
* 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."); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js index 5ee3817dfa..1fe8aba603 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js @@ -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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js index 8e96dfa0ef..4fb31c9f48 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js @@ -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(); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js index b9a6ab9782..56ff8f596b 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-draggable.js @@ -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 diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-snippet.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-snippet.js index 159c2d3d0a..e5c36587b2 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-snippet.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-snippet.js @@ -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'