diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java index 07e3e91f01..70701c02e6 100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java @@ -1685,7 +1685,11 @@ public final class StandardProcessGroup implements ProcessGroup { } if (isRootGroup() && (!snippet.getInputPorts().isEmpty() || !snippet.getOutputPorts().isEmpty())) { - throw new IllegalStateException("Cannot move Ports from the Root Group to a Non-Root Group"); + throw new IllegalStateException("Cannot move Ports out of the root group"); + } + + if (destination.isRootGroup() && (!snippet.getInputPorts().isEmpty() || !snippet.getOutputPorts().isEmpty())) { + throw new IllegalStateException("Cannot move Ports into the root group"); } for (final String id : replaceNullWithEmptySet(snippet.getInputPorts())) { diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconMoveToParent.png b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconMoveToParent.png new file mode 100644 index 0000000000..283de3d451 Binary files /dev/null and b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/images/iconMoveToParent.png differ diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js index 4d2da84505..3b47a8d757 100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js @@ -914,6 +914,21 @@ nf.Actions = (function () { }); }, + /** + * Moves the currently selected component into the current parent group. + */ + moveIntoParent: function () { + var selection = nf.CanvasUtils.getSelection(); + + // ensure that components have been specified + if (selection.empty()) { + return; + } + + // move the current selection into the parent group + nf.CanvasUtils.moveComponentsToParent(selection); + }, + /** * Creates a new template based off the currently selected components. If no components * are selected, a template of the entire canvas is made. diff --git a/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js index 4c6be57d44..af4473e092 100644 --- a/nifi/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/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js @@ -50,6 +50,58 @@ nf.CanvasUtils = (function () { return mid; }; + + var moveComponents = function (components, groupId) { + return $.Deferred(function (deferred) { + // ensure the current selection is eligible for move into the specified group + nf.CanvasUtils.eligibleForMove(components, groupId).done(function () { + // create a snippet for the specified components and link to the data flow + var snippetDetails = nf.Snippet.marshal(components, true); + nf.Snippet.create(snippetDetails).done(function (response) { + var snippet = response.snippet; + + // move the snippet into the target + nf.Snippet.move(snippet.id, groupId).done(function () { + var componentMap = d3.map(); + + // add the id to the type's array + var addComponent = function (type, id) { + if (!componentMap.has(type)) { + componentMap.set(type, []); + } + componentMap.get(type).push(id); + }; + + // go through each component being removed + components.each(function (d) { + addComponent(d.type, d.component.id); + }); + + // refresh all component types as necessary (handle components that have been removed) + componentMap.forEach(function (type, ids) { + nf[type].remove(ids); + }); + + // refresh the birdseye + nf.Birdseye.refresh(); + deferred.resolve(); + }).fail(nf.Common.handleAjaxError).fail(function () { + deferred.reject(); + }).always(function () { + // unable to acutally move the components so attempt to + // unlink and remove just the snippet + nf.Snippet.unlink(snippet.id).done(function () { + nf.Snippet.remove(snippet.id); + }); + }); + }).fail(nf.Common.handleAjaxError).fail(function () { + deferred.reject(); + }); + }).fail(function () { + deferred.reject(); + }); + }).promise(); + }; return { config: { @@ -1025,6 +1077,22 @@ nf.CanvasUtils = (function () { return origin; }, + /** + * Moves the specified components into the current parent group. + * + * @param {selection} components + */ + moveComponentsToParent: function (components) { + var groupId = nf.Canvas.getParentGroupId(); + + // if the group id is null, we're already in the top most group + if (groupId === null) { + nf.Dialog.showOkDialog('Components are already in the topmost group.'); + } else { + moveComponents(components, groupId); + } + }, + /** * Moves the specified components into the specified group. * @@ -1033,46 +1101,11 @@ nf.CanvasUtils = (function () { */ moveComponents: function (components, group) { var groupData = group.datum(); - - // ensure the current selection is eligible for move into the specified group - nf.CanvasUtils.eligibleForMove(components, group).done(function () { - // create a snippet for the specified components and link to the data flow - var snippetDetails = nf.Snippet.marshal(components, true); - nf.Snippet.create(snippetDetails).done(function (response) { - var snippet = response.snippet; - - // move the snippet into the target - nf.Snippet.move(snippet.id, groupData.component.id).done(function () { - var componentMap = d3.map(); - - // add the id to the type's array - var addComponent = function (type, id) { - if (!componentMap.has(type)) { - componentMap.set(type, []); - } - componentMap.get(type).push(id); - }; - - // go through each component being removed - components.each(function (d) { - addComponent(d.type, d.component.id); - }); - - // refresh all component types as necessary (handle components that have been removed) - componentMap.forEach(function (type, ids) { - nf[type].remove(ids); - }); - - // reload the target group - nf.ProcessGroup.reload(groupData.component); - }).fail(nf.Common.handleAjaxError).always(function () { - // unable to acutally move the components so attempt to - // unlink and remove just the snippet - nf.Snippet.unlink(snippet.id).done(function () { - nf.Snippet.remove(snippet.id); - }); - }); - }).fail(nf.Common.handleAjaxError); + + // move the components into the destination and... + moveComponents(components, groupData.component.id).done(function () { + // reload the target group + nf.ProcessGroup.reload(groupData.component); }); }, @@ -1161,15 +1194,15 @@ nf.CanvasUtils = (function () { }, /** - * Ensures components are eligible to be moved. The new target can be optionally specified. + * Ensures components are eligible to be moved. The new group can be optionally specified. * * 1) Ensuring that the input and output ports are not connected outside of this group * 2) If the target is specified; ensuring there are no port name conflicts in the target group * * @argument {selection} selection The selection being moved - * @argument {selection} group The selection containing the new group + * @argument {string} groupId The id of the new group */ - eligibleForMove: function (selection, group) { + eligibleForMove: function (selection, groupId) { var inputPorts = []; var outputPorts = []; @@ -1191,7 +1224,7 @@ nf.CanvasUtils = (function () { // ports in the root group cannot be moved if (nf.Canvas.getParentGroupId() === null) { nf.Dialog.showOkDialog({ - dialogContent: 'Ports in the root group cannot be moved into another group.', + dialogContent: 'Cannot move Ports out of the root group', overlayBackground: false }); portConnectionDeferred.reject(); @@ -1245,12 +1278,11 @@ nf.CanvasUtils = (function () { // create a deferred for checking port names in the target var portNameCheck = function () { return $.Deferred(function (portNameDeferred) { - var groupData = group.datum(); // add the get request $.ajax({ type: 'GET', - url: config.urls.controller + '/process-groups/' + encodeURIComponent(groupData.component.id), + url: config.urls.controller + '/process-groups/' + encodeURIComponent(groupId), data: { verbose: true }, @@ -1294,7 +1326,7 @@ nf.CanvasUtils = (function () { // execute the checks in order portConnectionCheck().done(function () { - if (nf.Common.isDefinedAndNotNull(group)) { + if (nf.Common.isDefinedAndNotNull(groupId)) { $.when(portNameCheck()).done(function () { deferred.resolve(); }).fail(function () { diff --git a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js index 2d0a41d782..e652dd4aa1 100644 --- a/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js +++ b/nifi/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js @@ -15,7 +15,7 @@ * limitations under the License. */ -/* global nf */ +/* global nf, d3 */ nf.ContextMenu = (function () { @@ -276,6 +276,15 @@ nf.ContextMenu = (function () { var canStopTransmission = function (selection) { return nf.Common.isDFM() && nf.CanvasUtils.canAllStopTransmitting(selection); }; + + /** + * Determines if the components in the specified selection can be moved into a parent group. + * + * @param {type} selection + */ + var canMoveToParent = function (selection) { + return !selection.empty() && nf.CanvasUtils.isDisconnected(selection) && nf.Canvas.getParentGroupId() !== null; + }; /** * Adds a menu item to the context menu. @@ -363,6 +372,7 @@ nf.ContextMenu = (function () { {condition: isNotConnection, menuItem: {img: 'images/iconCenterView.png', text: 'Center in view', action: 'center'}}, {condition: isCopyable, menuItem: {img: 'images/iconCopy.png', text: 'Copy', action: 'copy'}}, {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: isDeletable, menuItem: {img: 'images/iconDelete.png', text: 'Delete', action: 'delete'}} ];