Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Invoke Node Drains #6819

Merged
merged 49 commits into from
Jan 24, 2020
Merged

UI: Invoke Node Drains #6819

merged 49 commits into from
Jan 24, 2020

Conversation

DingoEatingFuzz
Copy link
Contributor

Closes #6352

This brings the ability to submit, update, stop, and force node drains from the UI. It also brings along the related task of toggling node eligibility.

Screen Shot 2019-12-06 at 11 51 54 PM

The updated client detail page, including a drain control, an eligibility toggle, and rearranged title bar elements.

Screen Shot 2019-12-06 at 11 52 06 PM

The drain options dropdown/popover, including deadline, force, and system job (equivalent to cli options).

Screen Shot 2019-12-06 at 11 52 16 PM

The deadline option includes some convenient presets.

Screen Shot 2019-12-06 at 11 52 25 PM

The custom deadline allows for text input of a well formatted duration.

Screen Shot 2019-12-06 at 11 52 35 PM

When a drain is running, the number of running/migrating/completed allocations are shown and updated in realtime.

Screen Shot 2019-12-06 at 11 52 47 PM

The stop and force actions use the familiar two-step pattern to prevents accidents.

Screen Shot 2019-12-06 at 11 53 01 PM

If you are on the page when a drain completes, you will receive a completion notification.

@DingoEatingFuzz DingoEatingFuzz requested review from backspace and a team December 7, 2019 08:00
Copy link
Contributor

@backspace backspace left a comment

Choose a reason for hiding this comment

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

Overall this seems great to me, I have a few suggestions and questions, but 🥳

try {
deadline = parseDuration(this.deadline);
} catch (err) {
this.set('parseError', 'Failed to parse duration');
Copy link
Contributor

Choose a reason for hiding this comment

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

I tried this out locally and wondered why the parseDuration utility generated specific errors but those get made generic here, so the UI only shows this one message:

image

It seems like it would be more helpful to print the parse errors specifically, maybe with a fallback if the error is somehow unknown?

Also/if not, maybe there could be a link to documentation that spells out what the expectations are, apart from the placeholder example?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason is a pretty shallow one: I didn't want line wrapping. I agree this could use more attention though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new duration lib includes error messages which I included. Screenshot:

image

Drain System Jobs
{{/toggle}}
<span class="tooltip multiline" aria-label="Stop allocations for system jobs.">
{{x-icon "info-circle-outline" class="is-faded"}}
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn’t quite as necessary as it seemed in this PR (see GIF) but I wonder if something like the is-right-aligned thing would be useful here to address the slight truncation I see:

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think that makes sense here.

ui/app/styles/components/popover-menu.scss Show resolved Hide resolved

await Toggle.toggle();
assert.equal(props.onToggle.callCount, 1);
});
Copy link
Contributor

Choose a reason for hiding this comment

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

In the vein of the above comment on keyboard access, I tried it out and it all worked as expected (apart from a moment of 🤔 where triggering “Drain” with the keyboard happened too quickly for the progress indicator to even show that I’d actually done it successfully, but I think that’s something that won’t happen in a real scenario vs local testing), but what do you think of having an assertion for that at this level too? I find it somewhat unfortunate to having a multiplication of tests for different triggers but I also like to have some assertions that catch possible regressions… so maybe this could be Toggle.click and then Toggle.spaceBar in another test, or the like?

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 had that at one point, but then I couldn't get it to work. Turns out the spacebar action on inputs actually invoke the click handler.

So I agree that it would be good to capture, since it's not hard to imagine the checkbox being replaced with a span or something at some point and we accidentally lose the keyboard support, but I'm not sure how to mock it since it isn't actually a different event.

Copy link
Contributor

Choose a reason for hiding this comment

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

I’ve tested keyboard things with page objects before; I haven’t tried it in this situation but maybe this would work:

spaceBar: triggerable('keypress', '[data-test-input]', { eventProperties: { keyCode: 32 } })

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The issue isn't emulating the key event. It's that emulating the key event doesn't toggle the checkbox.

I don't know what the browser is doing, but when you press space when the checkbox is active, it sets checked and fires a click event.

When you emulate the space press, nothing happens. It's as if key controls for form inputs is a browser detail that gets translated into different DOM events before making it into javascript land.

I pushed up a commit to show you what I tried: 050f60a

Copy link
Contributor

Choose a reason for hiding this comment

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

oh wow, well that’s disappointing, sorry for misunderstanding 😞

this.setProperties(props);
await this.render(commonTemplate);

assert.ok(Array.from(find('[data-test-popover-trigger]').classList).includes('is-special'));
Copy link
Contributor

Choose a reason for hiding this comment

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

Since qunit-dom is present, what do you think about something like this slightly-more-direct assertion?

assert.dom('[data-test-popover-trigger]').hasClass('is-special');

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 need to brush up on qunit-dom instead of continuing to reach for my primitive tools, ha.


await PopoverMenu.focusNext();

assert.equal(document.activeElement, find('#mock-input-for-test'));
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly this could be isFocused 😀

await waitUntil(() => server.pretender.handledRequests.findBy('method', 'POST'));

assert.ok(ClientDetail.eligibilityToggle.isDisabled);
server.pretender.resolve(server.pretender.requestReferences[0].request);
Copy link
Contributor

Choose a reason for hiding this comment

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

ooh nice, I haven’t seen this before, useful!

assert.notOk(ClientDetail.eligibilityError.isPresent);
});

test('when navigating away from a client that as an error message to another client, the error is not shown', async function(assert) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Tiny typo here, as ➡️ has?


await ClientDetail.visit({ id: node2.id });

assert.notOk(ClientDetail.eligibilityError.isPresent);
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 thoroughness of these tests 😍

<p data-test-message>Allocations have been drained and the node has been set to ineligible.</p>
</div>
<div class="column is-centered is-minimum">
<button data-test-dimiss class="button is-info" onclick={{action (mut showDrainNotification) false}}>Okay</button>
Copy link
Contributor

Choose a reason for hiding this comment

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

I see that there’s a clickable dismiss in the page object but I didn’t see any use of it in any tests, is it an oversight? I did try it out with this particular notification and it could be overkill to have assertions that dismissal works for every variation, but maybe the major one could have one? Or maybe that clickable can just be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's used a few times in the acceptance tests.

await ClientDetail.stopDrainError.dismiss();
assert.notOk(ClientDetail.stopDrainError.isPresent);

Github may have auto-hid the diff and tricked you if you were trying to cmd+f for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

oops, that’s entirely true, thanks 😯

Copy link

@joshklekamp joshklekamp left a comment

Choose a reason for hiding this comment

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

Looking good! I had a couple of questions and requests, but it is looking great!

@@ -0,0 +1,75 @@
$size: 10px;

Choose a reason for hiding this comment

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

This should be 12px so that there is a 2px padding on the top, bottom, and outer edge of the 8px knob.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tweaked some numbers:
image

@@ -1,4 +1,4 @@
$size: 0.75em;
$size: 1.3rem;

Choose a reason for hiding this comment

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

Can we add the icons to the status light as well? And possibly update the striped gray fill for initializing? https://share.goabstract.com/54443b43-f285-4873-8dd6-711acc63ad8c?sha=74bc51e24f125d7b56cab17f39ca674bc6c3b793

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, I plan on doing this in a follow up PR (but still before we release this without it) if that's alright.

onDrain=(action "drainNotify")
onError=(action "drainError")}}
</div>
</div>

<div class="boxed-section is-small">
<div class="boxed-section-body inline-definitions">

Choose a reason for hiding this comment

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

Can we remove the Draining and Eligibility status from here now that the UI accounts for it via the eligibility toggle and the drain strategy component? Not sure this is the right spot to comment but wanted to make sure you saw this.

@@ -8,4 +8,8 @@
&.with-headroom {
margin-top: 1rem;
}

&.with-subheading {
margin-bottom: 0.5rem;

Choose a reason for hiding this comment

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

Can we bump this to .85rem? The 16px I had before was probably a little too generous, but .85 gets us to 12px, which is still a standard spacing class for Structure.

duration *= 1000000;

return duration;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering if using something like https://github.com/jkroso/parse-duration might be a replacement for this? That's the first one I saw when I looked, but it seems well tested, zero dependency, small and well used. Might not be ideal, or you might have to use Nomad specific things that I'm not aware of, but thought I'd ask just incase.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I will probably end up switching this out for Duration.js since Vault is already using that one.

I was determined to write this implementation without regex, but I'll swallow my pride and use a lib 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

Sounds good to me!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Officially swapped.

@backspace
Copy link
Contributor

Thanks for the followup changes, seems ready to merge to me 🥳

@johncowen
Copy link
Contributor

+1 to that ^ 🥳

@github-actions
Copy link

I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jan 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Node Drain Eligibility UI support
4 participants