2023-04-10 15:36:59 +00:00
|
|
|
|
/**
|
|
|
|
|
* Copyright (c) HashiCorp, Inc.
|
|
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2021-02-17 21:01:44 +00:00
|
|
|
|
/* eslint-disable ember/no-test-module-for */
|
2021-12-28 19:30:38 +00:00
|
|
|
|
/* eslint-disable qunit/require-expect */
|
2023-07-20 16:37:06 +00:00
|
|
|
|
import { currentURL, settled } from '@ember/test-helpers';
|
2019-03-13 00:04:16 +00:00
|
|
|
|
import { module, test } from 'qunit';
|
|
|
|
|
import { setupApplicationTest } from 'ember-qunit';
|
2019-09-26 18:47:07 +00:00
|
|
|
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
2020-09-19 05:20:09 +00:00
|
|
|
|
import moment from 'moment';
|
2020-07-28 17:59:14 +00:00
|
|
|
|
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
2021-12-28 16:08:12 +00:00
|
|
|
|
import moduleForJob, {
|
|
|
|
|
moduleForJobWithClientStatus,
|
|
|
|
|
} from 'nomad-ui/tests/helpers/module-for-job';
|
2018-07-11 02:20:02 +00:00
|
|
|
|
import JobDetail from 'nomad-ui/tests/pages/jobs/detail';
|
2023-07-20 16:37:06 +00:00
|
|
|
|
import percySnapshot from '@percy/ember';
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2018-11-08 05:04:27 +00:00
|
|
|
|
moduleForJob('Acceptance | job detail (batch)', 'allocations', () =>
|
2023-05-10 20:59:33 +00:00
|
|
|
|
server.create('job', {
|
|
|
|
|
type: 'batch',
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
2023-06-02 15:59:08 +00:00
|
|
|
|
createAllocations: true,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
2023-05-10 20:59:33 +00:00
|
|
|
|
})
|
2018-11-08 05:04:27 +00:00
|
|
|
|
);
|
2021-10-07 21:11:38 +00:00
|
|
|
|
|
2018-11-08 05:04:27 +00:00
|
|
|
|
moduleForJob('Acceptance | job detail (system)', 'allocations', () =>
|
2023-05-05 20:25:21 +00:00
|
|
|
|
server.create('job', {
|
|
|
|
|
type: 'system',
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
2023-06-02 15:59:08 +00:00
|
|
|
|
createAllocations: true,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
2023-05-05 20:25:21 +00:00
|
|
|
|
})
|
2023-03-06 15:06:31 +00:00
|
|
|
|
);
|
|
|
|
|
|
2021-10-07 21:11:38 +00:00
|
|
|
|
moduleForJob('Acceptance | job detail (sysbatch)', 'allocations', () =>
|
2023-05-19 19:51:35 +00:00
|
|
|
|
server.create('job', {
|
|
|
|
|
type: 'sysbatch',
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
2023-06-02 15:59:08 +00:00
|
|
|
|
createAllocations: true,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
failed: 1,
|
|
|
|
|
},
|
2023-05-19 19:51:35 +00:00
|
|
|
|
})
|
2021-10-07 21:11:38 +00:00
|
|
|
|
);
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
moduleForJobWithClientStatus(
|
|
|
|
|
'Acceptance | job detail with client status (sysbatch)',
|
|
|
|
|
() =>
|
|
|
|
|
server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
datacenters: ['dc1'],
|
|
|
|
|
type: 'sysbatch',
|
|
|
|
|
createAllocations: false,
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
})
|
2021-10-07 21:11:38 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
moduleForJobWithClientStatus(
|
|
|
|
|
'Acceptance | job detail with client status (sysbatch with namespace)',
|
|
|
|
|
() => {
|
|
|
|
|
const namespace = server.create('namespace', { id: 'test' });
|
|
|
|
|
return server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
datacenters: ['dc1'],
|
|
|
|
|
type: 'sysbatch',
|
|
|
|
|
namespaceId: namespace.name,
|
|
|
|
|
createAllocations: false,
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2021-10-07 21:11:38 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-06 15:06:31 +00:00
|
|
|
|
moduleForJobWithClientStatus(
|
|
|
|
|
'Acceptance | job detail with client status (sysbatch with namespace and wildcard dc)',
|
|
|
|
|
() => {
|
|
|
|
|
const namespace = server.create('namespace', { id: 'test' });
|
|
|
|
|
return server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
datacenters: ['*'],
|
|
|
|
|
type: 'sysbatch',
|
|
|
|
|
namespaceId: namespace.name,
|
|
|
|
|
createAllocations: false,
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2023-03-06 15:06:31 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2021-10-07 21:11:38 +00:00
|
|
|
|
moduleForJob('Acceptance | job detail (sysbatch child)', 'allocations', () => {
|
|
|
|
|
const parent = server.create('job', 'periodicSysbatch', {
|
|
|
|
|
childrenCount: 1,
|
|
|
|
|
shallow: true,
|
|
|
|
|
datacenters: ['dc1'],
|
2023-06-02 15:59:08 +00:00
|
|
|
|
createAllocations: true,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2021-10-07 21:11:38 +00:00
|
|
|
|
});
|
|
|
|
|
return server.db.jobs.where({ parentId: parent.id })[0];
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
moduleForJobWithClientStatus(
|
|
|
|
|
'Acceptance | job detail with client status (sysbatch child)',
|
|
|
|
|
() => {
|
|
|
|
|
const parent = server.create('job', 'periodicSysbatch', {
|
|
|
|
|
childrenCount: 1,
|
|
|
|
|
shallow: true,
|
|
|
|
|
datacenters: ['dc1'],
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
|
|
|
|
return server.db.jobs.where({ parentId: parent.id })[0];
|
|
|
|
|
}
|
|
|
|
|
);
|
2021-10-07 21:11:38 +00:00
|
|
|
|
|
|
|
|
|
moduleForJobWithClientStatus(
|
|
|
|
|
'Acceptance | job detail with client status (sysbatch child with namespace)',
|
|
|
|
|
() => {
|
|
|
|
|
const namespace = server.create('namespace', { id: 'test' });
|
|
|
|
|
const parent = server.create('job', 'periodicSysbatch', {
|
|
|
|
|
childrenCount: 1,
|
|
|
|
|
shallow: true,
|
|
|
|
|
namespaceId: namespace.name,
|
|
|
|
|
datacenters: ['dc1'],
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2021-10-07 21:11:38 +00:00
|
|
|
|
});
|
|
|
|
|
return server.db.jobs.where({ parentId: parent.id })[0];
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-06 15:06:31 +00:00
|
|
|
|
moduleForJobWithClientStatus(
|
|
|
|
|
'Acceptance | job detail with client status (sysbatch child with namespace and wildcard dc)',
|
|
|
|
|
() => {
|
|
|
|
|
const namespace = server.create('namespace', { id: 'test' });
|
|
|
|
|
const parent = server.create('job', 'periodicSysbatch', {
|
|
|
|
|
childrenCount: 1,
|
|
|
|
|
shallow: true,
|
|
|
|
|
namespaceId: namespace.name,
|
|
|
|
|
datacenters: ['*'],
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2023-03-06 15:06:31 +00:00
|
|
|
|
});
|
|
|
|
|
return server.db.jobs.where({ parentId: parent.id })[0];
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2020-09-19 05:20:09 +00:00
|
|
|
|
moduleForJob(
|
|
|
|
|
'Acceptance | job detail (periodic)',
|
|
|
|
|
'children',
|
|
|
|
|
() => server.create('job', 'periodic', { shallow: true }),
|
|
|
|
|
{
|
2021-12-28 14:45:20 +00:00
|
|
|
|
'the default sort is submitTime descending': async function (job, assert) {
|
2020-09-19 05:20:09 +00:00
|
|
|
|
const mostRecentLaunch = server.db.jobs
|
|
|
|
|
.where({ parentId: job.id })
|
|
|
|
|
.sortBy('submitTime')
|
|
|
|
|
.reverse()[0];
|
|
|
|
|
|
2021-09-01 18:40:25 +00:00
|
|
|
|
assert.ok(JobDetail.jobsHeader.hasSubmitTime);
|
2020-09-19 05:20:09 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
JobDetail.jobs[0].submitTime,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
moment(mostRecentLaunch.submitTime / 1000000).format(
|
|
|
|
|
'MMM DD HH:mm:ss ZZ'
|
|
|
|
|
)
|
2020-09-19 05:20:09 +00:00
|
|
|
|
);
|
|
|
|
|
},
|
2023-06-22 14:22:41 +00:00
|
|
|
|
"don't display redundant information in children table": async function (
|
|
|
|
|
job,
|
|
|
|
|
assert
|
|
|
|
|
) {
|
2023-06-22 17:11:44 +00:00
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasNodePool);
|
2023-06-22 14:22:41 +00:00
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasPriority);
|
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasType);
|
|
|
|
|
},
|
2020-09-19 05:20:09 +00:00
|
|
|
|
}
|
2018-11-08 05:04:27 +00:00
|
|
|
|
);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2021-09-01 18:40:25 +00:00
|
|
|
|
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;
|
|
|
|
|
},
|
|
|
|
|
{
|
2023-06-22 14:22:41 +00:00
|
|
|
|
"don't display namespace in children table": async function (job, assert) {
|
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasNamespace);
|
2021-09-01 18:40:25 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2020-09-19 05:20:09 +00:00
|
|
|
|
moduleForJob(
|
|
|
|
|
'Acceptance | job detail (parameterized)',
|
|
|
|
|
'children',
|
2023-05-19 19:51:35 +00:00
|
|
|
|
() =>
|
|
|
|
|
server.create('job', 'parameterized', {
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
|
|
|
|
}),
|
2020-09-19 05:20:09 +00:00
|
|
|
|
{
|
|
|
|
|
'the default sort is submitTime descending': async (job, assert) => {
|
|
|
|
|
const mostRecentLaunch = server.db.jobs
|
|
|
|
|
.where({ parentId: job.id })
|
|
|
|
|
.sortBy('submitTime')
|
|
|
|
|
.reverse()[0];
|
|
|
|
|
|
2021-09-01 18:40:25 +00:00
|
|
|
|
assert.ok(JobDetail.jobsHeader.hasSubmitTime);
|
2020-09-19 05:20:09 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
JobDetail.jobs[0].submitTime,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
moment(mostRecentLaunch.submitTime / 1000000).format(
|
|
|
|
|
'MMM DD HH:mm:ss ZZ'
|
|
|
|
|
)
|
2020-09-19 05:20:09 +00:00
|
|
|
|
);
|
|
|
|
|
},
|
2023-06-22 14:22:41 +00:00
|
|
|
|
"don't display redundant information in children table": async function (
|
|
|
|
|
job,
|
|
|
|
|
assert
|
|
|
|
|
) {
|
2023-06-22 17:11:44 +00:00
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasNodePool);
|
2023-06-22 14:22:41 +00:00
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasPriority);
|
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasType);
|
|
|
|
|
},
|
2020-09-19 05:20:09 +00:00
|
|
|
|
}
|
2018-02-02 01:22:09 +00:00
|
|
|
|
);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2021-09-01 18:40:25 +00:00
|
|
|
|
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;
|
|
|
|
|
},
|
|
|
|
|
{
|
2023-06-22 14:22:41 +00:00
|
|
|
|
"don't display namespace in children table": async function (job, assert) {
|
|
|
|
|
assert.notOk(JobDetail.jobsHeader.hasNamespace);
|
2021-09-01 18:40:25 +00:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
2018-11-08 05:04:27 +00:00
|
|
|
|
moduleForJob('Acceptance | job detail (periodic child)', 'allocations', () => {
|
2021-12-28 16:08:12 +00:00
|
|
|
|
const parent = server.create('job', 'periodic', {
|
|
|
|
|
childrenCount: 1,
|
|
|
|
|
shallow: true,
|
2023-06-02 15:59:08 +00:00
|
|
|
|
createAllocations: true,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
|
|
|
|
noActiveDeployment: true,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
2018-02-02 01:22:09 +00:00
|
|
|
|
return server.db.jobs.where({ parentId: parent.id })[0];
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
moduleForJob(
|
|
|
|
|
'Acceptance | job detail (parameterized child)',
|
|
|
|
|
'allocations',
|
|
|
|
|
() => {
|
|
|
|
|
const parent = server.create('job', 'parameterized', {
|
|
|
|
|
childrenCount: 1,
|
|
|
|
|
shallow: true,
|
2023-05-19 19:51:35 +00:00
|
|
|
|
noActiveDeployment: true,
|
2023-06-02 15:59:08 +00:00
|
|
|
|
createAllocations: true,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
|
|
|
|
return server.db.jobs.where({ parentId: parent.id })[0];
|
|
|
|
|
}
|
|
|
|
|
);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2018-11-08 05:04:27 +00:00
|
|
|
|
moduleForJob(
|
|
|
|
|
'Acceptance | job detail (service)',
|
|
|
|
|
'allocations',
|
2023-05-05 20:25:21 +00:00
|
|
|
|
() => server.create('job', { type: 'service', noActiveDeployment: true }),
|
2018-11-08 05:04:27 +00:00
|
|
|
|
{
|
2019-03-14 06:44:53 +00:00
|
|
|
|
'the subnav links to deployment': async (job, assert) => {
|
|
|
|
|
await JobDetail.tabFor('deployments').visit();
|
|
|
|
|
assert.equal(currentURL(), `/jobs/${job.id}/deployments`);
|
|
|
|
|
},
|
2021-12-28 16:08:12 +00:00
|
|
|
|
'when the job is not found, an error message is shown, but the URL persists':
|
|
|
|
|
async (job, assert) => {
|
|
|
|
|
await JobDetail.visit({ id: 'not-a-real-job' });
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
server.pretender.handledRequests
|
|
|
|
|
.filter((request) => !request.url.includes('policy'))
|
|
|
|
|
.findBy('status', 404).url,
|
|
|
|
|
'/v1/job/not-a-real-job',
|
|
|
|
|
'A request to the nonexistent job is made'
|
|
|
|
|
);
|
|
|
|
|
assert.equal(currentURL(), '/jobs/not-a-real-job', 'The URL persists');
|
|
|
|
|
assert.ok(JobDetail.error.isPresent, 'Error message is shown');
|
|
|
|
|
assert.equal(
|
|
|
|
|
JobDetail.error.title,
|
|
|
|
|
'Not Found',
|
|
|
|
|
'Error message is for 404'
|
|
|
|
|
);
|
|
|
|
|
},
|
2018-11-08 05:04:27 +00:00
|
|
|
|
}
|
|
|
|
|
);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
module('Acceptance | job detail (with namespaces)', function (hooks) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
setupApplicationTest(hooks);
|
2019-03-14 06:44:53 +00:00
|
|
|
|
setupMirage(hooks);
|
|
|
|
|
|
2020-11-06 14:21:38 +00:00
|
|
|
|
let job, managementToken, clientToken;
|
2019-03-13 00:04:16 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
hooks.beforeEach(function () {
|
2017-10-23 17:30:11 +00:00
|
|
|
|
server.createList('namespace', 2);
|
2023-06-22 17:11:44 +00:00
|
|
|
|
server.create('node-pool');
|
2017-10-23 17:30:11 +00:00
|
|
|
|
server.create('node');
|
2020-08-25 15:56:02 +00:00
|
|
|
|
job = server.create('job', {
|
|
|
|
|
type: 'service',
|
|
|
|
|
status: 'running',
|
|
|
|
|
namespaceId: server.db.namespaces[1].name,
|
2023-05-05 20:25:21 +00:00
|
|
|
|
noActiveDeployment: true,
|
2020-10-29 12:46:42 +00:00
|
|
|
|
});
|
|
|
|
|
server.createList('job', 3, {
|
|
|
|
|
namespaceId: server.db.namespaces[0].name,
|
2020-08-25 15:56:02 +00:00
|
|
|
|
});
|
2020-05-11 19:43:17 +00:00
|
|
|
|
|
2020-11-06 14:21:38 +00:00
|
|
|
|
managementToken = server.create('token');
|
2020-05-11 19:43:17 +00:00
|
|
|
|
clientToken = server.create('token');
|
2019-03-13 00:04:16 +00:00
|
|
|
|
});
|
2017-10-23 17:30:11 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('it passes an accessibility audit', async function (assert) {
|
2020-07-28 17:59:14 +00:00
|
|
|
|
const namespace = server.db.namespaces.find(job.namespaceId);
|
2022-02-17 12:22:15 +00:00
|
|
|
|
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
2020-08-25 15:56:02 +00:00
|
|
|
|
await a11yAudit(assert);
|
2020-07-28 17:59:14 +00:00
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('when there are namespaces, the job detail page states the namespace for the job', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const namespace = server.db.namespaces.find(job.namespaceId);
|
2022-02-17 12:22:15 +00:00
|
|
|
|
|
|
|
|
|
await JobDetail.visit({
|
|
|
|
|
id: `${job.id}@${namespace.name}`,
|
|
|
|
|
});
|
2017-10-11 17:12:10 +00:00
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.ok(
|
|
|
|
|
JobDetail.statFor('namespace').text,
|
|
|
|
|
'Namespace included in stats'
|
|
|
|
|
);
|
2017-10-11 17:12:10 +00:00
|
|
|
|
});
|
2017-10-23 23:59:30 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('the exec button state can change between namespaces', async function (assert) {
|
2020-08-25 15:56:02 +00:00
|
|
|
|
const job1 = server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
namespaceId: server.db.namespaces[0].id,
|
|
|
|
|
});
|
|
|
|
|
const job2 = server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
namespaceId: server.db.namespaces[1].id,
|
|
|
|
|
});
|
2020-05-11 19:43:17 +00:00
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = clientToken.secretId;
|
|
|
|
|
|
|
|
|
|
const policy = server.create('policy', {
|
|
|
|
|
id: 'something',
|
|
|
|
|
name: 'something',
|
|
|
|
|
rulesJSON: {
|
|
|
|
|
Namespaces: [
|
|
|
|
|
{
|
|
|
|
|
Name: job1.namespaceId,
|
|
|
|
|
Capabilities: ['list-jobs', 'alloc-exec'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: job2.namespaceId,
|
|
|
|
|
Capabilities: ['list-jobs'],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
clientToken.policyIds = [policy.id];
|
|
|
|
|
clientToken.save();
|
|
|
|
|
|
|
|
|
|
await JobDetail.visit({ id: job1.id });
|
|
|
|
|
assert.notOk(JobDetail.execButton.isDisabled);
|
|
|
|
|
|
|
|
|
|
const secondNamespace = server.db.namespaces[1];
|
2022-02-17 12:22:15 +00:00
|
|
|
|
await JobDetail.visit({ id: `${job2.id}@${secondNamespace.name}` });
|
|
|
|
|
|
2020-05-11 19:43:17 +00:00
|
|
|
|
assert.ok(JobDetail.execButton.isDisabled);
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('the anonymous policy is fetched to check whether to show the exec button', async function (assert) {
|
2020-05-11 19:43:17 +00:00
|
|
|
|
window.localStorage.removeItem('nomadTokenSecret');
|
|
|
|
|
|
|
|
|
|
server.create('policy', {
|
|
|
|
|
id: 'anonymous',
|
|
|
|
|
name: 'anonymous',
|
|
|
|
|
rulesJSON: {
|
|
|
|
|
Namespaces: [
|
|
|
|
|
{
|
|
|
|
|
Name: 'default',
|
|
|
|
|
Capabilities: ['list-jobs', 'alloc-exec'],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
await JobDetail.visit({
|
2022-02-17 11:45:01 +00:00
|
|
|
|
id: `${job.id}@${server.db.namespaces[1].name}`,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
2022-02-17 11:45:01 +00:00
|
|
|
|
|
2020-05-11 19:43:17 +00:00
|
|
|
|
assert.notOk(JobDetail.execButton.isDisabled);
|
|
|
|
|
});
|
2020-10-29 12:46:42 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('meta table is displayed if job has meta attributes', async function (assert) {
|
2021-10-12 20:36:10 +00:00
|
|
|
|
const jobWithMeta = server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
namespaceId: server.db.namespaces[1].id,
|
|
|
|
|
meta: {
|
|
|
|
|
'a.b': 'c',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
await JobDetail.visit({
|
2022-02-17 12:22:15 +00:00
|
|
|
|
id: `${job.id}@${server.db.namespaces[1].name}`,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
2022-02-17 12:22:15 +00:00
|
|
|
|
|
2021-10-12 20:36:10 +00:00
|
|
|
|
assert.notOk(JobDetail.metaTable, 'Meta table not present');
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
await JobDetail.visit({
|
2022-02-17 12:22:15 +00:00
|
|
|
|
id: `${jobWithMeta.id}@${server.db.namespaces[1].name}`,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
2021-10-12 20:36:10 +00:00
|
|
|
|
assert.ok(JobDetail.metaTable, 'Meta table is present');
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('pack details are displayed', async function (assert) {
|
2021-10-12 20:36:10 +00:00
|
|
|
|
const namespace = server.db.namespaces[1].id;
|
|
|
|
|
const jobFromPack = server.create('job', {
|
|
|
|
|
status: 'running',
|
|
|
|
|
namespaceId: namespace,
|
|
|
|
|
meta: {
|
|
|
|
|
'pack.name': 'my-pack',
|
|
|
|
|
'pack.version': '1.0.0',
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2022-02-17 11:59:58 +00:00
|
|
|
|
await JobDetail.visit({ id: `${jobFromPack.id}@${namespace}` });
|
2022-02-17 12:22:15 +00:00
|
|
|
|
|
2021-10-12 20:36:10 +00:00
|
|
|
|
assert.ok(JobDetail.packTag, 'Pack tag is present');
|
|
|
|
|
assert.equal(
|
|
|
|
|
JobDetail.packStatFor('name').text,
|
|
|
|
|
`Name ${jobFromPack.meta['pack.name']}`,
|
|
|
|
|
`Pack name is ${jobFromPack.meta['pack.name']}`
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
JobDetail.packStatFor('version').text,
|
|
|
|
|
`Version ${jobFromPack.meta['pack.version']}`,
|
|
|
|
|
`Pack version is ${jobFromPack.meta['pack.version']}`
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('resource recommendations show when they exist and can be expanded, collapsed, and processed', async function (assert) {
|
2020-11-06 14:21:38 +00:00
|
|
|
|
server.create('feature', { name: 'Dynamic Application Sizing' });
|
|
|
|
|
|
2020-12-10 16:10:25 +00:00
|
|
|
|
job = server.create('job', {
|
|
|
|
|
type: 'service',
|
|
|
|
|
status: 'running',
|
|
|
|
|
namespaceId: server.db.namespaces[1].name,
|
|
|
|
|
groupsCount: 3,
|
|
|
|
|
createRecommendations: true,
|
2023-05-05 20:25:21 +00:00
|
|
|
|
noActiveDeployment: true,
|
2020-12-10 16:10:25 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-11-06 14:21:38 +00:00
|
|
|
|
window.localStorage.nomadTokenSecret = managementToken.secretId;
|
2021-12-28 16:08:12 +00:00
|
|
|
|
await JobDetail.visit({
|
2022-02-17 12:22:15 +00:00
|
|
|
|
id: `${job.id}@${server.db.namespaces[1].name}`,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
2020-10-29 12:46:42 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
const groupsWithRecommendations = job.taskGroups.filter((group) =>
|
|
|
|
|
group.tasks.models.any((task) => task.recommendations.models.length)
|
2020-12-10 16:10:25 +00:00
|
|
|
|
);
|
|
|
|
|
const jobRecommendationCount = groupsWithRecommendations.length;
|
|
|
|
|
|
|
|
|
|
const firstRecommendationGroup = groupsWithRecommendations.models[0];
|
|
|
|
|
|
|
|
|
|
assert.equal(JobDetail.recommendations.length, jobRecommendationCount);
|
2020-10-29 12:46:42 +00:00
|
|
|
|
|
|
|
|
|
const recommendation = JobDetail.recommendations[0];
|
|
|
|
|
|
2020-12-10 16:10:25 +00:00
|
|
|
|
assert.equal(recommendation.group, firstRecommendationGroup.name);
|
2020-10-29 12:46:42 +00:00
|
|
|
|
assert.ok(recommendation.card.isHidden);
|
|
|
|
|
|
|
|
|
|
const toggle = recommendation.toggleButton;
|
|
|
|
|
|
|
|
|
|
assert.equal(toggle.text, 'Show');
|
|
|
|
|
|
|
|
|
|
await toggle.click();
|
|
|
|
|
|
|
|
|
|
assert.ok(recommendation.card.isPresent);
|
|
|
|
|
assert.equal(toggle.text, 'Collapse');
|
|
|
|
|
|
|
|
|
|
await toggle.click();
|
|
|
|
|
|
|
|
|
|
assert.ok(recommendation.card.isHidden);
|
|
|
|
|
|
|
|
|
|
await toggle.click();
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
recommendation.card.slug.groupName,
|
|
|
|
|
firstRecommendationGroup.name
|
|
|
|
|
);
|
2020-10-29 12:46:42 +00:00
|
|
|
|
|
|
|
|
|
await recommendation.card.acceptButton.click();
|
|
|
|
|
|
2020-12-10 16:10:25 +00:00
|
|
|
|
assert.equal(JobDetail.recommendations.length, jobRecommendationCount - 1);
|
2020-10-29 12:46:42 +00:00
|
|
|
|
|
|
|
|
|
await JobDetail.tabFor('definition').visit();
|
|
|
|
|
await JobDetail.tabFor('overview').visit();
|
|
|
|
|
|
2020-12-10 16:10:25 +00:00
|
|
|
|
assert.equal(JobDetail.recommendations.length, jobRecommendationCount - 1);
|
2020-10-29 12:46:42 +00:00
|
|
|
|
});
|
2020-11-06 14:21:38 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('resource recommendations are not fetched when the feature doesn’t exist', async function (assert) {
|
2020-11-06 14:21:38 +00:00
|
|
|
|
window.localStorage.nomadTokenSecret = managementToken.secretId;
|
2021-12-28 16:08:12 +00:00
|
|
|
|
await JobDetail.visit({
|
2022-02-17 12:22:15 +00:00
|
|
|
|
id: `${job.id}@${server.db.namespaces[1].name}`,
|
2021-12-28 16:08:12 +00:00
|
|
|
|
});
|
2020-11-06 14:21:38 +00:00
|
|
|
|
|
|
|
|
|
assert.equal(JobDetail.recommendations.length, 0);
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
2021-12-28 16:08:12 +00:00
|
|
|
|
server.pretender.handledRequests.filter((request) =>
|
|
|
|
|
request.url.includes('recommendations')
|
|
|
|
|
).length,
|
2020-11-06 14:21:38 +00:00
|
|
|
|
0
|
|
|
|
|
);
|
|
|
|
|
});
|
2021-07-14 20:27:24 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('when the dynamic autoscaler is applied, you can scale a task within the job detail page', async function (assert) {
|
2021-07-14 20:27:24 +00:00
|
|
|
|
const SCALE_AND_WRITE_NAMESPACE = 'scale-and-write-namespace';
|
|
|
|
|
const READ_ONLY_NAMESPACE = 'read-only-namespace';
|
|
|
|
|
const clientToken = server.create('token');
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
const namespace = server.create('namespace', {
|
|
|
|
|
id: SCALE_AND_WRITE_NAMESPACE,
|
|
|
|
|
});
|
|
|
|
|
const secondNamespace = server.create('namespace', {
|
|
|
|
|
id: READ_ONLY_NAMESPACE,
|
|
|
|
|
});
|
2021-07-14 20:27:24 +00:00
|
|
|
|
|
|
|
|
|
job = server.create('job', {
|
|
|
|
|
groupCount: 0,
|
|
|
|
|
createAllocations: false,
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
|
|
|
|
namespaceId: SCALE_AND_WRITE_NAMESPACE,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const job2 = server.create('job', {
|
|
|
|
|
groupCount: 0,
|
|
|
|
|
createAllocations: false,
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
|
|
|
|
namespaceId: READ_ONLY_NAMESPACE,
|
|
|
|
|
});
|
|
|
|
|
const scalingGroup2 = server.create('task-group', {
|
|
|
|
|
job: job2,
|
|
|
|
|
name: 'scaling',
|
|
|
|
|
count: 1,
|
|
|
|
|
shallow: true,
|
|
|
|
|
withScaling: true,
|
|
|
|
|
});
|
|
|
|
|
job2.update({ taskGroupIds: [scalingGroup2.id] });
|
|
|
|
|
|
|
|
|
|
const policy = server.create('policy', {
|
|
|
|
|
id: 'something',
|
|
|
|
|
name: 'something',
|
|
|
|
|
rulesJSON: {
|
|
|
|
|
Namespaces: [
|
|
|
|
|
{
|
|
|
|
|
Name: SCALE_AND_WRITE_NAMESPACE,
|
|
|
|
|
Capabilities: ['scale-job', 'submit-job', 'read-job', 'list-jobs'],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Name: READ_ONLY_NAMESPACE,
|
|
|
|
|
Capabilities: ['list-jobs', 'read-job'],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const scalingGroup = server.create('task-group', {
|
|
|
|
|
job,
|
|
|
|
|
name: 'scaling',
|
|
|
|
|
count: 1,
|
|
|
|
|
shallow: true,
|
|
|
|
|
withScaling: true,
|
|
|
|
|
});
|
|
|
|
|
job.update({ taskGroupIds: [scalingGroup.id] });
|
|
|
|
|
|
|
|
|
|
clientToken.policyIds = [policy.id];
|
|
|
|
|
clientToken.save();
|
|
|
|
|
window.localStorage.nomadTokenSecret = clientToken.secretId;
|
|
|
|
|
|
2022-02-17 12:22:15 +00:00
|
|
|
|
await JobDetail.visit({ id: `${job.id}@${namespace.name}` });
|
2021-07-14 20:27:24 +00:00
|
|
|
|
assert.notOk(JobDetail.incrementButton.isDisabled);
|
|
|
|
|
|
2022-02-17 12:22:15 +00:00
|
|
|
|
await JobDetail.visit({ id: `${job2.id}@${secondNamespace.name}` });
|
2021-07-14 20:27:24 +00:00
|
|
|
|
assert.ok(JobDetail.incrementButton.isDisabled);
|
|
|
|
|
});
|
2023-07-20 16:37:06 +00:00
|
|
|
|
|
|
|
|
|
test('handles when a job is remotely purged', async function (assert) {
|
|
|
|
|
const namespace = server.create('namespace');
|
|
|
|
|
const job = server.create('job', {
|
|
|
|
|
namespaceId: namespace.id,
|
|
|
|
|
status: 'running',
|
|
|
|
|
type: 'service',
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
|
|
|
|
createAllocations: true,
|
|
|
|
|
groupsCount: 1,
|
|
|
|
|
groupTaskCount: 1,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await JobDetail.visit({ id: `${job.id}@${namespace.id}` });
|
|
|
|
|
|
|
|
|
|
assert.equal(currentURL(), `/jobs/${job.id}%40${namespace.id}`);
|
|
|
|
|
|
|
|
|
|
// Simulate a 404 error on the job watcher
|
|
|
|
|
const controller = this.owner.lookup('controller:jobs.job');
|
|
|
|
|
let jobWatcher = controller.watchers.job;
|
|
|
|
|
jobWatcher.isError = true;
|
|
|
|
|
jobWatcher.error = { errors: [{ status: '404' }] };
|
|
|
|
|
await settled();
|
|
|
|
|
|
|
|
|
|
// User should be booted off the page
|
|
|
|
|
assert.equal(currentURL(), '/jobs?namespace=*');
|
|
|
|
|
|
|
|
|
|
// A notification should be present
|
|
|
|
|
assert
|
|
|
|
|
.dom('.flash-message.alert-critical')
|
|
|
|
|
.exists('A toast error message pops up.');
|
|
|
|
|
|
|
|
|
|
await percySnapshot(assert);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('handles when a job is remotely purged, from a job subnav page', async function (assert) {
|
|
|
|
|
const namespace = server.create('namespace');
|
|
|
|
|
const job = server.create('job', {
|
|
|
|
|
namespaceId: namespace.id,
|
|
|
|
|
status: 'running',
|
|
|
|
|
type: 'service',
|
|
|
|
|
shallow: true,
|
|
|
|
|
noActiveDeployment: true,
|
|
|
|
|
createAllocations: true,
|
|
|
|
|
groupsCount: 1,
|
|
|
|
|
groupTaskCount: 1,
|
|
|
|
|
allocStatusDistribution: {
|
|
|
|
|
running: 1,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await JobDetail.visit({ id: `${job.id}@${namespace.id}` });
|
|
|
|
|
await JobDetail.tabFor('allocations').visit();
|
|
|
|
|
|
|
|
|
|
assert.equal(currentURL(), `/jobs/${job.id}@${namespace.id}/allocations`);
|
|
|
|
|
|
|
|
|
|
// Simulate a 404 error on the job watcher
|
|
|
|
|
const controller = this.owner.lookup('controller:jobs.job');
|
|
|
|
|
let jobWatcher = controller.watchers.job;
|
|
|
|
|
jobWatcher.isError = true;
|
|
|
|
|
jobWatcher.error = { errors: [{ status: '404' }] };
|
|
|
|
|
await settled();
|
|
|
|
|
|
|
|
|
|
// User should be booted off the page
|
|
|
|
|
assert.equal(currentURL(), '/jobs?namespace=*');
|
|
|
|
|
|
|
|
|
|
// A notification should be present
|
|
|
|
|
assert
|
|
|
|
|
.dom('.flash-message.alert-critical')
|
|
|
|
|
.exists('A toast error message pops up.');
|
|
|
|
|
});
|
2017-10-23 23:59:30 +00:00
|
|
|
|
});
|