mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
[ui] Multi-condition start/revert/edit buttons when a job isn't running (#24985)
* Multi-condition start/revert/edit buttons when a job isn't running * mirage-mocked revertable jobs and acceptance tests * Remove version-watching from job index route
This commit is contained in:
3
.changelog/24985.txt
Normal file
3
.changelog/24985.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: Contextualizes the Start Job button on whether it is startable, revertable, or not
|
||||
```
|
||||
@@ -118,6 +118,14 @@ export default class Title extends Component {
|
||||
})
|
||||
startJob;
|
||||
|
||||
@task(function* (version) {
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
yield version.revertTo();
|
||||
})
|
||||
revertTo;
|
||||
|
||||
get description() {
|
||||
if (!this.job.ui?.Description) {
|
||||
return null;
|
||||
|
||||
@@ -446,6 +446,21 @@ export default class Job extends Model {
|
||||
|
||||
@hasMany('recommendation-summary') recommendationSummaries;
|
||||
|
||||
@computed('versions.@each.stable')
|
||||
get hasStableNonCurrentVersion() {
|
||||
return this.versions
|
||||
.sortBy('number')
|
||||
.reverse()
|
||||
.slice(1)
|
||||
.any((version) => version.get('stable'));
|
||||
}
|
||||
|
||||
@computed('versions.@each.stable')
|
||||
get latestStableVersion() {
|
||||
return this.versions.filterBy('stable').sortBy('number').reverse().slice(1)
|
||||
.firstObject;
|
||||
}
|
||||
|
||||
get actions() {
|
||||
return this.taskGroups.reduce((acc, taskGroup) => {
|
||||
return acc.concat(
|
||||
|
||||
@@ -68,7 +68,6 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
|
||||
@watchRelationship('allocations') watchAllocations;
|
||||
@watchRelationship('evaluations') watchEvaluations;
|
||||
@watchRelationship('latestDeployment') watchLatestDeployment;
|
||||
|
||||
@collect(
|
||||
'watchSummary',
|
||||
'watchAllocations',
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
</PH.Generic>
|
||||
{{/if}}
|
||||
<PH.Actions>
|
||||
|
||||
{{#if (not (eq this.job.status "dead"))}}
|
||||
{{#if (can "exec allocation" namespace=this.job.namespace)}}
|
||||
{{#if (and this.job.actions.length this.job.allocations.length)}}
|
||||
@@ -67,6 +66,14 @@
|
||||
action=(perform this.purgeJob)
|
||||
}}
|
||||
/>
|
||||
{{!--
|
||||
1. If job.stopped is true, that means the job was manually stopped and can be restared. So we should show the "start" button.
|
||||
2. If job.stopped is false, but if job.status is "dead", that means the job has failed and can't be restarted necessarily. We should should check to see that there's a stable verison of the job to fall back to.
|
||||
2a. If there is a stable version, we should show a "Revert to last stable version" button
|
||||
2b. If there is no stable version, we should show an "Edit and resubmit" button
|
||||
--}}
|
||||
|
||||
{{#if this.job.stopped}}
|
||||
<TwoStepButton
|
||||
data-test-start
|
||||
@alignRight={{true}}
|
||||
@@ -82,6 +89,25 @@
|
||||
action=(perform this.startJob true)
|
||||
}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#if this.job.hasStableNonCurrentVersion}}
|
||||
<TwoStepButton
|
||||
data-test-revert
|
||||
@alignRight={{true}}
|
||||
@idleText="Revert to last stable version (v{{this.job.latestStableVersion.number}})"
|
||||
@cancelText="Cancel"
|
||||
@confirmText="Yes, Revert to last stable version"
|
||||
@confirmationMessage="Are you sure you want to revert to the last stable version?"
|
||||
@awaitingConfirmation={{this.revertTo.isRunning}}
|
||||
@onConfirm={{perform this.revertTo this.job.latestStableVersion}}
|
||||
/>
|
||||
{{else}}
|
||||
<Hds::Button
|
||||
data-test-edit-and-resubmit
|
||||
{{hds-tooltip "This job has failed and has no stable previous version to fall back to. You can edit and resubmit the job to try again." options=(hash placement="bottom")}}
|
||||
@color="primary" @isInline={{true}} @text="Edit and Resubmit job" @route={{"jobs.job.definition" this.job.id}} @query={{hash isEditing=true}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</PH.Actions>
|
||||
</Hds::PageHeader>
|
||||
|
||||
@@ -380,6 +380,8 @@ function smallCluster(server) {
|
||||
|
||||
// #endregion Version Tags
|
||||
|
||||
createRestartableJobs(server);
|
||||
|
||||
server.create('job', {
|
||||
name: 'hcl-definition-job',
|
||||
id: 'display-hcl',
|
||||
@@ -1215,3 +1217,117 @@ function getScenarioQueryParameter() {
|
||||
return mirageScenario;
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export function createRestartableJobs(server) {
|
||||
const restartableJob = server.create('job', {
|
||||
name: 'restartable-job',
|
||||
stopped: true,
|
||||
status: 'dead',
|
||||
noDeployments: true,
|
||||
shallow: true,
|
||||
createAllocations: false,
|
||||
groupAllocCount: 0,
|
||||
});
|
||||
|
||||
const revertableJob = server.create('job', {
|
||||
name: 'revertable-job',
|
||||
stopped: false,
|
||||
status: 'dead',
|
||||
noDeployments: true,
|
||||
shallow: true,
|
||||
createAllocations: false,
|
||||
groupAllocCount: 0,
|
||||
});
|
||||
|
||||
const nonRevertableJob = server.create('job', {
|
||||
name: 'non-revertable-job',
|
||||
stopped: false,
|
||||
status: 'dead',
|
||||
shallow: true,
|
||||
createAllocations: false,
|
||||
groupAllocCount: 0,
|
||||
});
|
||||
|
||||
// So it shows up as "Failed" instead of "Scaled Down"
|
||||
restartableJob.taskGroups.models[0].update({
|
||||
count: 1,
|
||||
});
|
||||
revertableJob.taskGroups.models[0].update({
|
||||
count: 1,
|
||||
});
|
||||
nonRevertableJob.taskGroups.models[0].update({
|
||||
count: 1,
|
||||
});
|
||||
|
||||
// Remove all job-versions inherently created
|
||||
server.schema.jobVersions
|
||||
.all()
|
||||
.filter((v) => v.jobId === restartableJob.id)
|
||||
.models.forEach((v) => v.destroy());
|
||||
server.schema.jobVersions
|
||||
.all()
|
||||
.filter((v) => v.jobId === revertableJob.id)
|
||||
.models.forEach((v) => v.destroy());
|
||||
server.schema.jobVersions
|
||||
.all()
|
||||
.filter((v) => v.jobId === nonRevertableJob.id)
|
||||
.models.forEach((v) => v.destroy());
|
||||
|
||||
server.create('job-version', {
|
||||
job: revertableJob,
|
||||
namespace: revertableJob.namespace,
|
||||
version: 0,
|
||||
stable: false,
|
||||
versionTag: {
|
||||
Name: 'v0',
|
||||
Description: 'The first version',
|
||||
},
|
||||
});
|
||||
|
||||
server.create('job-version', {
|
||||
job: revertableJob,
|
||||
namespace: revertableJob.namespace,
|
||||
version: 1,
|
||||
stable: true,
|
||||
versionTag: {
|
||||
Name: 'v1',
|
||||
Description: 'The second version',
|
||||
},
|
||||
});
|
||||
|
||||
server.create('job-version', {
|
||||
job: revertableJob,
|
||||
namespace: revertableJob.namespace,
|
||||
version: 2,
|
||||
stable: false,
|
||||
versionTag: {
|
||||
Name: 'v2',
|
||||
Description: 'The third version',
|
||||
},
|
||||
});
|
||||
|
||||
server.create('job-version', {
|
||||
job: nonRevertableJob,
|
||||
namespace: nonRevertableJob.namespace,
|
||||
version: 0,
|
||||
stable: false,
|
||||
noActiveDeployment: true,
|
||||
});
|
||||
|
||||
server.create('job-version', {
|
||||
job: nonRevertableJob,
|
||||
namespace: nonRevertableJob.namespace,
|
||||
version: 1,
|
||||
stable: false,
|
||||
noActiveDeployment: true,
|
||||
});
|
||||
|
||||
server.schema.jobVersions
|
||||
.all()
|
||||
.filter((v) => v.jobId === revertableJob.id)
|
||||
.models.forEach((v) => v.update({ stable: true }));
|
||||
server.schema.jobVersions
|
||||
.all()
|
||||
.filter((v) => v.jobId === nonRevertableJob.id)
|
||||
.models.forEach((v) => v.update({ stable: false }));
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import moduleForJob, {
|
||||
} from 'nomad-ui/tests/helpers/module-for-job';
|
||||
import JobDetail from 'nomad-ui/tests/pages/jobs/detail';
|
||||
import percySnapshot from '@percy/ember';
|
||||
import { createRestartableJobs } from 'nomad-ui/mirage/scenarios/default';
|
||||
|
||||
moduleForJob('Acceptance | job detail (batch)', 'allocations', () =>
|
||||
server.create('job', {
|
||||
@@ -787,3 +788,73 @@ module('Acceptance | job detail (with namespaces)', function (hooks) {
|
||||
.exists('A toast error message pops up.');
|
||||
});
|
||||
});
|
||||
|
||||
module('Job Start/Stop/Revert/Edit and Resubmit', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
server.create('agent');
|
||||
server.create('node-pool');
|
||||
server.create('node');
|
||||
|
||||
createRestartableJobs(server);
|
||||
});
|
||||
|
||||
test('Start Job depends on the job being stopped', async function (assert) {
|
||||
const restartableJob = server.db.jobs.findBy(
|
||||
(j) => j.name === 'restartable-job'
|
||||
);
|
||||
const revertableJob = server.db.jobs.findBy(
|
||||
(j) => j.name === 'revertable-job'
|
||||
);
|
||||
const nonRevertableJob = server.db.jobs.findBy(
|
||||
(j) => j.name === 'non-revertable-job'
|
||||
);
|
||||
await JobDetail.visit({ id: restartableJob.id });
|
||||
|
||||
assert.ok(JobDetail.start.isPresent);
|
||||
assert.notOk(JobDetail.stop.isPresent);
|
||||
assert.notOk(JobDetail.revert.isPresent);
|
||||
assert.notOk(JobDetail.editAndResubmit.isPresent);
|
||||
await percySnapshot('Start Job depends on the job being stopped');
|
||||
|
||||
await JobDetail.visit({ id: revertableJob.id });
|
||||
assert.notOk(JobDetail.start.isPresent);
|
||||
|
||||
await percySnapshot('Revertable Job depends on having stable job versions');
|
||||
|
||||
await JobDetail.visit({ id: nonRevertableJob.id });
|
||||
assert.notOk(JobDetail.start.isPresent);
|
||||
await percySnapshot(
|
||||
'Non-revertable Job depends on having no stable job versions'
|
||||
);
|
||||
});
|
||||
|
||||
test('A revertable job depends on having stable job versions', async function (assert) {
|
||||
const revertableJob = server.db.jobs.findBy(
|
||||
(j) => j.name === 'revertable-job'
|
||||
);
|
||||
const nonRevertableJob = server.db.jobs.findBy(
|
||||
(j) => j.name === 'non-revertable-job'
|
||||
);
|
||||
await JobDetail.visit({ id: revertableJob.id });
|
||||
|
||||
assert.ok(JobDetail.revert.isPresent);
|
||||
assert.equal(JobDetail.revert.text, 'Revert to last stable version (v1)');
|
||||
|
||||
await JobDetail.visit({ id: nonRevertableJob.id });
|
||||
assert.notOk(JobDetail.revert.isPresent);
|
||||
assert.ok(JobDetail.editAndResubmit.isPresent);
|
||||
});
|
||||
|
||||
test('Clicking the resubmit button navigates to the job definition page in edit mode', async function (assert) {
|
||||
const job = server.db.jobs.findBy((j) => j.name === 'non-revertable-job');
|
||||
await JobDetail.visit({ id: job.id });
|
||||
await JobDetail.editAndResubmit.click();
|
||||
assert.equal(
|
||||
currentURL(),
|
||||
`/jobs/${job.id}/definition?isEditing=true&view=job-spec`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,6 +43,11 @@ export default create({
|
||||
stop: twoStepButton('[data-test-stop]'),
|
||||
start: twoStepButton('[data-test-start]'),
|
||||
purge: twoStepButton('[data-test-purge]'),
|
||||
revert: twoStepButton('[data-test-revert]'),
|
||||
editAndResubmit: {
|
||||
scope: '[data-test-edit-and-resubmit]',
|
||||
click: clickable(),
|
||||
},
|
||||
|
||||
packTag: isPresent('[data-test-pack-tag]'),
|
||||
metaTable: isPresent('[data-test-meta]'),
|
||||
|
||||
Reference in New Issue
Block a user