From e47d255b07d29fb025fb6099aa7a166658f805d2 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Mon, 11 Nov 2019 17:05:47 -0800 Subject: [PATCH] Factor out the drain popover and implement its behaviors --- ui/app/components/drain-popover.js | 161 ++++++++++++++++++ ui/app/styles/core/forms.scss | 4 + ui/app/templates/clients/client.hbs | 36 +--- ui/app/templates/components/drain-popover.hbs | 72 ++++++++ 4 files changed, 239 insertions(+), 34 deletions(-) create mode 100644 ui/app/components/drain-popover.js create mode 100644 ui/app/templates/components/drain-popover.hbs diff --git a/ui/app/components/drain-popover.js b/ui/app/components/drain-popover.js new file mode 100644 index 000000000..9ecccb3a4 --- /dev/null +++ b/ui/app/components/drain-popover.js @@ -0,0 +1,161 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { equal } from '@ember/object/computed'; +import { computed as overridable } from 'ember-overridable-computed'; +import { task } from 'ember-concurrency'; + +const durationDecode = str => { + const durationUnits = ['d', 'h', 'm', 's']; + const unitToMs = { + s: 1000, + m: 1000 * 60, + h: 1000 * 60 * 60, + d: 1000 * 60 * 60 * 24, + }; + + if (typeof str === 'number') return str; + + // Split the string into characters to make iteration easier + const chars = str.split(''); + + // Collect tokens + const tokens = []; + + // A token can be multi-character, so collect characters + let token = []; + + // If a non-numeric character follows a non-numeric character, that's a + // parse error, so this marker bool is needed + let disallowChar = false; + + // Take the first character off the chars array until there are no more + while (chars.length) { + let next = chars.shift(); + + // Check to see if the char is numeric + if (next >= 0 && next < 10) { + // Collect numeric characters until a non-numeric shows up + token.push(next); + // Release the double non-numeric mark + disallowChar = false; + } else { + if (disallowChar) { + throw new Error( + `ParseError: [${str}] Cannot follow a non-numeric token with a non-numeric token` + ); + } + if (!durationUnits.includes(next)) { + throw new Error(`ParseError: [${str}] Unallowed duration unit "${next}"`); + } + + // The token array now becomes a single int token + tokens.push(parseInt(token.join(''))); + // This non-numeric char is its own token + tokens.push(next); + + // Reset token array + token = []; + // Set the double non-numeric mark + disallowChar = true; + } + } + + // If there are numeric characters still in the token array, then there must have + // not been a followup non-numeric character which would have flushed the numeric tokens. + if (token.length) { + throw new Error(`ParseError: [${str}] Unmatched quantities and units`); + } + + // Loop over the tokens array, two at a time, converting unit and quanties into milliseconds + let duration = 0; + while (tokens.length) { + const quantity = tokens.shift(); + const unit = tokens.shift(); + duration += quantity * unitToMs[unit]; + } + + // Convert from Milliseconds to Nanoseconds + duration *= 1000000; + + console.log('DURATION', duration); + return duration; +}; + +export default Component.extend({ + tagName: '', + + client: null, + + onError() {}, + onDrain() {}, + + parseError: '', + + deadlineEnabled: false, + forceDrain: false, + drainSystemJobs: true, + + selectedDurationQuickOption: overridable(function() { + return this.durationQuickOptions.findBy('value', '4h'); + }), + + durationIsCustom: equal('selectedDurationQuickOption.value', 'custom'), + customDuration: '', + + durationQuickOptions: computed(() => [ + { label: '1 Hour', value: '1h' }, + { label: '4 Hours', value: '4h' }, + { label: '8 Hours', value: '8h' }, + { label: '12 Hours', value: '12h' }, + { label: '1 Day', value: '1d' }, + { label: 'Custom', value: 'custom' }, + ]), + + deadline: computed( + 'deadlineEnabled', + 'durationIsCustom', + 'customDuration', + 'selectedDurationQuickOption.value', + function() { + if (!this.deadlineEnabled) return 0; + if (this.durationIsCustom) return this.customDuration; + return this.selectedDurationQuickOption.value; + } + ), + + drain: task(function*(close) { + if (!this.client) return; + + let deadline; + try { + deadline = durationDecode(this.deadline); + } catch (err) { + this.set('parseError', err.message); + return; + } + + const spec = { + Deadline: deadline, + IgnoreSystemJobs: !this.drainSystemJobs, + }; + + console.log('Draining:', spec); + + close(); + + try { + if (this.forceDrain) { + yield this.client.forceDrain(spec); + } else { + yield this.client.drain(spec); + } + this.onDrain(); + } catch (err) { + this.onError(err); + } + }), + + preventDefault(e) { + e.preventDefault(); + }, +}); diff --git a/ui/app/styles/core/forms.scss b/ui/app/styles/core/forms.scss index 70135eaf5..5381921ea 100644 --- a/ui/app/styles/core/forms.scss +++ b/ui/app/styles/core/forms.scss @@ -41,6 +41,10 @@ display: inline-block; } + &.is-sub-field { + margin-left: 2em; + } + &:not(:last-child) { margin-bottom: 1rem; } diff --git a/ui/app/templates/clients/client.hbs b/ui/app/templates/clients/client.hbs index 4e4ce08b6..de5e2dddf 100644 --- a/ui/app/templates/clients/client.hbs +++ b/ui/app/templates/clients/client.hbs @@ -17,7 +17,7 @@ {{or model.name model.shortId}}

-

- {{#popover-menu label="Drain"}} -
-

Drain Options

-
- -
-
- -
-
- -
-
- - -
-
- {{/popover-menu}} + {{drain-popover client=model}}
diff --git a/ui/app/templates/components/drain-popover.hbs b/ui/app/templates/components/drain-popover.hbs new file mode 100644 index 000000000..4b2011515 --- /dev/null +++ b/ui/app/templates/components/drain-popover.hbs @@ -0,0 +1,72 @@ +{{#popover-menu label="Drain" triggerClass=(if drain.isRunning "is-loading") as |m|}} +
+

Drain Options

+
+ +
+ {{#if deadlineEnabled}} +
+ {{#power-select + options=durationQuickOptions + selected=selectedDurationQuickOption + onChange=(action (mut selectedDurationQuickOption)) as |opt|}} + {{opt.label}} + {{/power-select}} +
+ {{#if durationIsCustom}} +
+ + + {{#if parseError}} + {{parseError}} + {{/if}} +
+ {{/if}} + {{/if}} +
+ +
+
+ +
+
+ + +
+
+{{/popover-menu}}