+
{{or this.title this.job.name}}
{{this.job.status}}
{{yield}}
diff --git a/ui/tests/acceptance/job-dispatch-test.js b/ui/tests/acceptance/job-dispatch-test.js
index 8dff7340907..00d4aa888b2 100644
--- a/ui/tests/acceptance/job-dispatch-test.js
+++ b/ui/tests/acceptance/job-dispatch-test.js
@@ -1,3 +1,4 @@
+/* eslint-disable ember/no-test-module-for */
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
@@ -9,197 +10,216 @@ import { currentURL } from '@ember/test-helpers';
const REQUIRED_INDICATOR = '*';
-let job, namespace, managementToken, clientToken;
+moduleForJobDispatch('Acceptance | job dispatch', () => {
+ server.createList('namespace', 2);
+ const namespace = server.db.namespaces[0];
-module('Acceptance | job dispatch', function(hooks) {
- setupApplicationTest(hooks);
- setupCodeMirror(hooks);
- setupMirage(hooks);
-
- hooks.beforeEach(function() {
- // Required for placing allocations (a result of dispatching jobs)
- server.create('node');
- server.createList('namespace', 2);
-
- namespace = server.db.namespaces[0];
- job = server.create('job', 'parameterized', {
- status: 'running',
- namespaceId: namespace.name,
- });
-
- managementToken = server.create('token');
- clientToken = server.create('token');
-
- window.localStorage.nomadTokenSecret = managementToken.secretId;
+ return server.create('job', 'parameterized', {
+ status: 'running',
+ namespaceId: namespace.name,
});
+});
- test('it passes an accessibility audit', async function(assert) {
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- await a11yAudit(assert);
- });
+moduleForJobDispatch('Acceptance | job dispatch (with namespace)', () => {
+ server.createList('namespace', 2);
+ const namespace = server.db.namespaces[1];
- test('the dispatch button is displayed with management token', async function(assert) {
- await JobDetail.visit({ id: job.id, namespace: namespace.name });
- assert.notOk(JobDetail.dispatchButton.isDisabled);
+ return server.create('job', 'parameterized', {
+ status: 'running',
+ namespaceId: namespace.name,
});
+});
- test('the dispatch button is displayed when allowed', async function(assert) {
- window.localStorage.nomadTokenSecret = clientToken.secretId;
-
- const policy = server.create('policy', {
- id: 'dispatch',
- name: 'dispatch',
- rulesJSON: {
- Namespaces: [
- {
- Name: namespace.name,
- Capabilities: ['list-jobs', 'dispatch-job'],
- },
- ],
- },
- });
-
- clientToken.policyIds = [policy.id];
- clientToken.save();
-
- await JobDetail.visit({ id: job.id, namespace: namespace.name });
- assert.notOk(JobDetail.dispatchButton.isDisabled);
+function moduleForJobDispatch(title, jobFactory) {
+ let job, namespace, managementToken, clientToken;
- // Reset clientToken policies.
- clientToken.policyIds = [];
- clientToken.save();
- });
+ module(title, function(hooks) {
+ setupApplicationTest(hooks);
+ setupCodeMirror(hooks);
+ setupMirage(hooks);
- test('the dispatch button is disabled when not allowed', async function(assert) {
- window.localStorage.nomadTokenSecret = clientToken.secretId;
+ hooks.beforeEach(function() {
+ // Required for placing allocations (a result of dispatching jobs)
+ server.create('node');
- await JobDetail.visit({ id: job.id, namespace: namespace.name });
- assert.ok(JobDetail.dispatchButton.isDisabled);
- });
+ job = jobFactory();
+ namespace = server.db.namespaces.find(job.namespaceId);
- test('all meta fields are displayed', async function(assert) {
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- assert.equal(
- JobDispatch.metaFields.length,
- job.parameterizedJob.MetaOptional.length + job.parameterizedJob.MetaRequired.length
- );
- });
+ managementToken = server.create('token');
+ clientToken = server.create('token');
- test('required meta fields are properly indicated', async function(assert) {
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ window.localStorage.nomadTokenSecret = managementToken.secretId;
+ });
- JobDispatch.metaFields.forEach(f => {
- const hasIndicator = f.label.includes(REQUIRED_INDICATOR);
- const isRequired = job.parameterizedJob.MetaRequired.includes(f.field.id);
+ test('it passes an accessibility audit', async function(assert) {
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ await a11yAudit(assert);
+ });
- if (isRequired) {
- assert.ok(hasIndicator, `${f.label} contains required indicator.`);
- } else {
- assert.notOk(hasIndicator, `${f.label} doesn't contain required indicator.`);
- }
+ test('the dispatch button is displayed with management token', async function(assert) {
+ await JobDetail.visit({ id: job.id, namespace: namespace.name });
+ assert.notOk(JobDetail.dispatchButton.isDisabled);
});
- });
- test('job without meta fields', async function(assert) {
- const jobWithoutMeta = server.create('job', 'parameterized', {
- status: 'running',
- namespaceId: namespace.name,
- parameterizedJob: {
- MetaRequired: null,
- MetaOptional: null,
- },
+ test('the dispatch button is displayed when allowed', async function(assert) {
+ window.localStorage.nomadTokenSecret = clientToken.secretId;
+
+ const policy = server.create('policy', {
+ id: 'dispatch',
+ name: 'dispatch',
+ rulesJSON: {
+ Namespaces: [
+ {
+ Name: namespace.name,
+ Capabilities: ['list-jobs', 'dispatch-job'],
+ },
+ ],
+ },
+ });
+
+ clientToken.policyIds = [policy.id];
+ clientToken.save();
+
+ await JobDetail.visit({ id: job.id, namespace: namespace.name });
+ assert.notOk(JobDetail.dispatchButton.isDisabled);
+
+ // Reset clientToken policies.
+ clientToken.policyIds = [];
+ clientToken.save();
});
- await JobDispatch.visit({ id: jobWithoutMeta.id, namespace: namespace.name });
- assert.ok(JobDispatch.dispatchButton.isPresent);
- });
+ test('the dispatch button is disabled when not allowed', async function(assert) {
+ window.localStorage.nomadTokenSecret = clientToken.secretId;
- test('payload text area is hidden when forbidden', async function(assert) {
- job.parameterizedJob.Payload = 'forbidden';
- job.save();
+ await JobDetail.visit({ id: job.id, namespace: namespace.name });
+ assert.ok(JobDetail.dispatchButton.isDisabled);
+ });
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ test('all meta fields are displayed', async function(assert) {
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ assert.equal(
+ JobDispatch.metaFields.length,
+ job.parameterizedJob.MetaOptional.length + job.parameterizedJob.MetaRequired.length
+ );
+ });
- assert.ok(JobDispatch.payload.emptyMessage.isPresent);
- assert.notOk(JobDispatch.payload.editor.isPresent);
- });
+ test('required meta fields are properly indicated', async function(assert) {
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- test('payload is indicated as required', async function(assert) {
- const jobPayloadRequired = server.create('job', 'parameterized', {
- status: 'running',
- namespaceId: namespace.name,
- parameterizedJob: {
- Payload: 'required',
- },
+ JobDispatch.metaFields.forEach(f => {
+ const hasIndicator = f.label.includes(REQUIRED_INDICATOR);
+ const isRequired = job.parameterizedJob.MetaRequired.includes(f.field.id);
+
+ if (isRequired) {
+ assert.ok(hasIndicator, `${f.label} contains required indicator.`);
+ } else {
+ assert.notOk(hasIndicator, `${f.label} doesn't contain required indicator.`);
+ }
+ });
});
- const jobPayloadOptional = server.create('job', 'parameterized', {
- status: 'running',
- namespaceId: namespace.name,
- parameterizedJob: {
- Payload: 'optional',
- },
+
+ test('job without meta fields', async function(assert) {
+ const jobWithoutMeta = server.create('job', 'parameterized', {
+ status: 'running',
+ namespaceId: namespace.name,
+ parameterizedJob: {
+ MetaRequired: null,
+ MetaOptional: null,
+ },
+ });
+
+ await JobDispatch.visit({ id: jobWithoutMeta.id, namespace: namespace.name });
+ assert.ok(JobDispatch.dispatchButton.isPresent);
});
- await JobDispatch.visit({ id: jobPayloadRequired.id, namespace: namespace.name });
+ test('payload text area is hidden when forbidden', async function(assert) {
+ job.parameterizedJob.Payload = 'forbidden';
+ job.save();
- let payloadTitle = JobDispatch.payload.title;
- assert.ok(
- payloadTitle.includes(REQUIRED_INDICATOR),
- `${payloadTitle} contains required indicator.`
- );
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- await JobDispatch.visit({ id: jobPayloadOptional.id, namespace: namespace.name });
+ assert.ok(JobDispatch.payload.emptyMessage.isPresent);
+ assert.notOk(JobDispatch.payload.editor.isPresent);
+ });
- payloadTitle = JobDispatch.payload.title;
- assert.notOk(
- payloadTitle.includes(REQUIRED_INDICATOR),
- `${payloadTitle} doesn't contain required indicator.`
- );
- });
+ test('payload is indicated as required', async function(assert) {
+ const jobPayloadRequired = server.create('job', 'parameterized', {
+ status: 'running',
+ namespaceId: namespace.name,
+ parameterizedJob: {
+ Payload: 'required',
+ },
+ });
+ const jobPayloadOptional = server.create('job', 'parameterized', {
+ status: 'running',
+ namespaceId: namespace.name,
+ parameterizedJob: {
+ Payload: 'optional',
+ },
+ });
+
+ await JobDispatch.visit({ id: jobPayloadRequired.id, namespace: namespace.name });
+
+ let payloadTitle = JobDispatch.payload.title;
+ assert.ok(
+ payloadTitle.includes(REQUIRED_INDICATOR),
+ `${payloadTitle} contains required indicator.`
+ );
+
+ await JobDispatch.visit({ id: jobPayloadOptional.id, namespace: namespace.name });
+
+ payloadTitle = JobDispatch.payload.title;
+ assert.notOk(
+ payloadTitle.includes(REQUIRED_INDICATOR),
+ `${payloadTitle} doesn't contain required indicator.`
+ );
+ });
- test('dispatch a job', async function(assert) {
- function countDispatchChildren() {
- return server.db.jobs.where(j => j.id.startsWith(`${job.id}/`)).length;
- }
+ test('dispatch a job', async function(assert) {
+ function countDispatchChildren() {
+ return server.db.jobs.where(j => j.id.startsWith(`${job.id}/`)).length;
+ }
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- // Fill form.
- JobDispatch.metaFields.map(f => f.field.input('meta value'));
- JobDispatch.payload.editor.fillIn('payload');
+ // Fill form.
+ JobDispatch.metaFields.map(f => f.field.input('meta value'));
+ JobDispatch.payload.editor.fillIn('payload');
- const childrenCountBefore = countDispatchChildren();
- await JobDispatch.dispatchButton.click();
- const childrenCountAfter = countDispatchChildren();
+ const childrenCountBefore = countDispatchChildren();
+ await JobDispatch.dispatchButton.click();
+ const childrenCountAfter = countDispatchChildren();
- assert.equal(childrenCountAfter, childrenCountBefore + 1);
- assert.ok(currentURL().startsWith(`/jobs/${encodeURIComponent(`${job.id}/`)}`));
- });
+ assert.equal(childrenCountAfter, childrenCountBefore + 1);
+ assert.ok(currentURL().startsWith(`/jobs/${encodeURIComponent(`${job.id}/`)}`));
+ assert.ok(JobDetail.jobName);
+ });
- test('fail when required meta field is empty', async function(assert) {
- // Make sure we have a required meta param.
- job.parameterizedJob.MetaRequired = ['required'];
- job.parameterizedJob.Payload = 'forbidden';
- job.save();
+ test('fail when required meta field is empty', async function(assert) {
+ // Make sure we have a required meta param.
+ job.parameterizedJob.MetaRequired = ['required'];
+ job.parameterizedJob.Payload = 'forbidden';
+ job.save();
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- // Fill only optional meta params.
- JobDispatch.optionalMetaFields.map(f => f.field.input('meta value'));
+ // Fill only optional meta params.
+ JobDispatch.optionalMetaFields.map(f => f.field.input('meta value'));
- await JobDispatch.dispatchButton.click();
+ await JobDispatch.dispatchButton.click();
- assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
- });
+ assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
+ });
- test('fail when required payload is empty', async function(assert) {
- job.parameterizedJob.MetaRequired = [];
- job.parameterizedJob.Payload = 'required';
- job.save();
+ test('fail when required payload is empty', async function(assert) {
+ job.parameterizedJob.MetaRequired = [];
+ job.parameterizedJob.Payload = 'required';
+ job.save();
- await JobDispatch.visit({ id: job.id, namespace: namespace.name });
- await JobDispatch.dispatchButton.click();
+ await JobDispatch.visit({ id: job.id, namespace: namespace.name });
+ await JobDispatch.dispatchButton.click();
- assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
+ assert.ok(JobDispatch.hasError, 'Dispatch error message is shown');
+ });
});
-});
+}
diff --git a/ui/tests/integration/components/job-page/periodic-test.js b/ui/tests/integration/components/job-page/periodic-test.js
index f4a38c934c3..edcec6b5442 100644
--- a/ui/tests/integration/components/job-page/periodic-test.js
+++ b/ui/tests/integration/components/job-page/periodic-test.js
@@ -75,7 +75,7 @@ module('Integration | Component | job-page/periodic', function(hooks) {
const currentJobCount = server.db.jobs.length;
assert.equal(
- findAll('[data-test-job-name]').length,
+ findAll('[data-test-job-row] [data-test-job-name]').length,
childrenCount,
'The new periodic job launch is in the children list'
);
diff --git a/ui/tests/pages/jobs/detail.js b/ui/tests/pages/jobs/detail.js
index 9d2b679c584..cc6b64634b9 100644
--- a/ui/tests/pages/jobs/detail.js
+++ b/ui/tests/pages/jobs/detail.js
@@ -17,6 +17,8 @@ import recommendationAccordion from 'nomad-ui/tests/pages/components/recommendat
export default create({
visit: visitable('/jobs/:id'),
+ jobName: text('[data-test-job-name]'),
+
tabs: collection('[data-test-tab]', {
id: attribute('data-test-tab'),
visit: clickable('a'),