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
@classNames('boxed-section')
export default class Children extends Component.extend(Sortable) {
@service system;
@service userSettings;
job = null;

View File

@ -1,6 +1,6 @@
import Route from '@ember/routing/route';
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';
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),
latestDeployment:
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;
@watchAll('job') watchAll;
@watchQuery('job') watchAllJobs;
@watchRecord('job-summary') watchSummary;
@watchRelationship('allocations') watchAllocations;
@watchRelationship('evaluations') watchEvaluations;
@ -40,7 +42,7 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
@collect(
'watch',
'watchAll',
'watchAllJobs',
'watchSummary',
'watchAllocations',
'watchEvaluations',

View File

@ -31,9 +31,12 @@
@sortProperty={{this.sortProperty}}
@sortDescending={{this.sortDescending}}
@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="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="type">Type</t.sort-by>
<t.sort-by @prop="priority">Priority</t.sort-by>

View File

@ -25,6 +25,7 @@ moduleForJob(
.sortBy('submitTime')
.reverse()[0];
assert.ok(JobDetail.jobsHeader.hasSubmitTime);
assert.equal(
JobDetail.jobs[0].submitTime,
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(
'Acceptance | job detail (parameterized)',
'children',
@ -44,6 +64,7 @@ moduleForJob(
.sortBy('submitTime')
.reverse()[0];
assert.ok(JobDetail.jobsHeader.hasSubmitTime);
assert.equal(
JobDetail.jobs[0].submitTime,
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', () => {
const parent = server.create('job', 'periodic', { childrenCount: 1, shallow: true });
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() {
server.create('node');
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) {
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`);
});
test('the subnav links to overview', async function(assert) {
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) {
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) {
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) {
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) {
@ -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]'),
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]', {
id: attribute('data-test-job-row'),
name: text('[data-test-job-name]'),
namespace: text('[data-test-job-namespace]'),
link: attribute('href', '[data-test-job-name] a'),
submitTime: text('[data-test-job-submit-time]'),
status: text('[data-test-job-status]'),