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: Run a job from the Web UI #4592

Merged
merged 19 commits into from
Aug 18, 2018
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
751b6e2
Enforce a min-height for the code editor component
DingoEatingFuzz Aug 14, 2018
53f2ca3
New layout helper for associating two elements vertically
DingoEatingFuzz Aug 14, 2018
33956a6
New job run page and navigation to get there.
DingoEatingFuzz Aug 14, 2018
302401d
Add breadcrumb to the run job page
DingoEatingFuzz Aug 14, 2018
da1e179
Parse and Plan API and UI workflows
DingoEatingFuzz Aug 15, 2018
27f4a59
Fix no allocations error message layout for the recent allocations co…
DingoEatingFuzz Aug 15, 2018
21da150
Remove unused solarized theme configuration
DingoEatingFuzz Aug 15, 2018
f212887
Run job UI and API workflows
DingoEatingFuzz Aug 15, 2018
f29f435
Error messages for job submit
DingoEatingFuzz Aug 15, 2018
a970741
Move the Diff property read out of the template
DingoEatingFuzz Aug 15, 2018
09e6432
Support parse, plan, and run endpoints in mirage
DingoEatingFuzz Aug 15, 2018
4b12c06
Use the job name as the job id
DingoEatingFuzz Aug 16, 2018
3b8b894
Acceptance test for the jobs list page
DingoEatingFuzz Aug 16, 2018
875ba99
New test helper for getting the underlying CodeMirror instance from a…
DingoEatingFuzz Aug 16, 2018
3b5d96b
New PageObject helper for getting and setting CodeMirror values
DingoEatingFuzz Aug 16, 2018
c6fa757
New Page Object component for common error formatting
DingoEatingFuzz Aug 16, 2018
635411f
Rework job parse mirage request to get the job ID out of the payload
DingoEatingFuzz Aug 17, 2018
a5d6790
Acceptance tests for job run page
DingoEatingFuzz Aug 17, 2018
da8a6e4
Spiff up the form buttons with type and disabled attributes
DingoEatingFuzz Aug 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions ui/app/adapters/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,36 @@ export default Watchable.extend({
const url = this.urlForFindRecord(job.get('id'), 'job');
return this.ajax(url, 'DELETE');
},

parse(spec) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So nice the API does this 💅

const url = addToPath(this.urlForFindAll('job'), '/parse');
return this.ajax(url, 'POST', {
data: {
JobHCL: spec,
Canonicalize: true,
},
});
},

plan(job) {
const url = addToPath(this.urlForFindRecord(job.get('id'), 'job'), '/plan');
return this.ajax(url, 'POST', {
data: {
Job: job.get('_newDefinitionJSON'),
Diff: true,
},
});
},

// Running a job doesn't follow REST create semantics so it's easier to
// treat it as an action.
run(job) {
return this.ajax(this.urlForCreateRecord('job'), 'POST', {
data: {
Job: job.get('_newDefinitionJSON'),
},
});
},
});

function associateNamespace(url, namespace) {
Expand Down
65 changes: 65 additions & 0 deletions ui/app/controllers/jobs/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import Controller from '@ember/controller';
import { computed } from '@ember/object';
import { task } from 'ember-concurrency';
import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error';
import localStorageProperty from 'nomad-ui/utils/properties/local-storage';

export default Controller.extend({
parseError: null,
planError: null,
runError: null,
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reason for separating these out? Would you ever get a parse error that could then proceed to a plan?


planOutput: null,

showPlanMessage: localStorageProperty('nomadMessageJobPlan', true),
showEditorMessage: localStorageProperty('nomadMessageJobEditor', true),

stage: computed('planOutput', function() {
return this.get('planOutput') ? 'plan' : 'editor';
}),

plan: task(function*() {
this.reset();

try {
yield this.get('model').parse();
} catch (err) {
const error = messageFromAdapterError(err) || 'Could not parse input';
this.set('parseError', error);
return;
}

try {
const planOutput = yield this.get('model').plan();
this.set('planOutput', planOutput.Diff);
} catch (err) {
const error = messageFromAdapterError(err) || 'Could not plan job';
this.set('planError', error);
}
}).drop(),

submit: task(function*() {
try {
yield this.get('model').run();

const id = this.get('model.plainId');
const namespace = this.get('model.namespace.name') || 'default';

this.reset();

// navigate to the new job page
this.transitionToRoute('jobs.job', id, {
queryParams: { jobNamespace: namespace },
});
} catch (err) {
const error = messageFromAdapterError(err) || 'Could not submit job';
this.set('runError', error);
}
}),

reset() {
this.set('planOutput', null);
this.set('planError', null);
this.set('parseError', null);
},
});
59 changes: 59 additions & 0 deletions ui/app/models/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import RSVP from 'rsvp';
import { assert } from '@ember/debug';

const JOB_TYPES = ['service', 'batch', 'system'];

Expand Down Expand Up @@ -191,6 +193,54 @@ export default Model.extend({
return this.store.adapterFor('job').stop(this);
},

plan() {
assert('A job must be parsed before planned', this.get('_newDefinitionJSON'));
return this.store.adapterFor('job').plan(this);
},

run() {
assert('A job must be parsed before ran', this.get('_newDefinitionJSON'));
return this.store.adapterFor('job').run(this);
},

parse() {
const definition = this.get('_newDefinition');
let promise;

try {
// If the definition is already JSON then it doesn't need to be parsed.
const json = JSON.parse(definition);
this.set('_newDefinitionJSON', definition);
this.setIDByPayload(json);
promise = RSVP.resolve(definition);
} catch (err) {
// If the definition is invalid JSON, assume it is HCL. If it is invalid
Copy link
Contributor

Choose a reason for hiding this comment

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

Ha, we do this in Vault too.

// in anyway, the parse endpoint will throw an error.
promise = this.store
.adapterFor('job')
.parse(this.get('_newDefinition'))
.then(response => {
this.set('_newDefinitionJSON', response);
this.setIDByPayload(response);
});
}

return promise;
},

setIDByPayload(payload) {
const namespace = payload.Namespace || 'default';
const id = payload.Name;

this.set('plainId', id);
this.set('id', JSON.stringify([id, namespace]));

const namespaceRecord = this.store.peekRecord('namespace', namespace);
if (namespaceRecord) {
this.set('namespace', namespaceRecord);
}
},

statusClass: computed('status', function() {
const classMap = {
pending: 'is-pending',
Expand All @@ -206,4 +256,13 @@ export default Model.extend({
// Lazily decode the base64 encoded payload
return window.atob(this.get('payload') || '');
}),

// An arbitrary HCL or JSON string that is used by the serializer to plan
// and run this job. Used for both new job models and saved job models.
_newDefinition: attr('string'),

// The new definition may be HCL, in which case the API will need to parse the
// spec first. In order to preserve both the original HCL and the parsed response
// that will be submitted to the create job endpoint, another prop is necessary.
_newDefinitionJSON: attr('string'),
});
1 change: 1 addition & 0 deletions ui/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const Router = EmberRouter.extend({

Router.map(function() {
this.route('jobs', function() {
this.route('run');
Copy link
Contributor

Choose a reason for hiding this comment

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

🏃‍♂️

this.route('job', { path: '/:job_name' }, function() {
this.route('task-group', { path: '/:name' });
this.route('definition');
Expand Down
26 changes: 26 additions & 0 deletions ui/app/routes/jobs/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
store: service(),
system: service(),

breadcrumbs: [
{
label: 'Run',
args: ['jobs.run'],
},
],

model() {
return this.get('store').createRecord('job', {
namespace: this.get('system.activeNamespace'),
});
},

resetController(controller, isExiting) {
if (isExiting) {
controller.get('model').deleteRecord();
}
},
});
6 changes: 5 additions & 1 deletion ui/app/styles/components/codemirror.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ $dark-bright: lighten($dark, 15%);
height: auto;
}

.CodeMirror-scroll {
min-height: 500px;
}

.cm-s-hashi,
.cm-s-hashi-read-only {
&.CodeMirror {
Expand Down Expand Up @@ -39,7 +43,7 @@ $dark-bright: lighten($dark, 15%);
}

span.cm-comment {
color: $grey-light;
color: $grey;
}

span.cm-string,
Expand Down
51 changes: 26 additions & 25 deletions ui/app/styles/core.scss
Original file line number Diff line number Diff line change
@@ -1,34 +1,35 @@
// Utils
@import "./utils/reset.scss";
@import "./utils/z-indices";
@import "./utils/product-colors";
@import "./utils/bumper";
@import './utils/reset.scss';
@import './utils/z-indices';
@import './utils/product-colors';
@import './utils/bumper';
@import './utils/layout';

// Start with Bulma variables as a foundation
@import "bulma/sass/utilities/initial-variables";
@import 'bulma/sass/utilities/initial-variables';

// Override variables where appropriate
@import "./core/variables.scss";
@import './core/variables.scss';

// Bring in the rest of Bulma
@import "bulma/bulma";
@import 'bulma/bulma';

// Override Bulma details where appropriate
@import "./core/buttons";
@import "./core/breadcrumb";
@import "./core/columns";
@import "./core/forms";
@import "./core/icon";
@import "./core/level";
@import "./core/menu";
@import "./core/message";
@import "./core/navbar";
@import "./core/notification";
@import "./core/pagination";
@import "./core/progress";
@import "./core/section";
@import "./core/table";
@import "./core/tabs";
@import "./core/tag";
@import "./core/title";
@import "./core/typography";
@import './core/buttons';
@import './core/breadcrumb';
@import './core/columns';
@import './core/forms';
@import './core/icon';
@import './core/level';
@import './core/menu';
@import './core/message';
@import './core/navbar';
@import './core/notification';
@import './core/pagination';
@import './core/progress';
@import './core/section';
@import './core/table';
@import './core/tabs';
@import './core/tag';
@import './core/title';
@import './core/typography';
Copy link
Contributor

Choose a reason for hiding this comment

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

💥Quotepocalypse - did prettier just not do this file before?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Apparently not. It doesn't get touched very often.

3 changes: 3 additions & 0 deletions ui/app/styles/utils/layout.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.is-associative {
margin-top: -0.75em;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="boxed-section-head">
Recent Allocations
</div>
<div class="boxed-section-body is-full-bleed">
<div class="boxed-section-body {{if job.allocations.length "is-full-bleed"}}">
{{#if job.allocations.length}}
{{#list-table
source=sortedAllocations
Expand Down
13 changes: 9 additions & 4 deletions ui/app/templates/jobs/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
{{#if isForbidden}}
{{partial "partials/forbidden-message"}}
{{else}}
{{#if filteredJobs.length}}
<div class="content">
<div>{{search-box data-test-jobs-search searchTerm=(mut searchTerm) placeholder="Search jobs..."}}</div>
<div class="columns">
{{#if filteredJobs.length}}
<div class="column">
{{search-box data-test-jobs-search searchTerm=(mut searchTerm) placeholder="Search jobs..."}}
</div>
{{/if}}
<div class="column is-centered">
{{#link-to "jobs.run" data-test-run-job class="button is-primary is-pulled-right"}}Run Job{{/link-to}}
</div>
{{/if}}
</div>
{{#list-pagination
source=sortedJobs
size=pageSize
Expand Down
Loading