diff --git a/.changelog/11368.txt b/.changelog/11368.txt new file mode 100644 index 000000000000..abef009ec712 --- /dev/null +++ b/.changelog/11368.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Persist node drain settings in the browser +``` diff --git a/ui/app/components/drain-popover.js b/ui/app/components/drain-popover.js index 6690e9d721ba..764147396a2f 100644 --- a/ui/app/components/drain-popover.js +++ b/ui/app/components/drain-popover.js @@ -6,6 +6,7 @@ import { task } from 'ember-concurrency'; import Duration from 'duration-js'; import { tagName } from '@ember-decorators/component'; import classic from 'ember-classic-decorator'; +import localStorageProperty from 'nomad-ui/utils/properties/local-storage'; @classic @tagName('') @@ -22,6 +23,23 @@ export default class DrainPopover extends Component { forceDrain = false; drainSystemJobs = true; + @localStorageProperty('nomadDrainOptions', {}) drainOptions; + + didReceiveAttrs() { + // Load drain config values from local storage if availabe. + [ + 'deadlineEnabled', + 'customDuration', + 'forceDrain', + 'drainSystemJobs', + 'selectedDurationQuickOption', + ].forEach(k => { + if (k in this.drainOptions) { + this[k] = this.drainOptions[k]; + } + }); + } + @overridable(function() { return this.durationQuickOptions[0]; }) @@ -71,6 +89,14 @@ export default class DrainPopover extends Component { IgnoreSystemJobs: !this.drainSystemJobs, }; + this.drainOptions = { + deadlineEnabled: this.deadlineEnabled, + customDuration: this.deadline, + selectedDurationQuickOption: this.selectedDurationQuickOption, + drainSystemJobs: this.drainSystemJobs, + forceDrain: this.forceDrain, + }; + close(); try { diff --git a/ui/app/templates/components/drain-popover.hbs b/ui/app/templates/components/drain-popover.hbs index 3e8a3b05b129..a2c6af2eaf49 100644 --- a/ui/app/templates/components/drain-popover.hbs +++ b/ui/app/templates/components/drain-popover.hbs @@ -42,6 +42,7 @@ type="text" class="input {{if this.parseError "is-danger"}}" placeholder="1h30m" + value={{if (eq this.customDuration 0) "" this.customDuration}} oninput={{action (queue (action (mut this.parseError) '') (action (mut this.customDuration) value="target.value"))}} /> diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index c5f9638655a8..58ccb6ed2efa 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -18,7 +18,9 @@ let clientToken; const wasPreemptedFilter = allocation => !!allocation.preemptedByAllocation; function nonSearchPOSTS() { - return server.pretender.handledRequests.reject(request => request.url.includes('fuzzy')).filterBy('method', 'POST'); + return server.pretender.handledRequests + .reject(request => request.url.includes('fuzzy')) + .filterBy('method', 'POST'); } module('Acceptance | client detail', function(hooks) { @@ -26,6 +28,8 @@ module('Acceptance | client detail', function(hooks) { setupMirage(hooks); hooks.beforeEach(function() { + window.localStorage.clear(); + server.create('node', 'forceIPv4', { schedulingEligibility: 'eligible' }); node = server.db.nodes[0]; @@ -734,6 +738,45 @@ module('Acceptance | client detail', function(hooks) { ); }); + test('starting a drain persists options to localstorage', async function(assert) { + const nodes = server.createList('node', 2, { + drain: false, + schedulingEligibility: 'eligible', + }); + + await ClientDetail.visit({ id: nodes[0].id }); + await ClientDetail.drainPopover.toggle(); + + // Change all options to non-default values. + await ClientDetail.drainPopover.deadlineToggle.toggle(); + await ClientDetail.drainPopover.deadlineOptions.open(); + const optionsCount = ClientDetail.drainPopover.deadlineOptions.options.length; + await ClientDetail.drainPopover.deadlineOptions.options.objectAt(optionsCount - 1).choose(); + await ClientDetail.drainPopover.setCustomDeadline('1h40m20s'); + await ClientDetail.drainPopover.forceDrainToggle.toggle(); + await ClientDetail.drainPopover.systemJobsToggle.toggle(); + + await ClientDetail.drainPopover.submit(); + + const got = JSON.parse(window.localStorage.nomadDrainOptions); + const want = { + deadlineEnabled: true, + customDuration: '1h40m20s', + selectedDurationQuickOption: { label: 'Custom', value: 'custom' }, + drainSystemJobs: false, + forceDrain: true, + }; + assert.deepEqual(got, want); + + // Visit another node and check that drain config is persisted. + await ClientDetail.visit({ id: nodes[1].id }); + await ClientDetail.drainPopover.toggle(); + assert.true(ClientDetail.drainPopover.deadlineToggle.isActive); + assert.equal(ClientDetail.drainPopover.customDeadline, '1h40m20s'); + assert.true(ClientDetail.drainPopover.forceDrainToggle.isActive); + assert.false(ClientDetail.drainPopover.systemJobsToggle.isActive); + }); + test('the drain popover cancel button closes the popover', async function(assert) { node = server.create('node', { drain: false, diff --git a/ui/tests/pages/clients/detail.js b/ui/tests/pages/clients/detail.js index 4571e68f15f9..70afd69a7d9d 100644 --- a/ui/tests/pages/clients/detail.js +++ b/ui/tests/pages/clients/detail.js @@ -6,6 +6,7 @@ import { fillable, text, isPresent, + value, visitable, } from 'ember-cli-page-object'; @@ -118,7 +119,9 @@ export default create({ deadlineToggle: toggle('[data-test-drain-deadline-toggle]'), deadlineOptions: { - open: clickable('[data-test-drain-deadline-option-select-parent] .ember-power-select-trigger'), + open: clickable( + '[data-test-drain-deadline-option-select-parent] .ember-power-select-trigger' + ), options: collection('.ember-power-select-option', { label: text(), choose: clickable(), @@ -126,7 +129,7 @@ export default create({ }, setCustomDeadline: fillable('[data-test-drain-custom-deadline]'), - customDeadline: attribute('value', '[data-test-drain-custom-deadline]'), + customDeadline: value('[data-test-drain-custom-deadline]'), forceDrainToggle: toggle('[data-test-force-drain-toggle]'), systemJobsToggle: toggle('[data-test-system-jobs-toggle]'),