exceptionfactory commented on code in PR #8965:
URL: https://github.com/apache/nifi/pull/8965#discussion_r2098541404


##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java:
##########
@@ -2973,6 +2976,122 @@ public ControllerServiceEntity 
updateControllerService(final Revision revision,
         return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
     }
 
+    @Override
+    public ControllerServiceEntity moveControllerService(final Revision 
revision, final ControllerServiceDTO controllerServiceDTO, final String 
newProcessGroupID) {
+        // get the component, ensure we have access to it, and perform the 
move request
+        final ControllerServiceNode controllerService = 
controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
+        final RevisionUpdate<ControllerServiceDTO> snapshot = 
updateComponent(revision,
+                controllerService,
+                () -> {
+                    final ProcessGroup oldParentGroup = 
controllerService.getProcessGroup();
+                    controllerService.setMoving(true);
+                    oldParentGroup.removeControllerService(controllerService);
+                    if (!oldParentGroup.isRootGroup() && 
oldParentGroup.getParent().getIdentifier().equals(newProcessGroupID)) {
+                        // move to parent process group
+                        
oldParentGroup.getParent().addControllerService(controllerService);
+                    } else {
+                        // move to child process group
+                        
oldParentGroup.getProcessGroup(newProcessGroupID).addControllerService(controllerService);
+                    }
+                    return controllerService;
+                },
+                cs -> {
+                    final ControllerServiceDTO dto = 
dtoFactory.createControllerServiceDto(cs);
+                    final ControllerServiceReference ref = 
controllerService.getReferences();
+                    final ControllerServiceReferencingComponentsEntity 
referencingComponentsEntity = 
createControllerServiceReferencingComponentsEntity(ref);
+                    
dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
+                    return dto;
+                });
+
+        final PermissionsDTO permissions = 
dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = 
dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+        final List<BulletinDTO> bulletins = 
dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
+        final List<BulletinEntity> bulletinEntities = 
bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, 
permissions.getCanRead())).collect(Collectors.toList());
+        controllerService.performValidation();
+        return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
+    }
+
+    @Override
+    public List<ProcessGroupOptionEntity> getAllProcessGroupOptions(String 
controllerServiceId) {
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        List<ProcessGroupOptionEntity> options = new ArrayList<>();
+        final ControllerServiceDTO controllerServiceDTO = 
getControllerService(controllerServiceId, true).getComponent();
+        ProcessGroupEntity currentProcessGroup = 
getProcessGroup(controllerServiceDTO.getParentGroupId());
+
+        if (currentProcessGroup.getComponent().getParentGroupId() != null) {
+            final ProcessGroupAuthorizable authorizableProcessGroupParent = 
authorizableLookup.getProcessGroup(currentProcessGroup.getComponent().getParentGroupId());
+            if 
(authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                ProcessGroupOptionEntity option = 
generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroupParent);
+                option.setText(option.getText() + " (Parent)");
+                options.add(option);
+            }
+        }
+
+        
currentProcessGroup.getComponent().getContents().getProcessGroups().forEach(processGroup
 -> {
+            final ProcessGroupAuthorizable authorizableProcessGroup = 
authorizableLookup.getProcessGroup(processGroup.getId());
+            if 
(authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                options.add(generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroup));
+            }
+        });
+
+        return options;
+    }
+
+    @Override
+    public ProcessGroupOptionEntity 
generateProcessGroupOption(ControllerServiceDTO controllerServiceDTO, 
ProcessGroupAuthorizable processGroup) {
+        List<String> conflictingComponents = 
getConflictingComponents(controllerServiceDTO, processGroup);
+
+        ProcessGroupOptionEntity option = new ProcessGroupOptionEntity();
+        option.setText(processGroup.getProcessGroup().getName());
+        option.setValue(processGroup.getProcessGroup().getIdentifier());
+        option.setDisabled(false);
+
+        if (!conflictingComponents.isEmpty()) {
+            String errorMessage = "Cannot move to this process group because 
the following components would be out of scope: ";
+            errorMessage += String.join(" ", conflictingComponents);
+            option.setDescription(errorMessage);
+            option.setDisabled(true);
+        }
+
+        return option;
+    }
+
+    @Override
+    public List<String> getConflictingComponents(ControllerServiceDTO 
controllerServiceDTO, ProcessGroupAuthorizable processGroup) {
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        List<String> conflictingComponents = new ArrayList<>();
+        
controllerServiceDTO.getReferencingComponents().forEach(referencingComponent -> 
{
+            if 
(processGroup.getProcessGroup().findProcessor(referencingComponent.getId()) == 
null
+                    && 
processGroup.getProcessGroup().findControllerService(referencingComponent.getId(),
 true, false) == null) {
+                final Authorizable componentAuthorizable = 
authorizableLookup.getControllerServiceReferencingComponent(controllerServiceDTO.getId(),
 referencingComponent.getId());
+                if (componentAuthorizable.isAuthorized(authorizer, 
RequestAction.READ, user)) {
+                    conflictingComponents.add("[" + 
referencingComponent.getComponent().getName() + "]");
+                } else {
+                    conflictingComponents.add("[" + 
referencingComponent.getId() + "]");
+                }
+            }
+        });
+
+        controllerServiceDTO.getProperties().forEach((key, value) -> {
+            try {
+                ControllerServiceEntity refControllerService = 
this.getControllerService(value, false);
+                if (refControllerService != null) {
+                    if 
(processGroup.getProcessGroup().findControllerService(value, false, true) == 
null) {
+                        ComponentAuthorizable componentAuthorizable = 
authorizableLookup.getControllerService(value);
+                        if 
(componentAuthorizable.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)) {
+                            conflictingComponents.add("[" + 
refControllerService.getComponent().getName() + "]");
+                        } else {
+                            conflictingComponents.add("[" + 
refControllerService.getId() + "]");
+                        }
+                    }
+                }
+            } catch (Exception ignored) { }

Review Comment:
   Why is the exception ignored?



##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java:
##########
@@ -2973,6 +2976,122 @@ public ControllerServiceEntity 
updateControllerService(final Revision revision,
         return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
     }
 
+    @Override
+    public ControllerServiceEntity moveControllerService(final Revision 
revision, final ControllerServiceDTO controllerServiceDTO, final String 
newProcessGroupID) {
+        // get the component, ensure we have access to it, and perform the 
move request
+        final ControllerServiceNode controllerService = 
controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
+        final RevisionUpdate<ControllerServiceDTO> snapshot = 
updateComponent(revision,
+                controllerService,
+                () -> {
+                    final ProcessGroup oldParentGroup = 
controllerService.getProcessGroup();
+                    controllerService.setMoving(true);
+                    oldParentGroup.removeControllerService(controllerService);
+                    if (!oldParentGroup.isRootGroup() && 
oldParentGroup.getParent().getIdentifier().equals(newProcessGroupID)) {
+                        // move to parent process group
+                        
oldParentGroup.getParent().addControllerService(controllerService);
+                    } else {
+                        // move to child process group
+                        
oldParentGroup.getProcessGroup(newProcessGroupID).addControllerService(controllerService);
+                    }
+                    return controllerService;
+                },
+                cs -> {
+                    final ControllerServiceDTO dto = 
dtoFactory.createControllerServiceDto(cs);
+                    final ControllerServiceReference ref = 
controllerService.getReferences();
+                    final ControllerServiceReferencingComponentsEntity 
referencingComponentsEntity = 
createControllerServiceReferencingComponentsEntity(ref);
+                    
dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
+                    return dto;
+                });
+
+        final PermissionsDTO permissions = 
dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = 
dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+        final List<BulletinDTO> bulletins = 
dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
+        final List<BulletinEntity> bulletinEntities = 
bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, 
permissions.getCanRead())).collect(Collectors.toList());
+        controllerService.performValidation();
+        return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
+    }
+
+    @Override
+    public List<ProcessGroupOptionEntity> getAllProcessGroupOptions(String 
controllerServiceId) {
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        List<ProcessGroupOptionEntity> options = new ArrayList<>();
+        final ControllerServiceDTO controllerServiceDTO = 
getControllerService(controllerServiceId, true).getComponent();
+        ProcessGroupEntity currentProcessGroup = 
getProcessGroup(controllerServiceDTO.getParentGroupId());
+
+        if (currentProcessGroup.getComponent().getParentGroupId() != null) {
+            final ProcessGroupAuthorizable authorizableProcessGroupParent = 
authorizableLookup.getProcessGroup(currentProcessGroup.getComponent().getParentGroupId());
+            if 
(authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                ProcessGroupOptionEntity option = 
generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroupParent);
+                option.setText(option.getText() + " (Parent)");
+                options.add(option);
+            }
+        }
+
+        
currentProcessGroup.getComponent().getContents().getProcessGroups().forEach(processGroup
 -> {
+            final ProcessGroupAuthorizable authorizableProcessGroup = 
authorizableLookup.getProcessGroup(processGroup.getId());
+            if 
(authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                options.add(generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroup));
+            }
+        });
+
+        return options;
+    }
+
+    @Override
+    public ProcessGroupOptionEntity 
generateProcessGroupOption(ControllerServiceDTO controllerServiceDTO, 
ProcessGroupAuthorizable processGroup) {

Review Comment:
   This `ProcessGroupOptionEntity` is very generic, whereas it seems like it 
should be more targeted to the use case in question.



##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java:
##########
@@ -2973,6 +2976,122 @@ public ControllerServiceEntity 
updateControllerService(final Revision revision,
         return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
     }
 
+    @Override
+    public ControllerServiceEntity moveControllerService(final Revision 
revision, final ControllerServiceDTO controllerServiceDTO, final String 
newProcessGroupID) {
+        // get the component, ensure we have access to it, and perform the 
move request
+        final ControllerServiceNode controllerService = 
controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
+        final RevisionUpdate<ControllerServiceDTO> snapshot = 
updateComponent(revision,
+                controllerService,
+                () -> {
+                    final ProcessGroup oldParentGroup = 
controllerService.getProcessGroup();
+                    controllerService.setMoving(true);
+                    oldParentGroup.removeControllerService(controllerService);
+                    if (!oldParentGroup.isRootGroup() && 
oldParentGroup.getParent().getIdentifier().equals(newProcessGroupID)) {
+                        // move to parent process group
+                        
oldParentGroup.getParent().addControllerService(controllerService);
+                    } else {
+                        // move to child process group
+                        
oldParentGroup.getProcessGroup(newProcessGroupID).addControllerService(controllerService);
+                    }
+                    return controllerService;
+                },
+                cs -> {
+                    final ControllerServiceDTO dto = 
dtoFactory.createControllerServiceDto(cs);
+                    final ControllerServiceReference ref = 
controllerService.getReferences();
+                    final ControllerServiceReferencingComponentsEntity 
referencingComponentsEntity = 
createControllerServiceReferencingComponentsEntity(ref);
+                    
dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
+                    return dto;
+                });
+
+        final PermissionsDTO permissions = 
dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = 
dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+        final List<BulletinDTO> bulletins = 
dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
+        final List<BulletinEntity> bulletinEntities = 
bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, 
permissions.getCanRead())).collect(Collectors.toList());
+        controllerService.performValidation();
+        return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
+    }
+
+    @Override
+    public List<ProcessGroupOptionEntity> getAllProcessGroupOptions(String 
controllerServiceId) {
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        List<ProcessGroupOptionEntity> options = new ArrayList<>();
+        final ControllerServiceDTO controllerServiceDTO = 
getControllerService(controllerServiceId, true).getComponent();
+        ProcessGroupEntity currentProcessGroup = 
getProcessGroup(controllerServiceDTO.getParentGroupId());
+
+        if (currentProcessGroup.getComponent().getParentGroupId() != null) {
+            final ProcessGroupAuthorizable authorizableProcessGroupParent = 
authorizableLookup.getProcessGroup(currentProcessGroup.getComponent().getParentGroupId());
+            if 
(authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                ProcessGroupOptionEntity option = 
generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroupParent);
+                option.setText(option.getText() + " (Parent)");
+                options.add(option);
+            }
+        }
+
+        
currentProcessGroup.getComponent().getContents().getProcessGroups().forEach(processGroup
 -> {
+            final ProcessGroupAuthorizable authorizableProcessGroup = 
authorizableLookup.getProcessGroup(processGroup.getId());
+            if 
(authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                options.add(generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroup));
+            }
+        });
+
+        return options;
+    }
+
+    @Override
+    public ProcessGroupOptionEntity 
generateProcessGroupOption(ControllerServiceDTO controllerServiceDTO, 
ProcessGroupAuthorizable processGroup) {
+        List<String> conflictingComponents = 
getConflictingComponents(controllerServiceDTO, processGroup);
+
+        ProcessGroupOptionEntity option = new ProcessGroupOptionEntity();
+        option.setText(processGroup.getProcessGroup().getName());
+        option.setValue(processGroup.getProcessGroup().getIdentifier());
+        option.setDisabled(false);
+
+        if (!conflictingComponents.isEmpty()) {
+            String errorMessage = "Cannot move to this process group because 
the following components would be out of scope: ";
+            errorMessage += String.join(" ", conflictingComponents);

Review Comment:
   This message declaration could be collapsed into a single line.



##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java:
##########
@@ -664,6 +668,155 @@ public Response updateControllerService(
         );
     }
 
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/move-options")

Review Comment:
   `move-options` seems too generic for the purpose of the method. It seems 
more accurate to name this in relation to Process Groups, such as Destination 
Process Groups, or Available Process Groups.



##########
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java:
##########
@@ -134,6 +135,7 @@ public StandardControllerServiceNode(final 
LoggableComponent<ControllerService>
         super(id, validationContextFactory, serviceProvider, componentType, 
componentCanonicalClass, reloadComponent, extensionManager, validationTrigger, 
isExtensionMissing);
         this.serviceProvider = serviceProvider;
         this.active = new AtomicBoolean();
+        this.moving = new AtomicBoolean(false);

Review Comment:
   Adding this temporary state to the Node object raises a number of questions 
about the implementation, as it does not seem to fit with the other stateful 
properties.



##########
nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java:
##########
@@ -2580,7 +2580,9 @@ public void removeControllerService(final 
ControllerServiceNode service) {
                 throw new IllegalStateException("ControllerService " + 
service.getIdentifier() + " is not a member of this Process Group");
             }
 
-            service.verifyCanDelete();
+            if (!service.isMoving()) {

Review Comment:
   Is there a reason for not checking moving status inside the 
`verifyCanDelete` method?



##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java:
##########
@@ -2973,6 +2976,122 @@ public ControllerServiceEntity 
updateControllerService(final Revision revision,
         return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
     }
 
+    @Override
+    public ControllerServiceEntity moveControllerService(final Revision 
revision, final ControllerServiceDTO controllerServiceDTO, final String 
newProcessGroupID) {
+        // get the component, ensure we have access to it, and perform the 
move request
+        final ControllerServiceNode controllerService = 
controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
+        final RevisionUpdate<ControllerServiceDTO> snapshot = 
updateComponent(revision,
+                controllerService,
+                () -> {
+                    final ProcessGroup oldParentGroup = 
controllerService.getProcessGroup();
+                    controllerService.setMoving(true);
+                    oldParentGroup.removeControllerService(controllerService);
+                    if (!oldParentGroup.isRootGroup() && 
oldParentGroup.getParent().getIdentifier().equals(newProcessGroupID)) {
+                        // move to parent process group
+                        
oldParentGroup.getParent().addControllerService(controllerService);
+                    } else {
+                        // move to child process group
+                        
oldParentGroup.getProcessGroup(newProcessGroupID).addControllerService(controllerService);
+                    }
+                    return controllerService;
+                },
+                cs -> {
+                    final ControllerServiceDTO dto = 
dtoFactory.createControllerServiceDto(cs);
+                    final ControllerServiceReference ref = 
controllerService.getReferences();
+                    final ControllerServiceReferencingComponentsEntity 
referencingComponentsEntity = 
createControllerServiceReferencingComponentsEntity(ref);
+                    
dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
+                    return dto;
+                });
+
+        final PermissionsDTO permissions = 
dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = 
dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
+        final List<BulletinDTO> bulletins = 
dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
+        final List<BulletinEntity> bulletinEntities = 
bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, 
permissions.getCanRead())).collect(Collectors.toList());
+        controllerService.performValidation();
+        return 
entityFactory.createControllerServiceEntity(snapshot.getComponent(), 
dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, 
operatePermissions, bulletinEntities);
+    }
+
+    @Override
+    public List<ProcessGroupOptionEntity> getAllProcessGroupOptions(String 
controllerServiceId) {
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        List<ProcessGroupOptionEntity> options = new ArrayList<>();
+        final ControllerServiceDTO controllerServiceDTO = 
getControllerService(controllerServiceId, true).getComponent();
+        ProcessGroupEntity currentProcessGroup = 
getProcessGroup(controllerServiceDTO.getParentGroupId());
+
+        if (currentProcessGroup.getComponent().getParentGroupId() != null) {
+            final ProcessGroupAuthorizable authorizableProcessGroupParent = 
authorizableLookup.getProcessGroup(currentProcessGroup.getComponent().getParentGroupId());
+            if 
(authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroupParent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                ProcessGroupOptionEntity option = 
generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroupParent);
+                option.setText(option.getText() + " (Parent)");
+                options.add(option);
+            }
+        }
+
+        
currentProcessGroup.getComponent().getContents().getProcessGroups().forEach(processGroup
 -> {
+            final ProcessGroupAuthorizable authorizableProcessGroup = 
authorizableLookup.getProcessGroup(processGroup.getId());
+            if 
(authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.READ, user)
+                    && 
authorizableProcessGroup.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user)) {
+                options.add(generateProcessGroupOption(controllerServiceDTO, 
authorizableProcessGroup));
+            }
+        });
+
+        return options;
+    }
+
+    @Override
+    public ProcessGroupOptionEntity 
generateProcessGroupOption(ControllerServiceDTO controllerServiceDTO, 
ProcessGroupAuthorizable processGroup) {
+        List<String> conflictingComponents = 
getConflictingComponents(controllerServiceDTO, processGroup);
+
+        ProcessGroupOptionEntity option = new ProcessGroupOptionEntity();
+        option.setText(processGroup.getProcessGroup().getName());
+        option.setValue(processGroup.getProcessGroup().getIdentifier());
+        option.setDisabled(false);
+
+        if (!conflictingComponents.isEmpty()) {
+            String errorMessage = "Cannot move to this process group because 
the following components would be out of scope: ";
+            errorMessage += String.join(" ", conflictingComponents);
+            option.setDescription(errorMessage);
+            option.setDisabled(true);
+        }
+
+        return option;
+    }
+
+    @Override
+    public List<String> getConflictingComponents(ControllerServiceDTO 
controllerServiceDTO, ProcessGroupAuthorizable processGroup) {

Review Comment:
   This method returns the name or ID of referencing components, but 
"Conflicting Components" doesn't make that clear, so it seems like the naming 
is questionable, and it would probably be clearer to return a list of objects.



##########
nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java:
##########
@@ -664,6 +668,155 @@ public Response updateControllerService(
         );
     }
 
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/move-options")
+    @Operation(
+            summary = "Gets the move options for a controller service",
+            responses = {
+                    @ApiResponse(content = @Content(schema = 
@Schema(implementation = ProcessGroupOptionEntity.class))),
+                    @ApiResponse(responseCode = "400", description = "NiFi was 
unable to complete the request because it was invalid. The request should not 
be retried without modification."),
+                    @ApiResponse(responseCode = "401", description = "Client 
could not be authenticated."),
+                    @ApiResponse(responseCode = "403", description = "Client 
is not authorized to make this request."),
+                    @ApiResponse(responseCode = "404", description = "The 
specified resource could not be found."),
+                    @ApiResponse(responseCode = "409", description = "The 
request was valid but NiFi was not in the appropriate state to process it.")
+            },
+            security = {
+                    @SecurityRequirement(name = "Read - /flow")
+            }
+    )
+    public Response getProcessGroupOptions(
+            @Parameter(description = "The controller service id.")
+            @PathParam("id") final String controllerServiceId) {
+
+        if (controllerServiceId == null) {
+            throw new IllegalArgumentException("Controller service id must be 
specified.");
+        }
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.GET);
+        }
+
+        AtomicReference<List<ProcessGroupOptionEntity>> options = new 
AtomicReference<>();
+        serviceFacade.authorizeAccess(lookup -> {
+            final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+            final ComponentAuthorizable authorizableControllerService = 
lookup.getControllerService(controllerServiceId);
+            boolean authorized = 
authorizableControllerService.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user);
+
+            final ControllerServiceDTO controllerServiceDTO = 
serviceFacade.getControllerService(controllerServiceId, true).getComponent();
+
+            final ProcessGroupAuthorizable authorizableProcessGroupCurrent = 
lookup.getProcessGroup(controllerServiceDTO.getParentGroupId());
+            authorized = authorized && 
authorizableProcessGroupCurrent.getAuthorizable().isAuthorized(authorizer, 
RequestAction.WRITE, user);
+
+            if (authorized) {
+                
options.set(serviceFacade.getAllProcessGroupOptions(controllerServiceId));
+            }
+        });
+
+        ProcessGroupOptionsEntity processGroupOptionsEntity = new 
ProcessGroupOptionsEntity();
+        processGroupOptionsEntity.setProcessGroupOptionEntities(options.get());
+
+        return generateOkResponse(processGroupOptionsEntity).build();
+    }
+
+    /**
+     * Moves the specified Controller Service to parent/child process groups.
+     *
+     * @param id The id of the controller service to update.
+     * @param requestControllerServiceEntity A controllerServiceEntity.
+     * @return A controllerServiceEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/move")

Review Comment:
   Although the REST API is not completely consistent, the general pattern is 
to have the path as a resource, which would change the approach to have the 
Process Group in the path.



##########
nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ProcessGroupOptionsEndpointMerger.java:
##########
@@ -0,0 +1,44 @@
+package org.apache.nifi.cluster.coordination.http.endpoints;

Review Comment:
   This class is missing a license header.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to