Skip to content
This repository has been archived by the owner on Sep 5, 2024. It is now read-only.

[#370] Cancel a migration's in-progress tasks #517

Merged
merged 14 commits into from
Jul 30, 2018

Conversation

AparnaKarve
Copy link
Contributor

@AparnaKarve AparnaKarve commented Jul 26, 2018

Fixes #370

screen shot 2018-07-25 at 6 49 17 pm


screen shot 2018-07-27 at 7 53 44 am


screen shot 2018-07-25 at 6 56 49 pm

This is a continuation to @priley86 's and @mturley 's work...
Includes -

  • Canceling Migration Tasks
  • Displays the fa-ban icon for canceled tasks
  • Notification about the the Cancellation
  • Managing the checkboxes and Cancel Migration button state appropriately

https://bugzilla.redhat.com/show_bug.cgi?id=1564257

To be merged after ManageIQ/manageiq-api#421


const cancelSuccessMsg = __('Migration canceled successfully for VMs: ');
const vmNames = [];
tasks.map(task => vmNames.push(task.vmName));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here, we can just do

const vmNames = tasks.map(task => task.vmName)

const cancelSuccessMsg = __('Migration canceled successfully for VMs: ');
const vmNames = [];
tasks.map(task => vmNames.push(task.vmName));
const cancelSuccessMsgWithVMNames = `${cancelSuccessMsg} ${vmNames}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit, but if we pass an array (with multiple entries) into the template literal, there will not be spaces after each comma

Copy link
Contributor

@mturley mturley Jul 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, it might be worth doing something like this:

const vmNamesStr = vmNames.join(', ');
const cancelSuccessMsgWithVMNames = `${cancelSuccessMsg} ${vmNamesStr}`;

edit: to combine with the above comment, it could all just be:

const vmNames = tasks.map(task => task.vmName).join(', ');
const cancelSuccessMsgWithVMNames = `${cancelSuccessMsg} ${vmNames}`;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip.
Addressed this in the last commit

Copy link
Contributor

@michaelkro michaelkro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bulk selector gets checked after canceling a task (this is actually observable in the first screenshot). Steps to reproduce

  1. Cancel a task
  2. Notice that the bulk selector is now checked

// gather the selected tasks in state and feed them through
const { selectedCancelTasks } = this.state;
const { cancelPlanRequestTasksAction, cancelPlanRequestTasksUrl } = this.props;
cancelPlanRequestTasksAction(cancelPlanRequestTasksUrl, selectedCancelTasks);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to refetch the tasks here. Currently, after confirming the cancel, the success notification appears, but we need to wait for the next poll before the ListItem is updated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did not do a refetch here.

The listview reflects the cancelled state immediately upon a successful return from the API POST cancel action.
I have used markedForCancellation redux state to mark the cancelled items in the listview before the task is officially marked with cancel_requested=true by the API.

Copy link
Contributor

@michaelkro michaelkro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Observation about the bulk selector & checkbox state after cancellation. Does not affect functionality, but wondering what the intended behavior is for this. Steps to reproduce

  1. Cancel a task
  2. Notice that the checkbox for the ListItem is disabled, but checked
  3. Using the bulk selector, unselect all
  4. Now the checkbox for the ListItem is not checked

Copy link
Contributor

@michaelkro michaelkro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to comment

Can attempt to cancel a already cancelled task. Steps to reproduce

  1. Cancel a task. Notice that the checkbox for the ListItem is disabled, but checked.
  2. Attempt to cancel another task
  3. In the confirm modal, the already cancelled plan appears along with the new one

@AparnaKarve
Copy link
Contributor Author

@michaelkro I think the expected behavior should be -

  1. bulk selector should always be selectable --
  • it will be a no-op, if all the tasks are either already canceled or completed
  • Otherwise it will select/unselect tasks that are not canceled/completed
  1. From your observation above, [#370] Cancel a migration's in-progress tasks #517 (review), the confirm modal, should not mention an already cancelled plan along with the new one

Let me know if you can think of more.

Copy link
Member

@AllenBW AllenBW left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eeekkkk so trying this out.. latest miq master its not a happy camper...

a post to /api/request_tasks with the body {"action":"cancel","resources":[{"id":"173"}]} returns {"error":{"kind":"bad_request","message":"Unsupported Action cancel for the request_tasks collection specified","klass":"Api::BadRequestError"}}

Maybe there's a branch somewhere that i neeeeed to be on?

@@ -29,7 +30,8 @@ export const STATUS_MESSAGE_KEYS = {
VALIDATING: 'Validating',
VM_MIGRATIONS_COMPLETED: 'VM Transformations completed',
VM_MIGRATED: 'Virtual machine migrated',
VM_MIGRATIONS_FAILED: 'VM Transformations failed'
VM_MIGRATIONS_FAILED: 'VM Transformations failed',
CANCELLED: 'VM cancelled'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe want to say VM Migration canceled here and elsewhere...we follow the pattern of item/action/result elsewhere 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed this constant for now, since we don't know yet what string the state machine actually uses for a cancelled VM.

This will become more clear after some appliance testing and once Fabien's cancellation implementation is complete

@@ -79,6 +81,10 @@ const processVMTasks = vmTasks => {
taskDetails.options.playbooks = task.options.playbooks;
}

if (task.options.cancel_requested) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be done on the item creation above, say line 69,

cancel_requested: task.options.cancel_requested

(but I don't know what the value of that attribute is, so maybe direct assignment doesn't make sense)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in the last commit

</React.Fragment>
}
cancelButtonLabel={__('No')}
confirmButtonLabel={__('Cancel Migrations')}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmation buttons thus far have been single word actions, without specifying the item on which the action is taken, I know that this might have been specified in the mock... but it seems to introduce a design inconsistency @vconzola thoughts?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AllenBW This is a classic UX conundrum - how to confirm an action when that action is cancelling something. The normal button labels (OK, Cancel) lead to confusion - Does Cancel cancel or cancel the Cancel. So I ran this by Stacey (our info architect) and this is what we came up with. Maybe not consistent, but in this case necessary.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AllenBW @vconzola Just my $0.02, but I feel like the single-word restriction is specifically confusing in this "cancelling the cancel" case. I've been happy with products that I see use the opposite tactic when one word isn't enough-- very explicit and verbose buttons.

"Yes, cancel these migrations" / "No, do not cancel anything" or something to that effect.

I think using one word where it's not confusing, and then just going straight for a sentence otherwise instead of trying to find the two word button that works, is good. End of non-designer opinion.

@AparnaKarve
Copy link
Contributor Author

@AllenBW You will need ManageIQ/manageiq-api#421

@AllenBW
Copy link
Member

AllenBW commented Jul 26, 2018

Ahhhh that's the missing piece, thanks @AparnaKarve !

const { selectedCancelTasks } = this.state;
const { cancelPlanRequestTasksAction, cancelPlanRequestTasksUrl } = this.props;
cancelPlanRequestTasksAction(cancelPlanRequestTasksUrl, selectedCancelTasks);
this.setState({ showConfirmCancel: false });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modal window isn't hiding after firing this 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you check again? The modal seems to be hiding OK for me.

@AllenBW
Copy link
Member

AllenBW commented Jul 26, 2018

It might be something wacky I did... or desired behavior... but after canceling two in progress migrations... they get the plan appears to be stuck in inprogress purgatory... meaning... it just hangs out inprogress tab... with no action aside from download logs available

@AparnaKarve
Copy link
Contributor Author

I have noticed some strange behavior with Cancel locally as well... hence I think the real valid test of canceling a migration should be done on an appliance - it seemed to work a lot better on an appliance yesterday.

So based on the testing that can be done locally, I will try and fix some of the UI/UX issues that @michaelkro noticed above, but the bulk of the testing needs to be done on an appliance.

I will try and setup an appliance sometime today for testing this.

@vconzola
Copy link

@michaelkro I concur with @AparnaKarve expected behavior comment above.

@mturley
Copy link
Contributor

mturley commented Jul 26, 2018

Working on some of the quirky behavior around the checkboxes, since it's related to stuff I did. I'll have a commit for @AparnaKarve to cherry-pick shortly

@mturley
Copy link
Contributor

mturley commented Jul 26, 2018

Posting this here for posterity, it might conflict with @AparnaKarve's redux stuff, but it should fix some behavior around the bulk selector: mturley@a65868d

@AparnaKarve
Copy link
Contributor Author

After implementing the redux approach, came across a few use cases where the user can sneak in a Cancel on an already canceled task (in the brief window until the next polling happens)

Trying to address some of these cases...

@AparnaKarve AparnaKarve force-pushed the cancel_in_progress_migrations branch 2 times, most recently from 12b7d3f to 7c28590 Compare July 27, 2018 14:55
@AparnaKarve
Copy link
Contributor Author

@michaelkro @AllenBW @mturley Please test/review when you get a chance.

the plan appears to be stuck in inprogress purgatory... meaning... it just hangs out inprogress tab

@AllenBW We usually don't see this issue on appliances... although I have tried to address this in c3fe254,
86acf0d and
407f8b9

I will try and setup an appliance to test this, but note that, currently migrations are failing on the appliances in the "Pre-Migration" step - we need an automate PR to fix this

Note for future reference: `taskCancelled` indicates that a task has already been cancelled (inferred from the API output)
whereas `taskCancelling` indicates a task that was marked for cancellation in the UI
Both states will display the `fa-ban` icon in the listview and display the text `Canceled Migration` for the task
- Remove completed task from selection
- Apply cancellation for incomplete tasks only
unknown status messages are usually about stalled migrations, hence the status message - `VM migrations stalled`
@mturley
Copy link
Contributor

mturley commented Jul 27, 2018

Thank you so much for your hard work on this @AparnaKarve.

export const cancelPlanRequestTasksAction = (url, tasks) => dispatch => {
const completedTasks = [];
const incompleteTasks = [];
tasks.map(t => (t.completed ? completedTasks.push(t) : incompleteTasks.push(t)));
Copy link
Contributor

@michaelkro michaelkro Jul 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this ultimately has the desired effect, I think we may want to use forEach here. In my experience, we use map when we want to do something with the returned array. In this case, it seems that we are iterating through tasks (rather than mapping it to a new array to be used later)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beat me to it!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in the last commit

Copy link
Contributor

@mturley mturley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not going to request changes, because these are pretty minor nit picks for readability.

export const cancelPlanRequestTasksAction = (url, tasks) => dispatch => {
const completedTasks = [];
const incompleteTasks = [];
tasks.map(t => (t.completed ? completedTasks.push(t) : incompleteTasks.push(t)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you don't use map's return value here, it could be a forEach instead.. but it really doesn't matter, and if this is the only thing I can find to comment on, I think your code looks great!

@@ -50,21 +56,32 @@ const excludeDownloadDoneTaskId = (allDownloadLogInProgressTaskIds, taskId) =>
const includeDownloadInProgressTaskId = (allDownloadLogInProgressTaskIds, taskId) =>
allDownloadLogInProgressTaskIds ? allDownloadLogInProgressTaskIds.concat(taskId) : [taskId];

const removeCancelledTasksFromSelection = (allSelectedTasksForCancel, alreadyCancelledTasks) => {
Copy link
Contributor

@mturley mturley Jul 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, but a little odd looking, and will run in n^2 time (I think? been a while since big-O lectures). Here's my approach on this one, taking the map out and only using one filter, and then using every which will short circuit if it sees a non-matching element.

Not tested, but:

const removeCancelledTasksFromSelection = (allSelectedTasksForCancel, alreadyCancelledTasks) => (
  allSelectedTasksForCancel.filter(selectedTask =>
    alreadyCancelledTasks.every(cancelledTask => selectedTask.id !== cancelledTask.id)
  )
);

Not that we need to optimize for runtime here, but I think that's more readable too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion.

I have applied this change in the last commit.

Copy link
Contributor

@michaelkro michaelkro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker

Bulk selector becomes checked when there are no selectable tasks

fullscreen_7_27_18__12_05_pm

@mturley
Copy link
Contributor

mturley commented Jul 27, 2018

@michaelkro I think that's the bug I thought we were seeing at first yesterday, which should be fixed by my commit here in the old implementation: mturley@a65868d. @AparnaKarve I agree that it's not a blocker, but you could port this line from my commit into your logic to fix this.

const allSelected = !noneSelected && selectedCancelTasks.length === incompleteTasks.length;

(it's the !noneSelected that we need)

return {
filteredTasks,
incompleteTasks,
allSelected: selectedTasksForCancel.length === incompleteTasks.length,
Copy link
Contributor

@mturley mturley Jul 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, @AparnaKarve I didn't notice you based your work on a slightly older version of getCancelSelectionState. I pushed a newer one on Wednesday that fixed the bug @michaelkro was just reporting (select all checkbox is checked when there are no selectable tasks).

We just need to pull noneSelected up into a variable, and then do allSelected: !noneSelected && ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the bulk selector issues in the last commit

@michaelkro
Copy link
Contributor

I've been trying (unsuccessfully) to find what is making this happen, but it looks like the listview resorts after a cancellation request (after the next poll)

resort-gif

Copy link
Member

@AllenBW AllenBW left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG2M!!

Any final input @michaelkro @mturley ?

Copy link
Contributor

@michaelkro michaelkro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resorting thing is the only thing that I'm a little concerned about, but it could be due to local environment quirks. I'm ok with handling that as a separate issue

@AparnaKarve
Copy link
Contributor Author

Ok, so just a reminder that we need the API PR merged in first before this.

While I have some time until the API PR is merged, let me check what more I can address from the above comments...
(plus squashing some commits is also still TBD)

@michaelkro The resorting thing is independent of this PR (KK also mentioned to me about it once) - so yes, it could be handled as a separate issue.

@mturley
Copy link
Contributor

mturley commented Jul 27, 2018

Code all looks good to me, I haven't tested your new commits locally yet since yesterday morning though. While you're wrapping up I'll see if I can find any more issues.

Copy link
Contributor

@mturley mturley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks again @AparnaKarve @michaelkro and @priley86.

@AparnaKarve AparnaKarve force-pushed the cancel_in_progress_migrations branch from 7c28590 to 5f9c89b Compare July 27, 2018 17:43
@AparnaKarve
Copy link
Contributor Author

Thanks everyone for the reviews and testing!

I have done yet another round of addressing feedback - 5f9c89b
... but I am gonna stop now.

Currently keeping an eye on the API PR... will let you all know when this can be merged.

Copy link
Contributor

@mturley mturley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks so much for taking this over @AparnaKarve .

(allSelectedTasksForCancel = allSelectedTasksForCancel.filter(element => element.id !== cancelledTask.id))
const removeCancelledTasksFromSelection = (allSelectedTasksForCancel, alreadyCancelledTasks) =>
allSelectedTasksForCancel.filter(selectedTask =>
alreadyCancelledTasks.every(cancelledTask => selectedTask.id !== cancelledTask.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 I love the lesser-known Array methods :)

@@ -285,7 +285,7 @@ class PlanRequestDetailList extends React.Component {
return {
filteredTasks,
incompleteTasks,
allSelected: selectedTasksForCancel.length === incompleteTasks.length,
allSelected: selectedTasksForCancel.length === incompleteTasks.length && selectedTasksForCancel.length > 0,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@michaelkro michaelkro merged commit 445c6a1 into ManageIQ:master Jul 30, 2018
@AparnaKarve AparnaKarve deleted the cancel_in_progress_migrations branch July 30, 2018 23:12
AparnaKarve pushed a commit to AparnaKarve/manageiq-v2v that referenced this pull request Jul 31, 2018
…igrations

[ManageIQ#370] Cancel a migration's in-progress tasks
@simaishi
Copy link
Contributor

simaishi commented Aug 1, 2018

Backported to Gaprindashvili via #525

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants