ui: set namespace when looking for and displaying children jobs (#11110)

This commit is contained in:
Luiz Aoqui 2021-09-01 14:40:25 -04:00 committed by GitHub
parent f09d5ebcd6
commit eb0ed980a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 12 deletions

3
.changelog/11110.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fixed an issue that prevented periodic and dispatch jobs in a non-default namespace to be properly rendered
```

View File

@ -9,6 +9,7 @@ import classic from 'ember-classic-decorator';
@classic @classic
@classNames('boxed-section') @classNames('boxed-section')
export default class Children extends Component.extend(Sortable) { export default class Children extends Component.extend(Sortable) {
@service system;
@service userSettings; @service userSettings;
job = null; job = null;

View File

@ -1,6 +1,6 @@
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed'; import { collect } from '@ember/object/computed';
import { watchRecord, watchRelationship, watchAll } from 'nomad-ui/utils/properties/watch'; import { watchRecord, watchRelationship, watchQuery } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers'; import WithWatchers from 'nomad-ui/mixins/with-watchers';
export default class IndexRoute extends Route.extend(WithWatchers) { export default class IndexRoute extends Route.extend(WithWatchers) {
@ -15,7 +15,9 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
evaluations: this.watchEvaluations.perform(model), evaluations: this.watchEvaluations.perform(model),
latestDeployment: latestDeployment:
model.get('supportsDeployments') && this.watchLatestDeployment.perform(model), model.get('supportsDeployments') && this.watchLatestDeployment.perform(model),
list: model.get('hasChildren') && this.watchAll.perform(), list:
model.get('hasChildren') &&
this.watchAllJobs.perform({ namespace: model.namespace.get('name') }),
}); });
} }
@ -32,7 +34,7 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
} }
@watchRecord('job') watch; @watchRecord('job') watch;
@watchAll('job') watchAll; @watchQuery('job') watchAllJobs;
@watchRecord('job-summary') watchSummary; @watchRecord('job-summary') watchSummary;
@watchRelationship('allocations') watchAllocations; @watchRelationship('allocations') watchAllocations;
@watchRelationship('evaluations') watchEvaluations; @watchRelationship('evaluations') watchEvaluations;
@ -40,7 +42,7 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
@collect( @collect(
'watch', 'watch',
'watchAll', 'watchAllJobs',
'watchSummary', 'watchSummary',
'watchAllocations', 'watchAllocations',
'watchEvaluations', 'watchEvaluations',

View File

@ -31,9 +31,12 @@
@sortProperty={{this.sortProperty}} @sortProperty={{this.sortProperty}}
@sortDescending={{this.sortDescending}} @sortDescending={{this.sortDescending}}
@class="with-foot" as |t|> @class="with-foot" as |t|>
<t.head> <t.head data-test-jobs-header>
<t.sort-by @prop="name">Name</t.sort-by> <t.sort-by @prop="name">Name</t.sort-by>
<t.sort-by @prop="submitTime">Submitted At</t.sort-by> {{#if this.system.shouldShowNamespaces}}
<t.sort-by @prop="namespace.name" data-test-jobs-namespace-header>Namespace</t.sort-by>
{{/if}}
<t.sort-by @prop="submitTime" data-test-jobs-submit-time-header>Submitted At</t.sort-by>
<t.sort-by @prop="status">Status</t.sort-by> <t.sort-by @prop="status">Status</t.sort-by>
<t.sort-by @prop="type">Type</t.sort-by> <t.sort-by @prop="type">Type</t.sort-by>
<t.sort-by @prop="priority">Priority</t.sort-by> <t.sort-by @prop="priority">Priority</t.sort-by>

View File

@ -25,6 +25,7 @@ moduleForJob(
.sortBy('submitTime') .sortBy('submitTime')
.reverse()[0]; .reverse()[0];
assert.ok(JobDetail.jobsHeader.hasSubmitTime);
assert.equal( assert.equal(
JobDetail.jobs[0].submitTime, JobDetail.jobs[0].submitTime,
moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ') moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ')
@ -33,6 +34,25 @@ moduleForJob(
} }
); );
moduleForJob(
'Acceptance | job detail (periodic in namespace)',
'children',
() => {
const namespace = server.create('namespace', { id: 'test' });
const parent = server.create('job', 'periodic', {
shallow: true,
namespaceId: namespace.name,
});
return parent;
},
{
'display namespace in children table': async function(job, assert) {
assert.ok(JobDetail.jobsHeader.hasNamespace);
assert.equal(JobDetail.jobs[0].namespace, job.namespace);
},
}
);
moduleForJob( moduleForJob(
'Acceptance | job detail (parameterized)', 'Acceptance | job detail (parameterized)',
'children', 'children',
@ -44,6 +64,7 @@ moduleForJob(
.sortBy('submitTime') .sortBy('submitTime')
.reverse()[0]; .reverse()[0];
assert.ok(JobDetail.jobsHeader.hasSubmitTime);
assert.equal( assert.equal(
JobDetail.jobs[0].submitTime, JobDetail.jobs[0].submitTime,
moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ') moment(mostRecentLaunch.submitTime / 1000000).format('MMM DD HH:mm:ss ZZ')
@ -52,6 +73,25 @@ moduleForJob(
} }
); );
moduleForJob(
'Acceptance | job detail (parameterized in namespace)',
'children',
() => {
const namespace = server.create('namespace', { id: 'test' });
const parent = server.create('job', 'parameterized', {
shallow: true,
namespaceId: namespace.name,
});
return parent;
},
{
'display namespace in children table': async function(job, assert) {
assert.ok(JobDetail.jobsHeader.hasNamespace);
assert.equal(JobDetail.jobs[0].namespace, job.namespace);
},
}
);
moduleForJob('Acceptance | job detail (periodic child)', 'allocations', () => { moduleForJob('Acceptance | job detail (periodic child)', 'allocations', () => {
const parent = server.create('job', 'periodic', { childrenCount: 1, shallow: true }); const parent = server.create('job', 'periodic', { childrenCount: 1, shallow: true });
return server.db.jobs.where({ parentId: parent.id })[0]; return server.db.jobs.where({ parentId: parent.id })[0];

View File

@ -22,32 +22,51 @@ export default function moduleForJob(title, context, jobFactory, additionalTests
hooks.beforeEach(async function() { hooks.beforeEach(async function() {
server.create('node'); server.create('node');
job = jobFactory(); job = jobFactory();
await JobDetail.visit({ id: job.id }); if (!job.namespace || job.namespace === 'default') {
await JobDetail.visit({ id: job.id });
} else {
await JobDetail.visit({ id: job.id, namespace: job.namespace });
}
}); });
test('visiting /jobs/:job_id', async function(assert) { test('visiting /jobs/:job_id', async function(assert) {
assert.equal(currentURL(), `/jobs/${encodeURIComponent(job.id)}`); assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace)
);
assert.equal(document.title, `Job ${job.name} - Nomad`); assert.equal(document.title, `Job ${job.name} - Nomad`);
}); });
test('the subnav links to overview', async function(assert) { test('the subnav links to overview', async function(assert) {
await JobDetail.tabFor('overview').visit(); await JobDetail.tabFor('overview').visit();
assert.equal(currentURL(), `/jobs/${encodeURIComponent(job.id)}`); assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace)
);
}); });
test('the subnav links to definition', async function(assert) { test('the subnav links to definition', async function(assert) {
await JobDetail.tabFor('definition').visit(); await JobDetail.tabFor('definition').visit();
assert.equal(currentURL(), `/jobs/${encodeURIComponent(job.id)}/definition`); assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/definition`, job.namespace)
);
}); });
test('the subnav links to versions', async function(assert) { test('the subnav links to versions', async function(assert) {
await JobDetail.tabFor('versions').visit(); await JobDetail.tabFor('versions').visit();
assert.equal(currentURL(), `/jobs/${encodeURIComponent(job.id)}/versions`); assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/versions`, job.namespace)
);
}); });
test('the subnav links to evaluations', async function(assert) { test('the subnav links to evaluations', async function(assert) {
await JobDetail.tabFor('evaluations').visit(); await JobDetail.tabFor('evaluations').visit();
assert.equal(currentURL(), `/jobs/${encodeURIComponent(job.id)}/evaluations`); assert.equal(
currentURL(),
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}/evaluations`, job.namespace)
);
}); });
test('the title buttons are dependent on job status', async function(assert) { test('the title buttons are dependent on job status', async function(assert) {
@ -99,3 +118,11 @@ export default function moduleForJob(title, context, jobFactory, additionalTests
} }
}); });
} }
function urlWithNamespace(url, namespace) {
if (!namespace || namespace === 'default') {
return url;
}
return `${url}?namespace=${namespace}`;
}

View File

@ -64,9 +64,16 @@ export default create({
viewAllAllocations: text('[data-test-view-all-allocations]'), viewAllAllocations: text('[data-test-view-all-allocations]'),
jobsHeader: {
scope: '[data-test-jobs-header]',
hasSubmitTime: isPresent('[data-test-jobs-submit-time-header]'),
hasNamespace: isPresent('[data-test-jobs-namespace-header]'),
},
jobs: collection('[data-test-job-row]', { jobs: collection('[data-test-job-row]', {
id: attribute('data-test-job-row'), id: attribute('data-test-job-row'),
name: text('[data-test-job-name]'), name: text('[data-test-job-name]'),
namespace: text('[data-test-job-namespace]'),
link: attribute('href', '[data-test-job-name] a'), link: attribute('href', '[data-test-job-name] a'),
submitTime: text('[data-test-job-submit-time]'), submitTime: text('[data-test-job-submit-time]'),
status: text('[data-test-job-status]'), status: text('[data-test-job-status]'),