NIFI-6638: Empty multiple queues at once at different flow levels#3700
NIFI-6638: Empty multiple queues at once at different flow levels#3700VinceCastro wants to merge 6 commits intoapache:masterfrom
Conversation
Added method "containsConnections" and "containsProcessGroups" to check if a selection contains respectively one or more connections and one or more process groups. Added method "toReadableBytes" to convert a certain number indicating bytes to a human readable string. Added methods to check if one or more queues can be emptied or not. Enriched the context menu with functions to empty queues at different levels of the flow. Added methods to get all connections from one or more process groups. Added a method to empty one or more connections. Added actions to empty the selected connections, one or more process groups connections, one or more process groups connections in a recursive way. Widen context menu in order to make room for the new empty queue menu entry labels. Tiny css modification in order to allow dialog headers to contain an always automatically centered text that can now go on a new line if allowed width has been exceeded. Updated doc with images showing the menu supporting the new empty queues functionality.
|
Are we sure we need that many functionalities?
Are there use cases in which you are interested into emptying all queue of a processor group which is not the current one AND at the same time we do not want to delete the queue recursively? I think that the most useful features are:
And therefore I'd start by introducing these ones (which are by chance the most comprehensive, so it would be easy to add the other ones in a second moment) and potentially add the other cases with another PR |
| * @Param {string} actionName | ||
| * @param {Array} connections | ||
| * @Param {Array} errors | ||
| * |
There was a problem hiding this comment.
nit: remove empty line
| * |
| @@ -564,7 +564,7 @@ div.nifi-tooltip { | |||
| background-color:rgba(249,250,251,0.97); /*tint base-color 96%*/ | |||
There was a problem hiding this comment.
| background-color:rgba(249,250,251,0.97); /*tint base-color 96%*/ | |
| background-color: rgba(249,250,251,0.97); /*tint base-color 96%*/ |
| @@ -564,7 +564,7 @@ div.nifi-tooltip { | |||
| background-color:rgba(249,250,251,0.97); /*tint base-color 96%*/ | |||
| border:1px solid #004849; /*link-color*/ | |||
There was a problem hiding this comment.
| border:1px solid #004849; /*link-color*/ | |
| border: 1px solid #004849; /*link-color*/ |
| width: 215px; | ||
| width: 235px; | ||
| max-height: inherit; | ||
| color:#004849 |
There was a problem hiding this comment.
| color:#004849 | |
| color: #004849; |
|
|
||
| // set the progress bar to a certain percentage | ||
| var setProgressBar = function (percentComplete) { | ||
| if($("#drop-request-percent-complete .progress-label").length) { |
There was a problem hiding this comment.
| if($("#drop-request-percent-complete .progress-label").length) { | |
| if ($("#drop-request-percent-complete.progress-label").length) { |
| var actionName = ''; | ||
| var dialogContent = ''; | ||
|
|
||
| if(selectionSize === 0) { |
There was a problem hiding this comment.
| if(selectionSize === 0) { | |
| if (selectionSize === 0) { |
| dialogContent = 'Are you sure you want to empty all queues inside the current process group and all its sub process groups (recursive)? All FlowFiles waiting at the time of the request will be removed.'; | ||
| connections = d3.selectAll('g.connection').data(); | ||
| } | ||
| else if(selectionSize === 1) { |
There was a problem hiding this comment.
| else if(selectionSize === 1) { | |
| else if (selectionSize === 1) { |
| .map(function (processGroup) { | ||
| return processGroup.id; | ||
| }) | ||
| : |
There was a problem hiding this comment.
Would you rather prefer the following?
var processGroupIDs;
if (selectionSize === 0) {
processGroupIDs = ...
}
else {
processGroupIDs = ...
}
There was a problem hiding this comment.
This is more readable and coherent with the rest of the codebase IMO
| nfErrorHandler.handleAjaxError(xhr, status, error); | ||
| } else { | ||
| completeDropRequest() | ||
| if(connections.length === 0 && errors.length === 0) { |
There was a problem hiding this comment.
| if(connections.length === 0 && errors.length === 0) { | |
| if (connections.length === 0 && errors.length === 0) { |
| toReadableBytes: function (bytes) { | ||
| var sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; | ||
|
|
||
| if(bytes === 0) { |
There was a problem hiding this comment.
| if(bytes === 0) { | |
| if (bytes === 0) { |
|
Some minor style fixes in the suggestions |
|
@VinceCastro Thanks for the PR! This capability is important and is something that many folks have requested. The proposed solution will perform an unbounded number of requests in order to clear all nested queues. I think we need to change this as this solution likely won't scale well with larger data flows. When the subject to the action is a process group, we should issue a single request to the backend with a flag indicating if the request is recursive. The clear queue action is an asynchronous request that the UI polls while the action completes. When the action encompasses multiple queues we should be able to provide this context with the same asynchronous request/response model. |
|
Got it! So you suggest to move the empty multiple queues logic from the front-end to the back-end side by adding one or more rest api to the exsisting endpoints. We may have cases where the user wants to empty different selected process groups at once, or different selected queues at once, so I would suggest to add two rest api to the "FlowFile Queues" endpoint: the first one should accept a list of process group IDs in order to empty the queues inside them(recursively or not); the second one should accept a list of connections IDs to empty. We may collect all IDs into a json body. This way we can efficiently cover all cases where the user wants to:
What do you think about? @adarmiento you stated:
What if I want to empty only a subset of the flow I'm currently viewing? In that case features like "empty the selected process group" or "empty the selected process groups" are necessary, do you agree? However, as mentioned by @adarmiento
I have doubts about the usefulness of the non recursive actions. Are there use cases where we do not really need to empty a process group recursively? Do you guys @mcgilman @adarmiento suggest to add the non recursive actions or postpone them to another pr? |
|
I'd say that to empty a subset of the flow I am currently viewing, I'd go for the "selected queues: empty the selected queues", however, I understand your urge to add more convenience methods. |
|
@VinceCastro I'd leave the choice to keep it as a single PR, or split it, up to your comfort level with the back-end code. This is your first contribution, have you looked at the back-end layer? A few file pointers in case you want to look into this:
This is a POST for a single Queue. The ID of the queue goes in the URL. You might be able to replace this with the ID of a process group and then do a lookup, but probably easier to create a new end point for But what about submitting multiple queues async? I think you would need another endpoint there too, and have the body of the post contain some JSON describing the list. Interestingly, the |
|
Hi and thanks for the useful tips! I'm currently checking the back-end out, I'm going to decide later whether or not to split the pr. |
…pFlowFileRequest constructor signature
…es like build failures in Java 11 due to JavaFX being separated from jre
|
@adarmiento @mcgilman @patricker I moved the multiple queues emptying logic from the front-end to the back-end side, could you please check the changes out? |
|
Could you please add some more test cases enforcing your expected behavior? |
| DropFlowFileState state = null; | ||
| boolean allFinished = true; | ||
| String failureReason = null; | ||
| List<DropRequestDTO.FailureReason> failureReason = null; |
There was a problem hiding this comment.
If this is a List, maybe it would be more explanatory to refactor this as failureReasons
|
|
||
| public DropFlowFileRequest(final String identifier) { | ||
| this.identifier = identifier; | ||
| public DropFlowFileRequest(final String requestIdentifier,final String connectionIdentifier) { |
There was a problem hiding this comment.
nit
| public DropFlowFileRequest(final String requestIdentifier,final String connectionIdentifier) { | |
| public DropFlowFileRequest(final String requestIdentifier, final String connectionIdentifier) { |
| import java.util.Set; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.ArrayList; | ||
| import java.util.Map; | ||
| import java.util.Collections; | ||
| import java.util.Comparator; |
There was a problem hiding this comment.
Nit: Usually import are left in alphabetical order (package then classes)
| } | ||
| } | ||
|
|
||
| multiQueueDropRequestMap.put(requestIdentifier,new HashSet<>()); |
There was a problem hiding this comment.
| multiQueueDropRequestMap.put(requestIdentifier,new HashSet<>()); | |
| multiQueueDropRequestMap.put(requestIdentifier, new HashSet<>()); |
| public synchronized LoadBalanceCompression getLoadBalanceCompression() { | ||
| return compression; | ||
| } | ||
|
|
| .max() | ||
| .orElseGet(() -> dto.getSubmissionTime().getTime())) | ||
| ); | ||
| //dto.setState(dropRequest.getState().toString()); |
| dto.setFailureReasons(dropRequests.stream() | ||
| .filter(dropRequest -> dropRequest.getFailureReason() != null) | ||
| .map(dropRequest -> { | ||
| return new DropRequestDTO.FailureReason(dropRequest.getConnectionIdentifier(),dropRequest.getFailureReason()); }) |
There was a problem hiding this comment.
| return new DropRequestDTO.FailureReason(dropRequest.getConnectionIdentifier(),dropRequest.getFailureReason()); }) | |
| return new DropRequestDTO.FailureReason(dropRequest.getConnectionIdentifier(), dropRequest.getFailureReason()); }) |
| dto.setFinished(isDropRequestComplete( | ||
| dropRequests.stream() | ||
| .map(DropFlowFileStatus::getState) | ||
| .reduce(DropFlowFileState.COMPLETE,(a,b) -> { |
There was a problem hiding this comment.
| .reduce(DropFlowFileState.COMPLETE,(a,b) -> { | |
| .reduce(DropFlowFileState.COMPLETE, (a,b) -> { |
| return dropRequest; | ||
| } | ||
|
|
||
| public static Set<DropFlowFileStatus> dropFlowFiles(final Set<FlowFileQueue> flowFileQueues, final String requestIdentifier, final String requestor) { |
There was a problem hiding this comment.
Could you add a little doc to this method?
| if (multiQueueDropRequestMap.size() > 50) { | ||
| final Set<String> toDrop = new HashSet<>(); | ||
|
|
||
| for (final Map.Entry<String, Set<DropFlowFileRequest>> entry : multiQueueDropRequestMap.entrySet()) { |
There was a problem hiding this comment.
This part looks very complex. Could you please add a little comment?
|
closing due to inactivity. but looks like it was a great effort/contrib. hopefully restored at some point |
This PR also refers to NIFI-3632, NIFI-5329 and NIFI-4308.
Description of PR
Replaced the current "Empty queue" button in the context menu with an "Empty queues" sub-menu providing different options to empty multiple queues at different levels of the flow depending on the selected components.
In particular, different buttons have been added to the new "Empty queues" sub-menu:
Errors management: if the emptying process fails to empty one or more queues, it keeps going till all the selected queues are processed. After that, the final summary dialog eventually provides information about the errors faced in emptying one or more queues.
In order to streamline the review of the contribution we ask you
to ensure the following steps have been taken:
For all changes:
Is there a JIRA ticket associated with this PR? Is it referenced
in the commit message?
Does your PR title start with NIFI-XXXX where XXXX is the JIRA number you are trying to resolve? Pay particular attention to the hyphen "-" character.
Has your PR been rebased against the latest commit within the target branch (typically
master)?Is your initial contribution a single, squashed commit? Additional commits in response to PR reviewer feedback should be made on this branch and pushed to allow change tracking. Do not
squashor use--forcewhen pushing to allow for clean monitoring of changes.For code changes:
mvn -Pcontrib-check clean installat the rootnififolder?LICENSEfile, including the mainLICENSEfile undernifi-assembly?NOTICEfile, including the mainNOTICEfile found undernifi-assembly?.displayNamein addition to .name (programmatic access) for each of the new properties?For documentation related changes:
Note:
Please ensure that once the PR is submitted, you check travis-ci for build issues and submit an update to your PR as soon as possible.