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: Add job version revert buttons #10336

Merged
merged 16 commits into from Apr 20, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 18 additions & 0 deletions ui/app/adapters/job-version.js
@@ -0,0 +1,18 @@
import ApplicationAdapter from './application';
import addToPath from 'nomad-ui/utils/add-to-path';

export default class JobVersionAdapter extends ApplicationAdapter {
revertTo(jobVersion) {
const jobAdapter = this.store.adapterFor('job');

const url = addToPath(jobAdapter.urlForFindRecord(jobVersion.get('job.id'), 'job'), '/revert');
const [jobName] = JSON.parse(jobVersion.get('job.id'));

return this.ajax(url, 'POST', {
data: {
JobID: jobName,
JobVersion: jobVersion.number,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

I forget how this URL building stuff works. Is this going to append the namespace as a query param?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ya, I did manually test that but it makes sense to automate, how does c30917f look for that?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good!

}
}
42 changes: 42 additions & 0 deletions ui/app/components/job-version.js
@@ -1,6 +1,9 @@
import Component from '@ember/component';
import { action, computed } from '@ember/object';
import { classNames } from '@ember-decorators/component';
import { inject as service } from '@ember/service';
import { task } from 'ember-concurrency';
import messageForError from 'nomad-ui/utils/message-from-adapter-error';
import classic from 'ember-classic-decorator';

const changeTypes = ['Added', 'Deleted', 'Edited'];
Expand All @@ -14,6 +17,8 @@ export default class JobVersion extends Component {
// Passes through to the job-diff component
verbose = true;

@service router;

@computed('version.diff')
get changeCount() {
const diff = this.get('version.diff');
Expand All @@ -30,10 +35,47 @@ export default class JobVersion extends Component {
);
}

@computed('version.{number,job.version}')
get isCurrent() {
return this.get('version.number') === this.get('version.job.version');
}

@action
toggleDiff() {
this.toggleProperty('isOpen');
}

@task(function*() {
try {
const versionBeforeReversion = this.get('version.job.version');

yield this.version.revertTo();
yield this.version.job.reload();

const versionAfterReversion = this.get('version.job.version');

if (versionBeforeReversion === versionAfterReversion) {
this.handleError({
level: 'warn',
title: 'Reversion Had No Effect',
description: 'Reverting to an identical older version doesn’t produce a new version',
});
} else {
const job = this.get('version.job');

this.router.transitionTo('jobs.job', job.get('plainId'), {
queryParams: { namespace: job.get('namespace.name') },
});
}
} catch (e) {
this.handleError({
level: 'danger',
title: 'Could Not Revert',
description: messageForError(e, 'revert'),
});
}
})
revertTo;
}

const flatten = (accumulator, array) => accumulator.concat(array);
Expand Down
2 changes: 1 addition & 1 deletion ui/app/components/two-step-button.js
Expand Up @@ -9,7 +9,7 @@ import classic from 'ember-classic-decorator';

@classic
@classNames('two-step-button')
@classNameBindings('inlineText:has-inline-text')
@classNameBindings('inlineText:has-inline-text', 'fadingBackground:has-fading-background')
export default class TwoStepButton extends Component {
idleText = '';
cancelText = '';
Expand Down
24 changes: 24 additions & 0 deletions ui/app/controllers/jobs/job/versions.js
@@ -1,9 +1,33 @@
import Controller from '@ember/controller';
import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting';
import { alias } from '@ember/object/computed';
import { action, computed } from '@ember/object';
import classic from 'ember-classic-decorator';

const alertClassFallback = 'is-info';

const errorLevelToAlertClass = {
danger: 'is-danger',
warn: 'is-warning',
};

@classic
export default class VersionsController extends Controller.extend(WithNamespaceResetting) {
error = null;

@alias('model') job;

@computed('error.level')
get errorLevelClass() {
return errorLevelToAlertClass[this.get('error.level')] || alertClassFallback;
}

onDismiss() {
this.set('error', null);
}

@action
handleError(errorObject) {
this.set('error', errorObject);
}
}
4 changes: 4 additions & 0 deletions ui/app/models/job-version.js
Expand Up @@ -7,4 +7,8 @@ export default class JobVersion extends Model {
@attr('date') submitTime;
@attr('number') number;
@attr() diff;

revertTo() {
return this.store.adapterFor('job-version').revertTo(this);
}
}
9 changes: 6 additions & 3 deletions ui/app/routes/jobs/job/versions.js
@@ -1,6 +1,6 @@
import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRelationship } from 'nomad-ui/utils/properties/watch';
import { watchRecord, watchRelationship } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';

export default class VersionsRoute extends Route.extend(WithWatchers) {
Expand All @@ -11,10 +11,13 @@ export default class VersionsRoute extends Route.extend(WithWatchers) {

startWatchers(controller, model) {
if (model) {
controller.set('watcher', this.watchVersions.perform(model));
controller.set('watcher', this.watch.perform(model));
controller.set('watchVersions', this.watchVersions.perform(model));
}
}

@watchRecord('job') watch;
@watchRelationship('versions') watchVersions;
@collect('watchVersions') watchers;

@collect('watch', 'watchVersions') watchers;
}
5 changes: 5 additions & 0 deletions ui/app/styles/components/boxed-section.scss
Expand Up @@ -19,6 +19,11 @@
margin-left: auto;
}

.is-fixed-width {
display: inline-block;
width: 8em;
}

&.is-compact {
padding: 0.75em;
}
Expand Down
12 changes: 10 additions & 2 deletions ui/app/styles/components/two-step-button.scss
Expand Up @@ -14,11 +14,19 @@
}

.confirmation-text {
position: static;
margin-right: 0.5ch;
position: absolute;
left: auto;
right: 0;
top: auto;
margin-right: 100%;
}
}

&.has-fading-background .confirmation-text {
padding: 0.5rem 0 0.5rem 4rem;
background: linear-gradient(to left, white, white 90%, transparent 100%);
}

.confirmation-text {
position: absolute;
left: 0;
Expand Down
41 changes: 36 additions & 5 deletions ui/app/templates/components/job-version.hbs
Expand Up @@ -8,11 +8,42 @@
<span class="term">Submitted</span>
<span data-test-version-submit-time class="submit-date">{{format-ts this.version.submitTime}}</span>
</span>
{{#if this.version.diff}}
<button class="button is-light is-compact pull-right" {{action "toggleDiff"}} type="button">{{this.changeCount}} {{pluralize "Change" this.changeCount}}</button>
{{else}}
<span class="pull-right">No Changes</span>
{{/if}}
<div class="pull-right">
{{#unless this.isCurrent}}
{{#if (can "run job")}}
<TwoStepButton
data-test-revert-to
@classes={{hash
idleButton="is-warning is-outlined"
confirmButton="is-warning"}}
@alignRight={{true}}
@idleText="Revert"
@cancelText="Cancel"
@confirmText="Yes, Revert"
@confirmationMessage="Are you sure you want to revert to this version?"
@inlineText={{true}}
@fadingBackground={{true}}
@awaitingConfirmation={{this.revertTo.isRunning}}
@onConfirm={{perform this.revertTo}} />
{{else}}
<button
data-test-revert-to
type="button"
class="button is-warning is-outlined is-small tooltip"
disabled
aria-label="You don’t have permission to revert"
>
Revert
</button>
{{/if}}
{{/unless}}

{{#if this.version.diff}}
<button class="button is-light is-small is-fixed-width" {{action "toggleDiff"}} type="button">{{this.changeCount}} {{pluralize "Change" this.changeCount}}</button>
{{else}}
<div class="is-fixed-width is-size-7 has-text-centered">No Changes</div>
{{/if}}
</div>
</div>
{{#if this.isOpen}}
<div class="boxed-section-body is-dark">
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/job-versions-stream.hbs
Expand Up @@ -5,6 +5,6 @@
</li>
{{/if}}
<li data-test-version class="timeline-object">
<JobVersion @version={{record.version}} @verbose={{this.verbose}} />
<JobVersion @version={{record.version}} @verbose={{this.verbose}} @handleError={{@handleError}} />
</li>
{{/each}}
16 changes: 15 additions & 1 deletion ui/app/templates/jobs/job/versions.hbs
@@ -1,5 +1,19 @@
{{page-title "Job " this.job.name " versions"}}
<JobSubnav @job={{this.job}} />
<section class="section">
<JobVersionsStream @versions={{this.model.versions}} @verbose={{true}} />
{{#if this.error}}
<div data-test-inline-error class="notification {{this.errorLevelClass}}">
<div class="columns">
<div class="column">
<h3 data-test-inline-error-title class="title is-4">{{this.error.title}}</h3>
<p data-test-inline-error-body>{{this.error.description}}</p>
</div>
<div class="column is-centered is-minimum">
<button data-test-inline-error-close class="button {{this.errorLevelClass}}" onclick={{action this.onDismiss}} type="button">Okay</button>
</div>
</div>
</div>
{{/if}}

<JobVersionsStream @versions={{this.model.versions}} @verbose={{true}} @handleError={{action this.handleError}} />
</section>
9 changes: 9 additions & 0 deletions ui/mirage/config.js
Expand Up @@ -180,6 +180,15 @@ export default function() {
return okEmpty();
});

this.post('/job/:id/revert', function({ jobs }, { requestBody }) {
const { JobID, JobVersion } = JSON.parse(requestBody);
const job = jobs.find(JobID);
job.version = JobVersion;
job.save();

return okEmpty();
});

this.post('/job/:id/scale', function({ jobs }, { params }) {
return this.serialize(jobs.find(params.id));
});
Expand Down