diff --git a/.changelog/19225.txt b/.changelog/19225.txt new file mode 100644 index 000000000..852f7d956 --- /dev/null +++ b/.changelog/19225.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: show plan output warnings alongside placement failures and dry-run info when running a job through the web ui +``` diff --git a/ui/app/components/job-editor/review.js b/ui/app/components/job-editor/review.js new file mode 100644 index 000000000..134be915f --- /dev/null +++ b/ui/app/components/job-editor/review.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { htmlSafe } from '@ember/template'; + +export default class JobEditorReviewComponent extends Component { + // Slightly formats the warning string to be more readable + get warnings() { + return htmlSafe( + (this.args.data.planOutput.warnings || '') + .replace(/\n/g, '
') + .replace(/\t/g, '    ') + ); + } +} diff --git a/ui/app/models/job-plan.js b/ui/app/models/job-plan.js index e6c02732c..76b740b43 100644 --- a/ui/app/models/job-plan.js +++ b/ui/app/models/job-plan.js @@ -12,5 +12,8 @@ export default class JobPlan extends Model { @attr() diff; @fragmentArray('placement-failure', { defaultValue: () => [] }) failedTGAllocs; + @hasMany('allocation') preemptions; + + @attr('string') warnings; } diff --git a/ui/app/templates/components/job-editor/review.hbs b/ui/app/templates/components/job-editor/review.hbs index ec56ee2c3..4d4615a83 100644 --- a/ui/app/templates/components/job-editor/review.hbs +++ b/ui/app/templates/components/job-editor/review.hbs @@ -13,22 +13,32 @@ /> -
-
Scheduler dry-run
-
+ + + Scheduler dry-run + {{#if @data.planOutput.failedTGAllocs}} - {{#each @data.planOutput.failedTGAllocs as |placementFailure|}} + {{#each @data.planOutput.failedTGAllocs as |placementFailure|}} - {{/each}} + {{/each}} {{else}} - All tasks successfully allocated. + All tasks successfully allocated. {{/if}} -
-
+ + +
+ +{{#if this.warnings}} + + +

+ {{this.warnings}} +

+
+
+
+{{/if}} + {{#if (and @data.planOutput.preemptions.isFulfilled @data.planOutput.preemptions.length diff --git a/ui/mirage/config.js b/ui/mirage/config.js index 36e44d880..44db51d2d 100644 --- a/ui/mirage/config.js +++ b/ui/mirage/config.js @@ -139,10 +139,16 @@ export default function () { const FailedTGAllocs = body.Job.Unschedulable && generateFailedTGAllocs(body.Job); + const jobPlanWarnings = body.Job.WithWarnings && generateWarnings(); + return new Response( 200, {}, - JSON.stringify({ FailedTGAllocs, Diff: generateDiff(req.params.id) }) + JSON.stringify({ + FailedTGAllocs, + Warnings: jobPlanWarnings, + Diff: generateDiff(req.params.id), + }) ); }); @@ -1224,3 +1230,7 @@ function generateFailedTGAllocs(job, taskGroups) { return hash; }, {}); } + +function generateWarnings() { + return '2 warnings:\n\n* Group "yourtask" has warnings: 1 error occurred:\n\t* Task "yourtask" has warnings: 1 error occurred:\n\t* 2 errors occurred:\n\t* Identity[vault_default] identities without an audience are insecure\n\t* Identity[vault_default] identities without an expiration are insecure\n* Task yourtask has an identity called vault_default but no vault block'; +} diff --git a/ui/tests/integration/components/job-editor-test.js b/ui/tests/integration/components/job-editor-test.js index c0cf0f4b6..55d4ce3b9 100644 --- a/ui/tests/integration/components/job-editor-test.js +++ b/ui/tests/integration/components/job-editor-test.js @@ -15,6 +15,7 @@ import jobEditor from 'nomad-ui/tests/pages/components/job-editor'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import setupCodeMirror from 'nomad-ui/tests/helpers/codemirror'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; +import percySnapshot from '@percy/ember'; const Editor = create(jobEditor()); @@ -290,8 +291,8 @@ module('Integration | Component | job-editor', function (hooks) { await componentA11yAudit(this.element, assert); }); - test('when the scheduler dry-run has warnings, the warnings are shown to the user', async function (assert) { - assert.expect(4); + test('when the scheduler dry-run has errors, the errors are shown to the user', async function (assert) { + assert.expect(5); const spec = jsonJob({ Unschedulable: true }); const job = await this.store.createRecord('job'); @@ -312,7 +313,27 @@ module('Integration | Component | job-editor', function (hooks) { 'The scheduler dry-run message includes the warning from send back by the API' ); + assert.notOk( + Editor.warningMessage.isPresent, + 'The scheduler dry-run warning block is not present when there is an error but no warnings' + ); + await componentA11yAudit(this.element, assert); + + await percySnapshot(assert); + }); + + test('When the scheduler dry-run has warnings, the warnings are shown to the user', async function (assert) { + assert.expect(1); + const spec = jsonJob({ WithWarnings: true }); + const job = await this.store.createRecord('job'); + await renderNewJob(this, job); + await planJob(spec); + assert.ok( + Editor.warningMessage.isPresent, + 'The scheduler dry-run warning block is shown to the user' + ); + await percySnapshot(assert); }); test('when the scheduler dry-run has no warnings, a success message is shown to the user', async function (assert) { diff --git a/ui/tests/pages/components/job-editor.js b/ui/tests/pages/components/job-editor.js index c0f29ec40..c5b25769c 100644 --- a/ui/tests/pages/components/job-editor.js +++ b/ui/tests/pages/components/job-editor.js @@ -41,7 +41,12 @@ export default () => ({ scope: '[data-test-dry-run-message]', title: text('[data-test-dry-run-title]'), body: text('[data-test-dry-run-body]'), - errored: hasClass('is-warning'), - succeeded: hasClass('is-primary'), + errored: hasClass('hds-alert--color-critical'), + succeeded: hasClass('hds-alert--color-success'), + }, + + warningMessage: { + scope: '[data-test-dry-run-warnings]', + body: text('[data-test-dry-run-warning-body]'), }, });