Merge pull request #14879 from hashicorp/mnomitch/job-purge-ui

Adds purge job button to UI
This commit is contained in:
Mike Nomitch 2022-10-14 12:46:20 -07:00 committed by GitHub
commit 91d32bb8df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 0 deletions

View file

@ -29,6 +29,11 @@ export default class JobAdapter extends WatchableNamespaceIDs {
return this.ajax(url, 'DELETE'); return this.ajax(url, 'DELETE');
} }
purge(job) {
const url = this.urlForFindRecord(job.get('id'), 'job') + '?purge=true';
return this.ajax(url, 'DELETE');
}
parse(spec) { parse(spec) {
const url = addToPath(this.urlForFindAll('job'), '/parse?namespace=*'); const url = addToPath(this.urlForFindAll('job'), '/parse?namespace=*');
return this.ajax(url, 'POST', { return this.ajax(url, 'POST', {

View file

@ -1,5 +1,6 @@
import Component from '@ember/component'; import Component from '@ember/component';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { inject as service } from '@ember/service';
import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error'; import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error';
import { tagName } from '@ember-decorators/component'; import { tagName } from '@ember-decorators/component';
import classic from 'ember-classic-decorator'; import classic from 'ember-classic-decorator';
@ -7,6 +8,8 @@ import classic from 'ember-classic-decorator';
@classic @classic
@tagName('') @tagName('')
export default class Title extends Component { export default class Title extends Component {
@service router;
job = null; job = null;
title = null; title = null;
@ -27,6 +30,27 @@ export default class Title extends Component {
}) })
stopJob; stopJob;
@task(function* () {
try {
const job = this.job;
yield job.purge();
this.flashMessages.add({
title: 'Job Purged',
message: `You have purged ${this.job.name}`,
type: 'success',
destroyOnClick: false,
timeout: 5000,
});
this.router.transitionTo('jobs');
} catch (err) {
this.handleError({
title: 'Error purging job',
description: messageFromAdapterError(err, 'purge jobs'),
});
}
})
purgeJob;
@task(function* () { @task(function* () {
const job = this.job; const job = this.job;
const definition = yield job.fetchRawDefinition(); const definition = yield job.fetchRawDefinition();

View file

@ -227,6 +227,10 @@ export default class Job extends Model {
return this.store.adapterFor('job').stop(this); return this.store.adapterFor('job').stop(this);
} }
purge() {
return this.store.adapterFor('job').purge(this);
}
plan() { plan() {
assert('A job must be parsed before planned', this._newDefinitionJSON); assert('A job must be parsed before planned', this._newDefinitionJSON);
return this.store.adapterFor('job').plan(this); return this.store.adapterFor('job').plan(this);

View file

@ -25,6 +25,15 @@
@awaitingConfirmation={{this.stopJob.isRunning}} @awaitingConfirmation={{this.stopJob.isRunning}}
@onConfirm={{perform this.stopJob}} /> @onConfirm={{perform this.stopJob}} />
{{else}} {{else}}
<TwoStepButton
data-test-purge
@alignRight={{true}}
@idleText="Purge Job"
@cancelText="Cancel"
@confirmText="Yes, Purge Job"
@confirmationMessage="Are you sure? You cannot undo this action."
@awaitingConfirmation={{this.purgeJob.isRunning}}
@onConfirm={{perform this.purgeJob}} />
<TwoStepButton <TwoStepButton
data-test-start data-test-start
@alignRight={{true}} @alignRight={{true}}

View file

@ -102,10 +102,12 @@ export default function moduleForJob(
test('the title buttons are dependent on job status', async function (assert) { test('the title buttons are dependent on job status', async function (assert) {
if (job.status === 'dead') { if (job.status === 'dead') {
assert.ok(JobDetail.start.isPresent); assert.ok(JobDetail.start.isPresent);
assert.ok(JobDetail.purge.isPresent);
assert.notOk(JobDetail.stop.isPresent); assert.notOk(JobDetail.stop.isPresent);
assert.notOk(JobDetail.execButton.isPresent); assert.notOk(JobDetail.execButton.isPresent);
} else { } else {
assert.notOk(JobDetail.start.isPresent); assert.notOk(JobDetail.start.isPresent);
assert.notOk(JobDetail.purge.isPresent);
assert.ok(JobDetail.stop.isPresent); assert.ok(JobDetail.stop.isPresent);
assert.ok(JobDetail.execButton.isPresent); assert.ok(JobDetail.execButton.isPresent);
} }

View file

@ -20,6 +20,11 @@ export async function startJob() {
await click('[data-test-start] [data-test-confirm-button]'); await click('[data-test-start] [data-test-confirm-button]');
} }
export async function purgeJob() {
await click('[data-test-purge] [data-test-idle-button]');
await click('[data-test-purge] [data-test-confirm-button]');
}
export function expectStartRequest(assert, server, job) { export function expectStartRequest(assert, server, job) {
const expectedURL = jobURL(job); const expectedURL = jobURL(job);
const request = server.pretender.handledRequests const request = server.pretender.handledRequests
@ -63,3 +68,14 @@ export function expectDeleteRequest(assert, server, job) {
'DELETE URL was made correctly' 'DELETE URL was made correctly'
); );
} }
export function expectPurgeRequest(assert, server, job) {
const expectedURL = jobURL(job) + '?purge=true';
assert.ok(
server.pretender.handledRequests
.filterBy('method', 'DELETE')
.find((req) => req.url === expectedURL),
'DELETE URL was made correctly'
);
}

View file

@ -11,9 +11,11 @@ import {
jobURL, jobURL,
stopJob, stopJob,
startJob, startJob,
purgeJob,
expectError, expectError,
expectDeleteRequest, expectDeleteRequest,
expectStartRequest, expectStartRequest,
expectPurgeRequest,
} from './helpers'; } from './helpers';
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
@ -226,6 +228,25 @@ module('Integration | Component | job-page/periodic', function (hooks) {
await expectError(assert, 'Could Not Start Job'); await expectError(assert, 'Could Not Start Job');
}); });
test('Purging a job sends a purge request for the job', async function (assert) {
assert.expect(1);
const mirageJob = this.server.create('job', 'periodic', {
childrenCount: 0,
createAllocations: false,
status: 'dead',
});
await this.store.findAll('job');
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
this.setProperties(commonProperties(job));
await render(commonTemplate);
await purgeJob();
expectPurgeRequest(assert, this.server, job);
});
test('Each job row includes the submitted time', async function (assert) { test('Each job row includes the submitted time', async function (assert) {
this.server.create('job', 'periodic', { this.server.create('job', 'periodic', {
id: 'parent', id: 'parent',

View file

@ -7,9 +7,11 @@ import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
import { import {
startJob, startJob,
stopJob, stopJob,
purgeJob,
expectError, expectError,
expectDeleteRequest, expectDeleteRequest,
expectStartRequest, expectStartRequest,
expectPurgeRequest,
} from './helpers'; } from './helpers';
import Job from 'nomad-ui/tests/pages/jobs/detail'; import Job from 'nomad-ui/tests/pages/jobs/detail';
import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer';
@ -128,6 +130,21 @@ module('Integration | Component | job-page/service', function (hooks) {
await expectError(assert, 'Could Not Start Job'); await expectError(assert, 'Could Not Start Job');
}); });
test('Purging a job sends a purge request for the job', async function (assert) {
assert.expect(1);
const mirageJob = makeMirageJob(this.server, { status: 'dead' });
await this.store.findAll('job');
const job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
this.setProperties(commonProperties(job));
await render(commonTemplate);
await purgeJob();
expectPurgeRequest(assert, this.server, job);
});
test('Recent allocations shows allocations in the job context', async function (assert) { test('Recent allocations shows allocations in the job context', async function (assert) {
assert.expect(3); assert.expect(3);

View file

@ -37,6 +37,7 @@ export default create({
stop: twoStepButton('[data-test-stop]'), stop: twoStepButton('[data-test-stop]'),
start: twoStepButton('[data-test-start]'), start: twoStepButton('[data-test-start]'),
purge: twoStepButton('[data-test-purge]'),
packTag: isPresent('[data-test-pack-tag]'), packTag: isPresent('[data-test-pack-tag]'),
metaTable: isPresent('[data-test-meta]'), metaTable: isPresent('[data-test-meta]'),

View file

@ -515,6 +515,20 @@ module('Unit | Adapter | Job', function (hooks) {
assert.equal(request.method, 'DELETE'); assert.equal(request.method, 'DELETE');
}); });
test('purge requests include the activeRegion', async function (assert) {
const region = 'region-2';
const job = await this.initializeWithJob({ region });
await this.subject().purge(job);
const request = this.server.pretender.handledRequests[0];
assert.equal(
request.url,
`/v1/job/${job.plainId}?purge=true&region=${region}`
);
assert.equal(request.method, 'DELETE');
});
test('parse requests include the activeRegion', async function (assert) { test('parse requests include the activeRegion', async function (assert) {
const region = 'region-2'; const region = 'region-2';
await this.initializeUI({ region }); await this.initializeUI({ region });