Job spec upload (#14747)

* Job spec upload by click or drag

* pseudo-restrict formats

* Changelog

* Tweak to job spec upload to be above editor layer

* Within the job-editor again tho

* Beginning testcase cleanup

* Test progression

* refact: update codemirror fillin logic

Co-authored-by: Jai Bhagat <jaybhagat841@gmail.com>
This commit is contained in:
Phil Renaud 2022-11-02 10:34:10 -04:00 committed by GitHub
parent a0bdc67d6a
commit 6d5fe56fa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 62 deletions

3
.changelog/14747.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: allow users to upload files by click or drag in the web ui
```

View File

@ -49,7 +49,6 @@ export default class JobEditor extends Component {
planOutput = null; planOutput = null;
@localStorageProperty('nomadMessageJobPlan', true) showPlanMessage; @localStorageProperty('nomadMessageJobPlan', true) showPlanMessage;
@localStorageProperty('nomadMessageJobEditor', true) showEditorMessage;
@computed('planOutput') @computed('planOutput')
get stage() { get stage() {
@ -117,4 +116,14 @@ export default class JobEditor extends Component {
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
} }
@action uploadJobSpec(event) {
const reader = new FileReader();
reader.onload = () => {
this.updateCode(reader.result);
};
const [file] = event.target.files;
reader.readAsText(file);
}
} }

View File

@ -129,3 +129,25 @@ $dark-bright: lighten($dark, 15%);
background-color: $dark-2; background-color: $dark-2;
} }
} }
header.run-job-header {
display: grid;
grid-template-columns: 1fr auto;
margin-bottom: 2rem;
gap: 0 1rem;
& > h1 {
grid-column: -1 / 1;
}
.job-spec-upload {
.button {
cursor: pointer;
}
input {
width: 100%;
height: 100%;
position: absolute;
display: none;
}
}
}

View File

@ -18,26 +18,16 @@
{{/if}} {{/if}}
{{#if (eq this.stage "editor")}} {{#if (eq this.stage "editor")}}
{{#if (and this.showEditorMessage (eq this.context "new"))}}
<div class="notification is-info"> <header class="run-job-header">
<div class="columns"> <h1 class="title is-3">Run a job</h1>
<div class="column"> <p>Paste or author HCL or JSON to submit to your cluster. A plan will be requested before the job is submitted. You can also attach a job spec by uploading a job file or dragging &amp; dropping a file to the editor.</p>
<h3 class="title is-4" data-test-editor-help-title>Run a Job</h3> <label class="job-spec-upload">
<p data-test-editor-help-message>Paste or author HCL or JSON to submit <input type="file" onchange={{action this.uploadJobSpec}} accept=".hcl,.json,.nomad" />
to your cluster. A plan will be requested before the job is <span class="button">Upload a job spec file</span>
submitted.</p> </label>
</div> </header>
<div class="column is-centered is-minimum">
<button
class="button is-info"
onclick={{toggle-action "showEditorMessage" this}}
data-test-editor-help-dismiss
type="button"
>Okay</button>
</div>
</div>
</div>
{{/if}}
<div class="boxed-section"> <div class="boxed-section">
<div class="boxed-section-head"> <div class="boxed-section-head">
Job Definition Job Definition
@ -63,6 +53,7 @@
/> />
</div> </div>
</div> </div>
<div class="content is-associative"> <div class="content is-associative">
<button <button
class="button is-primary {{if this.plan.isRunning 'is-loading'}}" class="button is-primary {{if this.plan.isRunning 'is-loading'}}"

View File

@ -105,59 +105,29 @@ module('Integration | Component | job-editor', function (hooks) {
}; };
const planJob = async (spec) => { const planJob = async (spec) => {
await Editor.editor.fillIn(spec); const cm = getCodeMirrorInstance(['data-test-editor']);
cm.setValue(spec);
await Editor.plan(); await Editor.plan();
}; };
test('the default state is an editor with an explanation popup', async function (assert) { test('the default state is an editor with an explanation popup', async function (assert) {
assert.expect(3); assert.expect(2);
const job = await this.store.createRecord('job'); const job = await this.store.createRecord('job');
await renderNewJob(this, job); await renderNewJob(this, job);
assert.ok(
Editor.editorHelp.isPresent,
'Editor explanation popup is present'
);
assert.ok(Editor.editor.isPresent, 'Editor is present'); assert.ok(Editor.editor.isPresent, 'Editor is present');
await componentA11yAudit(this.element, assert); await componentA11yAudit(this.element, assert);
}); });
test('the explanation popup can be dismissed', async function (assert) {
const job = await this.store.createRecord('job');
await renderNewJob(this, job);
await Editor.editorHelp.dismiss();
assert.notOk(
Editor.editorHelp.isPresent,
'Editor explanation popup is gone'
);
assert.equal(
window.localStorage.nomadMessageJobEditor,
'false',
'Dismissal is persisted in localStorage'
);
});
test('the explanation popup is not shown once the dismissal state is set in localStorage', async function (assert) {
window.localStorage.nomadMessageJobEditor = 'false';
const job = await this.store.createRecord('job');
await renderNewJob(this, job);
assert.notOk(
Editor.editorHelp.isPresent,
'Editor explanation popup is gone'
);
});
test('submitting a json job skips the parse endpoint', async function (assert) { test('submitting a json job skips the parse endpoint', async function (assert) {
const spec = jsonJob(); const spec = jsonJob();
const job = await this.store.createRecord('job'); const job = await this.store.createRecord('job');
await renderNewJob(this, job); await renderNewJob(this, job);
await planJob(spec); await planJob(spec);
console.log('wait');
const requests = this.server.pretender.handledRequests.mapBy('url'); const requests = this.server.pretender.handledRequests.mapBy('url');
assert.notOk( assert.notOk(
requests.includes('/v1/jobs/parse'), requests.includes('/v1/jobs/parse'),

View File

@ -28,13 +28,6 @@ export default () => ({
dismiss: clickable('[data-test-plan-help-dismiss]'), dismiss: clickable('[data-test-plan-help-dismiss]'),
}, },
editorHelp: {
isPresent: isPresent('[data-test-editor-help-title]'),
title: text('[data-test-editor-help-title]'),
message: text('[data-test-editor-help-message]'),
dismiss: clickable('[data-test-editor-help-dismiss]'),
},
editor: { editor: {
isPresent: isPresent('[data-test-editor]'), isPresent: isPresent('[data-test-editor]'),
contents: code('[data-test-editor]'), contents: code('[data-test-editor]'),