diff --git a/ui/app/adapters/job.js b/ui/app/adapters/job.js
index 08cc9b98e..d56ce732e 100644
--- a/ui/app/adapters/job.js
+++ b/ui/app/adapters/job.js
@@ -29,6 +29,11 @@ export default class JobAdapter extends WatchableNamespaceIDs {
return this.ajax(url, 'DELETE');
}
+ purge(job) {
+ const url = this.urlForFindRecord(job.get('id'), 'job') + '?purge=true';
+ return this.ajax(url, 'DELETE');
+ }
+
parse(spec) {
const url = addToPath(this.urlForFindAll('job'), '/parse?namespace=*');
return this.ajax(url, 'POST', {
diff --git a/ui/app/components/job-page/parts/title.js b/ui/app/components/job-page/parts/title.js
index 24d8fc073..07e3dc7c3 100644
--- a/ui/app/components/job-page/parts/title.js
+++ b/ui/app/components/job-page/parts/title.js
@@ -1,5 +1,6 @@
import Component from '@ember/component';
import { task } from 'ember-concurrency';
+import { inject as service } from '@ember/service';
import messageFromAdapterError from 'nomad-ui/utils/message-from-adapter-error';
import { tagName } from '@ember-decorators/component';
import classic from 'ember-classic-decorator';
@@ -7,6 +8,8 @@ import classic from 'ember-classic-decorator';
@classic
@tagName('')
export default class Title extends Component {
+ @service router;
+
job = null;
title = null;
@@ -27,6 +30,27 @@ export default class Title extends Component {
})
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* () {
const job = this.job;
const definition = yield job.fetchRawDefinition();
diff --git a/ui/app/models/job.js b/ui/app/models/job.js
index eb8558f07..2c3a2d20c 100644
--- a/ui/app/models/job.js
+++ b/ui/app/models/job.js
@@ -227,6 +227,10 @@ export default class Job extends Model {
return this.store.adapterFor('job').stop(this);
}
+ purge() {
+ return this.store.adapterFor('job').purge(this);
+ }
+
plan() {
assert('A job must be parsed before planned', this._newDefinitionJSON);
return this.store.adapterFor('job').plan(this);
diff --git a/ui/app/templates/components/job-page/parts/title.hbs b/ui/app/templates/components/job-page/parts/title.hbs
index a4fc8ed27..bfc8543b3 100644
--- a/ui/app/templates/components/job-page/parts/title.hbs
+++ b/ui/app/templates/components/job-page/parts/title.hbs
@@ -25,6 +25,15 @@
@awaitingConfirmation={{this.stopJob.isRunning}}
@onConfirm={{perform this.stopJob}} />
{{else}}
+
req.url === expectedURL),
+ 'DELETE URL was made correctly'
+ );
+}
diff --git a/ui/tests/integration/components/job-page/periodic-test.js b/ui/tests/integration/components/job-page/periodic-test.js
index aa55a8881..685677490 100644
--- a/ui/tests/integration/components/job-page/periodic-test.js
+++ b/ui/tests/integration/components/job-page/periodic-test.js
@@ -11,9 +11,11 @@ import {
jobURL,
stopJob,
startJob,
+ purgeJob,
expectError,
expectDeleteRequest,
expectStartRequest,
+ expectPurgeRequest,
} from './helpers';
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');
});
+ 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) {
this.server.create('job', 'periodic', {
id: 'parent',
diff --git a/ui/tests/integration/components/job-page/service-test.js b/ui/tests/integration/components/job-page/service-test.js
index 745c90f55..84026ccc7 100644
--- a/ui/tests/integration/components/job-page/service-test.js
+++ b/ui/tests/integration/components/job-page/service-test.js
@@ -7,9 +7,11 @@ import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
import {
startJob,
stopJob,
+ purgeJob,
expectError,
expectDeleteRequest,
expectStartRequest,
+ expectPurgeRequest,
} from './helpers';
import Job from 'nomad-ui/tests/pages/jobs/detail';
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');
});
+ 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) {
assert.expect(3);
diff --git a/ui/tests/pages/jobs/detail.js b/ui/tests/pages/jobs/detail.js
index 55f8507dc..860e4515b 100644
--- a/ui/tests/pages/jobs/detail.js
+++ b/ui/tests/pages/jobs/detail.js
@@ -37,6 +37,7 @@ export default create({
stop: twoStepButton('[data-test-stop]'),
start: twoStepButton('[data-test-start]'),
+ purge: twoStepButton('[data-test-purge]'),
packTag: isPresent('[data-test-pack-tag]'),
metaTable: isPresent('[data-test-meta]'),
diff --git a/ui/tests/unit/adapters/job-test.js b/ui/tests/unit/adapters/job-test.js
index 534c22376..8872b4d4b 100644
--- a/ui/tests/unit/adapters/job-test.js
+++ b/ui/tests/unit/adapters/job-test.js
@@ -515,6 +515,20 @@ module('Unit | Adapter | Job', function (hooks) {
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®ion=${region}`
+ );
+ assert.equal(request.method, 'DELETE');
+ });
+
test('parse requests include the activeRegion', async function (assert) {
const region = 'region-2';
await this.initializeUI({ region });