ui: fix remaining linting errors

This commit is contained in:
Jai Bhagat 2022-01-20 10:39:02 -05:00
parent 3350f3fb11
commit 52cf998e2c
25 changed files with 426 additions and 353 deletions

View file

@ -21,14 +21,14 @@ export default class Client extends AbstractAbility {
get policiesIncludeNodeRead() { get policiesIncludeNodeRead() {
return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [ return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [
'read', 'read',
'write' 'write',
]); ]);
} }
@computed('token.selfTokenPolicies.[]') @computed('token.selfTokenPolicies.[]')
get policiesIncludeNodeWrite() { get policiesIncludeNodeWrite() {
return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [ return policiesIncludePermissions(this.get('token.selfTokenPolicies'), [
'write' 'write',
]); ]);
} }
} }
@ -37,9 +37,9 @@ function policiesIncludePermissions(policies = [], permissions = []) {
// For each policy record, extract the Node policy // For each policy record, extract the Node policy
const nodePolicies = policies const nodePolicies = policies
.toArray() .toArray()
.map(policy => get(policy, 'rulesJSON.Node.Policy')) .map((policy) => get(policy, 'rulesJSON.Node.Policy'))
.compact(); .compact();
// Check for requested permissions // Check for requested permissions
return nodePolicies.some(policy => permissions.includes(policy)); return nodePolicies.some((policy) => permissions.includes(policy));
} }

View file

@ -49,7 +49,7 @@ export default class Trigger extends Component {
this.error = null; this.error = null;
} }
@task(function*() { @task(function* () {
this._reset(); this._reset();
try { try {
this.result = yield this.args.do(); this.result = yield this.args.do();

View file

@ -35,7 +35,12 @@ export default class AllocationsAllocationController extends Controller {
{ {
title: 'Task Group', title: 'Task Group',
label: allocation.taskGroupName, label: allocation.taskGroupName,
args: ['jobs.job.task-group', job.plainId, allocation.taskGroupName, jobQueryParams], args: [
'jobs.job.task-group',
job.plainId,
allocation.taskGroupName,
jobQueryParams,
],
}, },
{ {
title: 'Allocation', title: 'Allocation',

View file

@ -9,7 +9,11 @@ export default class AllocationsAllocationTaskController extends Controller {
return { return {
title: 'Task', title: 'Task',
label: this.task.get('name'), label: this.task.get('name'),
args: ['allocations.allocation.task', this.task.get('allocation'), this.task], args: [
'allocations.allocation.task',
this.task.get('allocation'),
this.task,
],
}; };
} }
} }

View file

@ -25,7 +25,9 @@ export default class VolumeController extends Controller {
label: 'Volumes', label: 'Volumes',
args: [ args: [
'csi.volumes', 'csi.volumes',
qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }), qpBuilder({
volumeNamespace: volume.get('namespace.name') || 'default',
}),
], ],
}, },
{ {
@ -33,7 +35,9 @@ export default class VolumeController extends Controller {
args: [ args: [
'csi.volumes.volume', 'csi.volumes.volume',
volume.plainId, volume.plainId,
qpBuilder({ volumeNamespace: volume.get('namespace.name') || 'default' }), qpBuilder({
volumeNamespace: volume.get('namespace.name') || 'default',
}),
], ],
}, },
]; ];

View file

@ -35,9 +35,7 @@ export default class Allocation extends Model {
@attr('string') nodeName; @attr('string') nodeName;
@computed @computed
get shortNodeId() { get shortNodeId() {
return this.belongsTo('node') return this.belongsTo('node').id().split('-')[0];
.id()
.split('-')[0];
} }
@attr('number') modifyIndex; @attr('number') modifyIndex;

View file

@ -17,7 +17,7 @@ export default class AllocationRoute extends Route.extend(WithWatchers) {
// Preload the job for the allocation since it's required for the breadcrumb trail // Preload the job for the allocation since it's required for the breadcrumb trail
return super return super
.model(...arguments) .model(...arguments)
.then(allocation => .then((allocation) =>
allocation allocation
.get('job') .get('job')
.then(() => this.store.findAll('namespace')) // namespaces belong to a job and are an asynchronous relationship so we can peak them later on .then(() => this.store.findAll('namespace')) // namespaces belong to a job and are an asynchronous relationship so we can peak them later on

View file

@ -16,7 +16,7 @@ export default class VolumeRoute extends Route.extend(WithWatchers) {
if (!model) return; if (!model) return;
controller.set('watchers', { controller.set('watchers', {
model: this.watch.perform(model) model: this.watch.perform(model),
}); });
} }
@ -30,9 +30,9 @@ export default class VolumeRoute extends Route.extend(WithWatchers) {
const fullId = JSON.stringify([`csi/${name}`, namespace || 'default']); const fullId = JSON.stringify([`csi/${name}`, namespace || 'default']);
return RSVP.hash({ return RSVP.hash({
volume: this.store.findRecord('volume', fullId, { reload: true }), volume: this.store.findRecord('volume', fullId, { reload: true }),
namespaces: this.store.findAll('namespace') namespaces: this.store.findAll('namespace'),
}) })
.then(hash => hash.volume) .then((hash) => hash.volume)
.catch(notifyError(this)); .catch(notifyError(this));
} }

View file

@ -5,7 +5,7 @@ import {
watchRecord, watchRecord,
watchRelationship, watchRelationship,
watchAll, watchAll,
watchQuery watchQuery,
} from 'nomad-ui/utils/properties/watch'; } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers'; import WithWatchers from 'nomad-ui/mixins/with-watchers';
@ -43,12 +43,12 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
list: list:
model.job.get('hasChildren') && model.job.get('hasChildren') &&
this.watchAllJobs.perform({ this.watchAllJobs.perform({
namespace: model.job.namespace.get('name') namespace: model.job.namespace.get('name'),
}), }),
nodes: nodes:
this.can.can('read client') && this.can.can('read client') &&
model.job.get('hasClientStatus') && model.job.get('hasClientStatus') &&
this.watchNodes.perform() this.watchNodes.perform(),
}); });
} }
@ -61,7 +61,7 @@ export default class IndexRoute extends Route.extend(WithWatchers) {
) { ) {
controller.setProperties({ controller.setProperties({
sortProperty: 'submitTime', sortProperty: 'submitTime',
sortDescending: true sortDescending: true,
}); });
} }
return super.setupController(...arguments); return super.setupController(...arguments);

View file

@ -4,7 +4,7 @@ import EmberError from '@ember/error';
import { resolve, all } from 'rsvp'; import { resolve, all } from 'rsvp';
import { import {
watchRecord, watchRecord,
watchRelationship watchRelationship,
} from 'nomad-ui/utils/properties/watch'; } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers'; import WithWatchers from 'nomad-ui/mixins/with-watchers';
import notifyError from 'nomad-ui/utils/notify-error'; import notifyError from 'nomad-ui/utils/notify-error';
@ -34,7 +34,7 @@ export default class TaskGroupRoute extends Route.extend(WithWatchers) {
// Refresh job allocations before-hand (so page sort works on load) // Refresh job allocations before-hand (so page sort works on load)
return all([ return all([
job.hasMany('allocations').reload(), job.hasMany('allocations').reload(),
job.get('scaleState') job.get('scaleState'),
]).then(() => taskGroup); ]).then(() => taskGroup);
}) })
.catch(notifyError(this)); .catch(notifyError(this));
@ -50,7 +50,7 @@ export default class TaskGroupRoute extends Route.extend(WithWatchers) {
allocations: this.watchAllocations.perform(job), allocations: this.watchAllocations.perform(job),
latestDeployment: latestDeployment:
job.get('supportsDeployments') && job.get('supportsDeployments') &&
this.watchLatestDeployment.perform(job) this.watchLatestDeployment.perform(job),
}); });
} }
} }

View file

@ -13,7 +13,7 @@ export default class BucketService extends Service {
} }
@action deregisterBreadcrumb(crumb) { @action deregisterBreadcrumb(crumb) {
const newCrumbs = this.crumbs.filter(c => c !== crumb); const newCrumbs = this.crumbs.filter((c) => c !== crumb);
this.crumbs = newCrumbs; this.crumbs = newCrumbs;
} }

View file

@ -19,12 +19,12 @@ export default function jobClientStatus(nodesKey, jobKey) {
return computed( return computed(
`${nodesKey}.[]`, `${nodesKey}.[]`,
`${jobKey}.{datacenters,status,allocations.@each.clientStatus,taskGroups}`, `${jobKey}.{datacenters,status,allocations.@each.clientStatus,taskGroups}`,
function() { function () {
const job = this.get(jobKey); const job = this.get(jobKey);
const nodes = this.get(nodesKey); const nodes = this.get(nodesKey);
// Filter nodes by the datacenters defined in the job. // Filter nodes by the datacenters defined in the job.
const filteredNodes = nodes.filter(n => { const filteredNodes = nodes.filter((n) => {
return job.datacenters.indexOf(n.datacenter) >= 0; return job.datacenters.indexOf(n.datacenter) >= 0;
}); });
@ -34,7 +34,7 @@ export default function jobClientStatus(nodesKey, jobKey) {
// Group the job allocations by the ID of the client that is running them. // Group the job allocations by the ID of the client that is running them.
const allocsByNodeID = {}; const allocsByNodeID = {};
job.allocations.forEach(a => { job.allocations.forEach((a) => {
const nodeId = a.belongsTo('node').id(); const nodeId = a.belongsTo('node').id();
if (!allocsByNodeID[nodeId]) { if (!allocsByNodeID[nodeId]) {
allocsByNodeID[nodeId] = []; allocsByNodeID[nodeId] = [];
@ -47,7 +47,7 @@ export default function jobClientStatus(nodesKey, jobKey) {
byStatus: {}, byStatus: {},
totalNodes: filteredNodes.length, totalNodes: filteredNodes.length,
}; };
filteredNodes.forEach(n => { filteredNodes.forEach((n) => {
const status = jobStatus(allocsByNodeID[n.id], job.taskGroups.length); const status = jobStatus(allocsByNodeID[n.id], job.taskGroups.length);
result.byNode[n.id] = status; result.byNode[n.id] = status;
@ -63,9 +63,9 @@ export default function jobClientStatus(nodesKey, jobKey) {
} }
function allQueued(nodes) { function allQueued(nodes) {
const nodeIDs = nodes.map(n => n.id); const nodeIDs = nodes.map((n) => n.id);
return { return {
byNode: Object.fromEntries(nodeIDs.map(id => [id, 'queued'])), byNode: Object.fromEntries(nodeIDs.map((id) => [id, 'queued'])),
byStatus: canonicalizeStatus({ queued: nodeIDs }), byStatus: canonicalizeStatus({ queued: nodeIDs }),
totalNodes: nodes.length, totalNodes: nodes.length,
}; };
@ -105,7 +105,7 @@ function jobStatus(allocs, expected) {
// Count how many allocations are in each `clientStatus` value. // Count how many allocations are in each `clientStatus` value.
const summary = allocs const summary = allocs
.filter(a => !a.isOld) .filter((a) => !a.isOld)
.reduce((acc, a) => { .reduce((acc, a) => {
const status = a.clientStatus; const status = a.clientStatus;
if (!acc[status]) { if (!acc[status]) {

View file

@ -16,21 +16,21 @@ let job;
let node; let node;
let allocation; let allocation;
module('Acceptance | allocation detail', function(hooks) { module('Acceptance | allocation detail', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
node = server.create('node'); node = server.create('node');
job = server.create('job', { job = server.create('job', {
groupsCount: 1, groupsCount: 1,
withGroupServices: true, withGroupServices: true,
createAllocations: false createAllocations: false,
}); });
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'running' clientStatus: 'running',
}); });
// Make sure the node has an unhealthy driver // Make sure the node has an unhealthy driver
@ -38,14 +38,14 @@ module('Acceptance | allocation detail', function(hooks) {
driver: assign(node.drivers, { driver: assign(node.drivers, {
docker: { docker: {
detected: true, detected: true,
healthy: false healthy: false,
} },
}) }),
}); });
// Make sure a task for the allocation depends on the unhealthy driver // Make sure a task for the allocation depends on the unhealthy driver
server.schema.tasks.first().update({ server.schema.tasks.first().update({
driver: 'docker' driver: 'docker',
}); });
window.localStorage.clear(); window.localStorage.clear();
@ -53,11 +53,11 @@ module('Acceptance | allocation detail', function(hooks) {
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
}); });
test('it passes an accessibility audit', async function(assert) { test('it passes an accessibility audit', async function (assert) {
await a11yAudit(assert); await a11yAudit(assert);
}); });
test('/allocation/:id should name the allocation and link to the corresponding job and node', async function(assert) { test('/allocation/:id should name the allocation and link to the corresponding job and node', async function (assert) {
assert.ok( assert.ok(
Allocation.title.includes(allocation.name), Allocation.title.includes(allocation.name),
'Allocation name is in the heading' 'Allocation name is in the heading'
@ -93,7 +93,7 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('/allocation/:id should include resource utilization graphs', async function(assert) { test('/allocation/:id should include resource utilization graphs', async function (assert) {
assert.equal( assert.equal(
Allocation.resourceCharts.length, Allocation.resourceCharts.length,
2, 2,
@ -111,17 +111,17 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('/allocation/:id should present task lifecycles', async function(assert) { test('/allocation/:id should present task lifecycles', async function (assert) {
const job = server.create('job', { const job = server.create('job', {
groupsCount: 1, groupsCount: 1,
groupTaskCount: 6, groupTaskCount: 6,
withGroupServices: true, withGroupServices: true,
createAllocations: false createAllocations: false,
}); });
const allocation = server.create('allocation', 'withTaskWithPorts', { const allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'running', clientStatus: 'running',
jobId: job.id jobId: job.id,
}); });
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
@ -136,7 +136,7 @@ module('Acceptance | allocation detail', function(hooks) {
const prestartEphemeralTask = server.db.taskStates const prestartEphemeralTask = server.db.taskStates
.where({ allocationId: allocation.id }) .where({ allocationId: allocation.id })
.sortBy('name') .sortBy('name')
.find(taskState => { .find((taskState) => {
const task = server.db.tasks.findBy({ name: taskState.name }); const task = server.db.tasks.findBy({ name: taskState.name });
return ( return (
task.Lifecycle && task.Lifecycle &&
@ -151,7 +151,7 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('/allocation/:id should list all tasks for the allocation', async function(assert) { test('/allocation/:id should list all tasks for the allocation', async function (assert) {
assert.equal( assert.equal(
Allocation.tasks.length, Allocation.tasks.length,
server.db.taskStates.where({ allocationId: allocation.id }).length, server.db.taskStates.where({ allocationId: allocation.id }).length,
@ -160,7 +160,7 @@ module('Acceptance | allocation detail', function(hooks) {
assert.notOk(Allocation.isEmpty, 'Task table empty state is not shown'); assert.notOk(Allocation.isEmpty, 'Task table empty state is not shown');
}); });
test('each task row should list high-level information for the task', async function(assert) { test('each task row should list high-level information for the task', async function (assert) {
const task = server.db.taskStates const task = server.db.taskStates
.where({ allocationId: allocation.id }) .where({ allocationId: allocation.id })
.sortBy('name')[0]; .sortBy('name')[0];
@ -169,16 +169,16 @@ module('Acceptance | allocation detail', function(hooks) {
const taskGroup = server.schema.taskGroups.where({ const taskGroup = server.schema.taskGroups.where({
jobId: allocation.jobId, jobId: allocation.jobId,
name: allocation.taskGroup name: allocation.taskGroup,
}).models[0]; }).models[0];
const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); const jobTask = taskGroup.tasks.models.find((m) => m.name === task.name);
const volumes = jobTask.volumeMounts.map(volume => ({ const volumes = jobTask.volumeMounts.map((volume) => ({
name: volume.Volume, name: volume.Volume,
source: taskGroup.volumes[volume.Volume].Source source: taskGroup.volumes[volume.Volume].Source,
})); }));
Allocation.tasks[0].as(taskRow => { Allocation.tasks[0].as((taskRow) => {
assert.equal(taskRow.name, task.name, 'Name'); assert.equal(taskRow.name, task.name, 'Name');
assert.equal(taskRow.state, task.state, 'State'); assert.equal(taskRow.state, task.state, 'State');
assert.equal(taskRow.message, event.displayMessage, 'Event Message'); assert.equal(taskRow.message, event.displayMessage, 'Event Message');
@ -189,7 +189,7 @@ module('Acceptance | allocation detail', function(hooks) {
); );
const volumesText = taskRow.volumes; const volumesText = taskRow.volumes;
volumes.forEach(volume => { volumes.forEach((volume) => {
assert.ok( assert.ok(
volumesText.includes(volume.name), volumesText.includes(volume.name),
`Found label ${volume.name}` `Found label ${volume.name}`
@ -202,7 +202,7 @@ module('Acceptance | allocation detail', function(hooks) {
}); });
}); });
test('each task row should link to the task detail page', async function(assert) { test('each task row should link to the task detail page', async function (assert) {
const task = server.db.taskStates const task = server.db.taskStates
.where({ allocationId: allocation.id }) .where({ allocationId: allocation.id })
.sortBy('name')[0]; .sortBy('name')[0];
@ -226,16 +226,16 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('tasks with an unhealthy driver have a warning icon', async function(assert) { test('tasks with an unhealthy driver have a warning icon', async function (assert) {
// Driver health status require node:read permission. // Driver health status require node:read permission.
const policy = server.create('policy', { const policy = server.create('policy', {
id: 'node-read', id: 'node-read',
name: 'node-read', name: 'node-read',
rulesJSON: { rulesJSON: {
Node: { Node: {
Policy: 'read' Policy: 'read',
} },
} },
}); });
const clientToken = server.create('token', { type: 'client' }); const clientToken = server.create('token', { type: 'client' });
clientToken.policyIds = [policy.id]; clientToken.policyIds = [policy.id];
@ -253,17 +253,17 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('proxy task has a proxy tag', async function(assert) { test('proxy task has a proxy tag', async function (assert) {
// Must create a new job as existing one has loaded and it contains the tasks // Must create a new job as existing one has loaded and it contains the tasks
job = server.create('job', { job = server.create('job', {
groupsCount: 1, groupsCount: 1,
withGroupServices: true, withGroupServices: true,
createAllocations: false createAllocations: false,
}); });
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'running', clientStatus: 'running',
jobId: job.id jobId: job.id,
}); });
const taskState = allocation.taskStates.models.sortBy('name')[0]; const taskState = allocation.taskStates.models.sortBy('name')[0];
@ -276,24 +276,24 @@ module('Acceptance | allocation detail', function(hooks) {
assert.ok(Allocation.tasks[0].hasProxyTag); assert.ok(Allocation.tasks[0].hasProxyTag);
}); });
test('when there are no tasks, an empty state is shown', async function(assert) { test('when there are no tasks, an empty state is shown', async function (assert) {
// Make sure the allocation is pending in order to ensure there are no tasks // Make sure the allocation is pending in order to ensure there are no tasks
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'pending' clientStatus: 'pending',
}); });
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
assert.ok(Allocation.isEmpty, 'Task table empty state is shown'); assert.ok(Allocation.isEmpty, 'Task table empty state is shown');
}); });
test('when the allocation has not been rescheduled, the reschedule events section is not rendered', async function(assert) { test('when the allocation has not been rescheduled, the reschedule events section is not rendered', async function (assert) {
assert.notOk( assert.notOk(
Allocation.hasRescheduleEvents, Allocation.hasRescheduleEvents,
'Reschedule Events section exists' 'Reschedule Events section exists'
); );
}); });
test('ports are listed', async function(assert) { test('ports are listed', async function (assert) {
const allServerPorts = allocation.taskResources.models[0].resources.Ports; const allServerPorts = allocation.taskResources.models[0].resources.Ports;
allServerPorts.sortBy('Label').forEach((serverPort, index) => { allServerPorts.sortBy('Label').forEach((serverPort, index) => {
@ -308,9 +308,9 @@ module('Acceptance | allocation detail', function(hooks) {
}); });
}); });
test('services are listed', async function(assert) { test('services are listed', async function (assert) {
const taskGroup = server.schema.taskGroups.findBy({ const taskGroup = server.schema.taskGroups.findBy({
name: allocation.taskGroup name: allocation.taskGroup,
}); });
assert.equal(Allocation.services.length, taskGroup.services.length); assert.equal(Allocation.services.length, taskGroup.services.length);
@ -331,7 +331,7 @@ module('Acceptance | allocation detail', function(hooks) {
const upstreams = serverService.Connect.SidecarService.Proxy.Upstreams; const upstreams = serverService.Connect.SidecarService.Proxy.Upstreams;
const serverUpstreamsString = upstreams const serverUpstreamsString = upstreams
.map( .map(
upstream => `${upstream.DestinationName}:${upstream.LocalBindPort}` (upstream) => `${upstream.DestinationName}:${upstream.LocalBindPort}`
) )
.join(' '); .join(' ');
@ -339,12 +339,12 @@ module('Acceptance | allocation detail', function(hooks) {
}); });
}); });
test('when the allocation is not found, an error message is shown, but the URL persists', async function(assert) { test('when the allocation is not found, an error message is shown, but the URL persists', async function (assert) {
await Allocation.visit({ id: 'not-a-real-allocation' }); await Allocation.visit({ id: 'not-a-real-allocation' });
assert.equal( assert.equal(
server.pretender.handledRequests server.pretender.handledRequests
.filter(request => !request.url.includes('policy')) .filter((request) => !request.url.includes('policy'))
.findBy('status', 404).url, .findBy('status', 404).url,
'/v1/allocation/not-a-real-allocation', '/v1/allocation/not-a-real-allocation',
'A request to the nonexistent allocation is made' 'A request to the nonexistent allocation is made'
@ -362,20 +362,20 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('allocation can be stopped', async function(assert) { test('allocation can be stopped', async function (assert) {
await Allocation.stop.idle(); await Allocation.stop.idle();
await Allocation.stop.confirm(); await Allocation.stop.confirm();
assert.equal( assert.equal(
server.pretender.handledRequests server.pretender.handledRequests
.reject(request => request.url.includes('fuzzy')) .reject((request) => request.url.includes('fuzzy'))
.findBy('method', 'POST').url, .findBy('method', 'POST').url,
`/v1/allocation/${allocation.id}/stop`, `/v1/allocation/${allocation.id}/stop`,
'Stop request is made for the allocation' 'Stop request is made for the allocation'
); );
}); });
test('allocation can be restarted', async function(assert) { test('allocation can be restarted', async function (assert) {
await Allocation.restart.idle(); await Allocation.restart.idle();
await Allocation.restart.confirm(); await Allocation.restart.confirm();
@ -386,7 +386,7 @@ module('Acceptance | allocation detail', function(hooks) {
); );
}); });
test('while an allocation is being restarted, the stop button is disabled', async function(assert) { test('while an allocation is being restarted, the stop button is disabled', async function (assert) {
server.pretender.post('/v1/allocation/:id/stop', () => [204, {}, ''], true); server.pretender.post('/v1/allocation/:id/stop', () => [204, {}, ''], true);
await Allocation.stop.idle(); await Allocation.stop.idle();
@ -400,7 +400,7 @@ module('Acceptance | allocation detail', function(hooks) {
await Allocation.stop.confirm(); await Allocation.stop.confirm();
}); });
test('if stopping or restarting fails, an error message is shown', async function(assert) { test('if stopping or restarting fails, an error message is shown', async function (assert) {
server.pretender.post('/v1/allocation/:id/stop', () => [403, {}, '']); server.pretender.post('/v1/allocation/:id/stop', () => [403, {}, '']);
await Allocation.stop.idle(); await Allocation.stop.idle();
@ -425,11 +425,11 @@ module('Acceptance | allocation detail', function(hooks) {
}); });
}); });
module('Acceptance | allocation detail (rescheduled)', function(hooks) { module('Acceptance | allocation detail (rescheduled)', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
node = server.create('node'); node = server.create('node');
@ -439,7 +439,7 @@ module('Acceptance | allocation detail (rescheduled)', function(hooks) {
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
}); });
test('when the allocation has been rescheduled, the reschedule events section is rendered', async function(assert) { test('when the allocation has been rescheduled, the reschedule events section is rendered', async function (assert) {
assert.ok( assert.ok(
Allocation.hasRescheduleEvents, Allocation.hasRescheduleEvents,
'Reschedule Events section exists' 'Reschedule Events section exists'
@ -447,11 +447,11 @@ module('Acceptance | allocation detail (rescheduled)', function(hooks) {
}); });
}); });
module('Acceptance | allocation detail (not running)', function(hooks) { module('Acceptance | allocation detail (not running)', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
node = server.create('node'); node = server.create('node');
@ -461,7 +461,7 @@ module('Acceptance | allocation detail (not running)', function(hooks) {
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
}); });
test('when the allocation is not running, the utilization graphs are replaced by an empty message', async function(assert) { test('when the allocation is not running, the utilization graphs are replaced by an empty message', async function (assert) {
assert.equal(Allocation.resourceCharts.length, 0, 'No resource charts'); assert.equal(Allocation.resourceCharts.length, 0, 'No resource charts');
assert.equal( assert.equal(
Allocation.resourceEmptyMessage, Allocation.resourceEmptyMessage,
@ -470,25 +470,25 @@ module('Acceptance | allocation detail (not running)', function(hooks) {
); );
}); });
test('the exec and stop/restart buttons are absent', async function(assert) { test('the exec and stop/restart buttons are absent', async function (assert) {
assert.notOk(Allocation.execButton.isPresent); assert.notOk(Allocation.execButton.isPresent);
assert.notOk(Allocation.stop.isPresent); assert.notOk(Allocation.stop.isPresent);
assert.notOk(Allocation.restart.isPresent); assert.notOk(Allocation.restart.isPresent);
}); });
}); });
module('Acceptance | allocation detail (preemptions)', function(hooks) { module('Acceptance | allocation detail (preemptions)', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
node = server.create('node'); node = server.create('node');
job = server.create('job', { createAllocations: false }); job = server.create('job', { createAllocations: false });
window.localStorage.clear(); window.localStorage.clear();
}); });
test('shows a dedicated section to the allocation that preempted this allocation', async function(assert) { test('shows a dedicated section to the allocation that preempted this allocation', async function (assert) {
allocation = server.create('allocation', 'preempted'); allocation = server.create('allocation', 'preempted');
const preempter = server.schema.find( const preempter = server.schema.find(
'allocation', 'allocation',
@ -539,7 +539,7 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) {
); );
}); });
test('shows a dedicated section to the allocations this allocation preempted', async function(assert) { test('shows a dedicated section to the allocations this allocation preempted', async function (assert) {
allocation = server.create('allocation', 'preempter'); allocation = server.create('allocation', 'preempter');
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
assert.ok( assert.ok(
@ -548,12 +548,12 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) {
); );
}); });
test('each preempted allocation in the table lists basic allocation information', async function(assert) { test('each preempted allocation in the table lists basic allocation information', async function (assert) {
allocation = server.create('allocation', 'preempter'); allocation = server.create('allocation', 'preempter');
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
const preemption = allocation.preemptedAllocations const preemption = allocation.preemptedAllocations
.map(id => server.schema.find('allocation', id)) .map((id) => server.schema.find('allocation', id))
.sortBy('modifyIndex') .sortBy('modifyIndex')
.reverse()[0]; .reverse()[0];
const preemptionRow = Allocation.preemptions.objectAt(0); const preemptionRow = Allocation.preemptions.objectAt(0);
@ -596,16 +596,16 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) {
); );
}); });
test('clicking the client ID in the preempted allocation row naviates to the client page', async function(assert) { test('clicking the client ID in the preempted allocation row naviates to the client page', async function (assert) {
// Navigating to the client page requires node:read permission. // Navigating to the client page requires node:read permission.
const policy = server.create('policy', { const policy = server.create('policy', {
id: 'node-read', id: 'node-read',
name: 'node-read', name: 'node-read',
rulesJSON: { rulesJSON: {
Node: { Node: {
Policy: 'read' Policy: 'read',
} },
} },
}); });
const clientToken = server.create('token', { type: 'client' }); const clientToken = server.create('token', { type: 'client' });
clientToken.policyIds = [policy.id]; clientToken.policyIds = [policy.id];
@ -616,7 +616,7 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) {
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
const preemption = allocation.preemptedAllocations const preemption = allocation.preemptedAllocations
.map(id => server.schema.find('allocation', id)) .map((id) => server.schema.find('allocation', id))
.sortBy('modifyIndex') .sortBy('modifyIndex')
.reverse()[0]; .reverse()[0];
const preemptionRow = Allocation.preemptions.objectAt(0); const preemptionRow = Allocation.preemptions.objectAt(0);
@ -629,7 +629,7 @@ module('Acceptance | allocation detail (preemptions)', function(hooks) {
); );
}); });
test('when an allocation both preempted allocations and was preempted itself, both preemptions sections are shown', async function(assert) { test('when an allocation both preempted allocations and was preempted itself, both preemptions sections are shown', async function (assert) {
allocation = server.create('allocation', 'preempter', 'preempted'); allocation = server.create('allocation', 'preempter', 'preempted');
await Allocation.visit({ id: allocation.id }); await Allocation.visit({ id: allocation.id });
assert.ok( assert.ok(

View file

@ -11,11 +11,11 @@ let node;
let managementToken; let managementToken;
let clientToken; let clientToken;
module('Acceptance | client monitor', function(hooks) { module('Acceptance | client monitor', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function () {
node = server.create('node'); node = server.create('node');
managementToken = server.create('token'); managementToken = server.create('token');
@ -27,12 +27,14 @@ module('Acceptance | client monitor', function(hooks) {
run.later(run, run.cancelTimers, 500); run.later(run, run.cancelTimers, 500);
}); });
test('it passes an accessibility audit', async function(assert) { test('it passes an accessibility audit', async function (assert) {
assert.expect(1);
await ClientMonitor.visit({ id: node.id }); await ClientMonitor.visit({ id: node.id });
await a11yAudit(assert); await a11yAudit(assert);
}); });
test('/clients/:id/monitor should have a breadcrumb trail linking back to clients', async function(assert) { test('/clients/:id/monitor should have a breadcrumb trail linking back to clients', async function (assert) {
await ClientMonitor.visit({ id: node.id }); await ClientMonitor.visit({ id: node.id });
assert.equal(Layout.breadcrumbFor('clients.index').text, 'Clients'); assert.equal(Layout.breadcrumbFor('clients.index').text, 'Clients');
@ -45,10 +47,10 @@ module('Acceptance | client monitor', function(hooks) {
assert.equal(currentURL(), '/clients'); assert.equal(currentURL(), '/clients');
}); });
test('the monitor page immediately streams agent monitor output at the info level', async function(assert) { test('the monitor page immediately streams agent monitor output at the info level', async function (assert) {
await ClientMonitor.visit({ id: node.id }); await ClientMonitor.visit({ id: node.id });
const logRequest = server.pretender.handledRequests.find(req => const logRequest = server.pretender.handledRequests.find((req) =>
req.url.startsWith('/v1/agent/monitor') req.url.startsWith('/v1/agent/monitor')
); );
assert.ok(ClientMonitor.logsArePresent); assert.ok(ClientMonitor.logsArePresent);
@ -56,13 +58,13 @@ module('Acceptance | client monitor', function(hooks) {
assert.ok(logRequest.url.includes('log_level=info')); assert.ok(logRequest.url.includes('log_level=info'));
}); });
test('switching the log level persists the new log level as a query param', async function(assert) { test('switching the log level persists the new log level as a query param', async function (assert) {
await ClientMonitor.visit({ id: node.id }); await ClientMonitor.visit({ id: node.id });
await ClientMonitor.selectLogLevel('Debug'); await ClientMonitor.selectLogLevel('Debug');
assert.equal(currentURL(), `/clients/${node.id}/monitor?level=debug`); assert.equal(currentURL(), `/clients/${node.id}/monitor?level=debug`);
}); });
test('when the current access token does not include the agent:read rule, a descriptive error message is shown', async function(assert) { test('when the current access token does not include the agent:read rule, a descriptive error message is shown', async function (assert) {
window.localStorage.nomadTokenSecret = clientToken.secretId; window.localStorage.nomadTokenSecret = clientToken.secretId;
await ClientMonitor.visit({ id: node.id }); await ClientMonitor.visit({ id: node.id });

View file

@ -40,7 +40,10 @@ module('Acceptance | server monitor', function (hooks) {
'Servers', 'Servers',
'The page should read the breadcrumb Servers' 'The page should read the breadcrumb Servers'
); );
assert.equal(Layout.breadcrumbFor('servers.server').text, `Server ${agent.name}`); assert.equal(
Layout.breadcrumbFor('servers.server').text,
`Server ${agent.name}`
);
await Layout.breadcrumbFor('servers.index').visit(); await Layout.breadcrumbFor('servers.index').visit();
assert.equal(currentURL(), '/servers'); assert.equal(currentURL(), '/servers');

View file

@ -1,3 +1,4 @@
/* eslint-disable qunit/require-expect */
import { currentURL, waitFor } from '@ember/test-helpers'; import { currentURL, waitFor } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
@ -10,27 +11,29 @@ import moment from 'moment';
let allocation; let allocation;
let task; let task;
module('Acceptance | task detail', function(hooks) { module('Acceptance | task detail', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
server.create('node'); server.create('node');
server.create('job', { createAllocations: false }); server.create('job', { createAllocations: false });
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'running' clientStatus: 'running',
}); });
task = server.db.taskStates.where({ allocationId: allocation.id })[0]; task = server.db.taskStates.where({ allocationId: allocation.id })[0];
await Task.visit({ id: allocation.id, name: task.name }); await Task.visit({ id: allocation.id, name: task.name });
}); });
test('it passes an accessibility audit', async function(assert) { test('it passes an accessibility audit', async function (assert) {
assert.expect(1);
await a11yAudit(assert); await a11yAudit(assert);
}); });
test('/allocation/:id/:task_name should name the task and list high-level task information', async function(assert) { test('/allocation/:id/:task_name should name the task and list high-level task information', async function (assert) {
assert.ok(Task.title.text.includes(task.name), 'Task name'); assert.ok(Task.title.text.includes(task.name), 'Task name');
assert.ok(Task.state.includes(task.state), 'Task state'); assert.ok(Task.state.includes(task.state), 'Task state');
@ -61,7 +64,7 @@ module('Acceptance | task detail', function(hooks) {
assert.equal(document.title, `Task ${task.name} - Nomad`); assert.equal(document.title, `Task ${task.name} - Nomad`);
}); });
test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { test('breadcrumbs match jobs / job / task group / allocation / task', async function (assert) {
const { jobId, taskGroup } = allocation; const { jobId, taskGroup } = allocation;
const job = server.db.jobs.find(jobId); const job = server.db.jobs.find(jobId);
@ -122,7 +125,7 @@ module('Acceptance | task detail', function(hooks) {
); );
}); });
test('/allocation/:id/:task_name should include resource utilization graphs', async function(assert) { test('/allocation/:id/:task_name should include resource utilization graphs', async function (assert) {
assert.equal( assert.equal(
Task.resourceCharts.length, Task.resourceCharts.length,
2, 2,
@ -140,7 +143,7 @@ module('Acceptance | task detail', function(hooks) {
); );
}); });
test('the events table lists all recent events', async function(assert) { test('the events table lists all recent events', async function (assert) {
const events = server.db.taskEvents.where({ taskStateId: task.id }); const events = server.db.taskEvents.where({ taskStateId: task.id });
assert.equal( assert.equal(
@ -150,26 +153,26 @@ module('Acceptance | task detail', function(hooks) {
); );
}); });
test('when a task has volumes, the volumes table is shown', async function(assert) { test('when a task has volumes, the volumes table is shown', async function (assert) {
const taskGroup = server.schema.taskGroups.where({ const taskGroup = server.schema.taskGroups.where({
jobId: allocation.jobId, jobId: allocation.jobId,
name: allocation.taskGroup name: allocation.taskGroup,
}).models[0]; }).models[0];
const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); const jobTask = taskGroup.tasks.models.find((m) => m.name === task.name);
assert.ok(Task.hasVolumes); assert.ok(Task.hasVolumes);
assert.equal(Task.volumes.length, jobTask.volumeMounts.length); assert.equal(Task.volumes.length, jobTask.volumeMounts.length);
}); });
test('when a task does not have volumes, the volumes table is not shown', async function(assert) { test('when a task does not have volumes, the volumes table is not shown', async function (assert) {
const job = server.create('job', { const job = server.create('job', {
createAllocations: false, createAllocations: false,
noHostVolumes: true noHostVolumes: true,
}); });
allocation = server.create('allocation', { allocation = server.create('allocation', {
jobId: job.id, jobId: job.id,
clientStatus: 'running' clientStatus: 'running',
}); });
task = server.db.taskStates.where({ allocationId: allocation.id })[0]; task = server.db.taskStates.where({ allocationId: allocation.id })[0];
@ -177,16 +180,16 @@ module('Acceptance | task detail', function(hooks) {
assert.notOk(Task.hasVolumes); assert.notOk(Task.hasVolumes);
}); });
test('each volume in the volumes table shows information about the volume', async function(assert) { test('each volume in the volumes table shows information about the volume', async function (assert) {
const taskGroup = server.schema.taskGroups.where({ const taskGroup = server.schema.taskGroups.where({
jobId: allocation.jobId, jobId: allocation.jobId,
name: allocation.taskGroup name: allocation.taskGroup,
}).models[0]; }).models[0];
const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); const jobTask = taskGroup.tasks.models.find((m) => m.name === task.name);
const volume = jobTask.volumeMounts[0]; const volume = jobTask.volumeMounts[0];
Task.volumes[0].as(volumeRow => { Task.volumes[0].as((volumeRow) => {
assert.equal(volumeRow.name, volume.Volume); assert.equal(volumeRow.name, volume.Volume);
assert.equal(volumeRow.destination, volume.Destination); assert.equal(volumeRow.destination, volume.Destination);
assert.equal( assert.equal(
@ -200,7 +203,7 @@ module('Acceptance | task detail', function(hooks) {
}); });
}); });
test('each recent event should list the time, type, and description of the event', async function(assert) { test('each recent event should list the time, type, and description of the event', async function (assert) {
const event = server.db.taskEvents.where({ taskStateId: task.id })[0]; const event = server.db.taskEvents.where({ taskStateId: task.id })[0];
const recentEvent = Task.events.objectAt(Task.events.length - 1); const recentEvent = Task.events.objectAt(Task.events.length - 1);
@ -213,12 +216,12 @@ module('Acceptance | task detail', function(hooks) {
assert.equal(recentEvent.message, event.displayMessage, 'Event message'); assert.equal(recentEvent.message, event.displayMessage, 'Event message');
}); });
test('when the allocation is not found, the application errors', async function(assert) { test('when the allocation is not found, the application errors', async function (assert) {
await Task.visit({ id: 'not-a-real-allocation', name: task.name }); await Task.visit({ id: 'not-a-real-allocation', name: task.name });
assert.equal( assert.equal(
server.pretender.handledRequests server.pretender.handledRequests
.filter(request => !request.url.includes('policy')) .filter((request) => !request.url.includes('policy'))
.findBy('status', 404).url, .findBy('status', 404).url,
'/v1/allocation/not-a-real-allocation', '/v1/allocation/not-a-real-allocation',
'A request to the nonexistent allocation is made' 'A request to the nonexistent allocation is made'
@ -232,7 +235,7 @@ module('Acceptance | task detail', function(hooks) {
assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); assert.equal(Task.error.title, 'Not Found', 'Error message is for 404');
}); });
test('when the allocation is found but the task is not, the application errors', async function(assert) { test('when the allocation is found but the task is not, the application errors', async function (assert) {
await Task.visit({ id: allocation.id, name: 'not-a-real-task-name' }); await Task.visit({ id: allocation.id, name: 'not-a-real-task-name' });
assert.ok( assert.ok(
@ -251,7 +254,7 @@ module('Acceptance | task detail', function(hooks) {
assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); assert.equal(Task.error.title, 'Not Found', 'Error message is for 404');
}); });
test('task can be restarted', async function(assert) { test('task can be restarted', async function (assert) {
await Task.restart.idle(); await Task.restart.idle();
await Task.restart.confirm(); await Task.restart.confirm();
@ -269,11 +272,11 @@ module('Acceptance | task detail', function(hooks) {
); );
}); });
test('when task restart fails (403), an ACL permissions error message is shown', async function(assert) { test('when task restart fails (403), an ACL permissions error message is shown', async function (assert) {
server.pretender.put('/v1/client/allocation/:id/restart', () => [ server.pretender.put('/v1/client/allocation/:id/restart', () => [
403, 403,
{}, {},
'' '',
]); ]);
await Task.restart.idle(); await Task.restart.idle();
@ -294,12 +297,12 @@ module('Acceptance | task detail', function(hooks) {
assert.notOk(Task.inlineError.isShown, 'Inline error is no longer shown'); assert.notOk(Task.inlineError.isShown, 'Inline error is no longer shown');
}); });
test('when task restart fails (500), the error message from the API is piped through to the alert', async function(assert) { test('when task restart fails (500), the error message from the API is piped through to the alert', async function (assert) {
const message = 'A plaintext error message'; const message = 'A plaintext error message';
server.pretender.put('/v1/client/allocation/:id/restart', () => [ server.pretender.put('/v1/client/allocation/:id/restart', () => [
500, 500,
{}, {},
message message,
]); ]);
await Task.restart.idle(); await Task.restart.idle();
@ -314,21 +317,21 @@ module('Acceptance | task detail', function(hooks) {
assert.notOk(Task.inlineError.isShown); assert.notOk(Task.inlineError.isShown);
}); });
test('exec button is present', async function(assert) { test('exec button is present', async function (assert) {
assert.ok(Task.execButton.isPresent); assert.ok(Task.execButton.isPresent);
}); });
}); });
module('Acceptance | task detail (no addresses)', function(hooks) { module('Acceptance | task detail (no addresses)', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
server.create('node'); server.create('node');
server.create('job'); server.create('job');
allocation = server.create('allocation', 'withoutTaskWithPorts', { allocation = server.create('allocation', 'withoutTaskWithPorts', {
clientStatus: 'running' clientStatus: 'running',
}); });
task = server.db.taskStates.where({ allocationId: allocation.id })[0]; task = server.db.taskStates.where({ allocationId: allocation.id })[0];
@ -336,28 +339,28 @@ module('Acceptance | task detail (no addresses)', function(hooks) {
}); });
}); });
module('Acceptance | task detail (different namespace)', function(hooks) { module('Acceptance | task detail (different namespace)', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
server.create('node'); server.create('node');
server.create('namespace'); server.create('namespace');
server.create('namespace', { id: 'other-namespace' }); server.create('namespace', { id: 'other-namespace' });
server.create('job', { server.create('job', {
createAllocations: false, createAllocations: false,
namespaceId: 'other-namespace' namespaceId: 'other-namespace',
}); });
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'running' clientStatus: 'running',
}); });
task = server.db.taskStates.where({ allocationId: allocation.id })[0]; task = server.db.taskStates.where({ allocationId: allocation.id })[0];
await Task.visit({ id: allocation.id, name: task.name }); await Task.visit({ id: allocation.id, name: task.name });
}); });
test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { test('breadcrumbs match jobs / job / task group / allocation / task', async function (assert) {
const { jobId, taskGroup } = allocation; const { jobId, taskGroup } = allocation;
const job = server.db.jobs.find(jobId); const job = server.db.jobs.find(jobId);
@ -394,28 +397,28 @@ module('Acceptance | task detail (different namespace)', function(hooks) {
}); });
}); });
module('Acceptance | task detail (not running)', function(hooks) { module('Acceptance | task detail (not running)', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
server.create('node'); server.create('node');
server.create('namespace'); server.create('namespace');
server.create('namespace', { id: 'other-namespace' }); server.create('namespace', { id: 'other-namespace' });
server.create('job', { server.create('job', {
createAllocations: false, createAllocations: false,
namespaceId: 'other-namespace' namespaceId: 'other-namespace',
}); });
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'complete' clientStatus: 'complete',
}); });
task = server.db.taskStates.where({ allocationId: allocation.id })[0]; task = server.db.taskStates.where({ allocationId: allocation.id })[0];
await Task.visit({ id: allocation.id, name: task.name }); await Task.visit({ id: allocation.id, name: task.name });
}); });
test('when the allocation for a task is not running, the resource utilization graphs are replaced by an empty message', async function(assert) { test('when the allocation for a task is not running, the resource utilization graphs are replaced by an empty message', async function (assert) {
assert.equal(Task.resourceCharts.length, 0, 'No resource charts'); assert.equal(Task.resourceCharts.length, 0, 'No resource charts');
assert.equal( assert.equal(
Task.resourceEmptyMessage, Task.resourceEmptyMessage,
@ -424,21 +427,21 @@ module('Acceptance | task detail (not running)', function(hooks) {
); );
}); });
test('exec button is absent', async function(assert) { test('exec button is absent', async function (assert) {
assert.notOk(Task.execButton.isPresent); assert.notOk(Task.execButton.isPresent);
}); });
}); });
module('Acceptance | proxy task detail', function(hooks) { module('Acceptance | proxy task detail', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
server.create('node'); server.create('node');
server.create('job', { createAllocations: false }); server.create('job', { createAllocations: false });
allocation = server.create('allocation', 'withTaskWithPorts', { allocation = server.create('allocation', 'withTaskWithPorts', {
clientStatus: 'running' clientStatus: 'running',
}); });
const taskState = allocation.taskStates.models[0]; const taskState = allocation.taskStates.models[0];
@ -449,7 +452,7 @@ module('Acceptance | proxy task detail', function(hooks) {
await Task.visit({ id: allocation.id, name: taskState.name }); await Task.visit({ id: allocation.id, name: taskState.name });
}); });
test('a proxy tag is shown', async function(assert) { test('a proxy tag is shown', async function (assert) {
assert.ok(Task.title.proxyTag.isPresent); assert.ok(Task.title.proxyTag.isPresent);
}); });
}); });

View file

@ -9,7 +9,7 @@ import {
formatBytes, formatBytes,
formatHertz, formatHertz,
formatScheduledBytes, formatScheduledBytes,
formatScheduledHertz formatScheduledHertz,
} from 'nomad-ui/utils/units'; } from 'nomad-ui/utils/units';
import TaskGroup from 'nomad-ui/tests/pages/jobs/job/task-group'; import TaskGroup from 'nomad-ui/tests/pages/jobs/job/task-group';
import Layout from 'nomad-ui/tests/pages/layout'; import Layout from 'nomad-ui/tests/pages/layout';
@ -24,30 +24,30 @@ let managementToken;
const sum = (total, n) => total + n; const sum = (total, n) => total + n;
module('Acceptance | task group detail', function(hooks) { module('Acceptance | task group detail', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('agent'); server.create('agent');
server.create('node', 'forceIPv4'); server.create('node', 'forceIPv4');
job = server.create('job', { job = server.create('job', {
groupsCount: 2, groupsCount: 2,
createAllocations: false createAllocations: false,
}); });
const taskGroups = server.db.taskGroups.where({ jobId: job.id }); const taskGroups = server.db.taskGroups.where({ jobId: job.id });
taskGroup = taskGroups[0]; taskGroup = taskGroups[0];
tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id));
server.create('node', 'forceIPv4'); server.create('node', 'forceIPv4');
allocations = server.createList('allocation', 2, { allocations = server.createList('allocation', 2, {
jobId: job.id, jobId: job.id,
taskGroup: taskGroup.name, taskGroup: taskGroup.name,
clientStatus: 'running' clientStatus: 'running',
}); });
// Allocations associated to a different task group on the job to // Allocations associated to a different task group on the job to
@ -55,20 +55,20 @@ module('Acceptance | task group detail', function(hooks) {
server.createList('allocation', 3, { server.createList('allocation', 3, {
jobId: job.id, jobId: job.id,
taskGroup: taskGroups[1].name, taskGroup: taskGroups[1].name,
clientStatus: 'running' clientStatus: 'running',
}); });
// Set a static name to make the search test deterministic // Set a static name to make the search test deterministic
server.db.allocations.forEach(alloc => { server.db.allocations.forEach((alloc) => {
alloc.name = 'aaaaa'; alloc.name = 'aaaaa';
}); });
// Mark the first alloc as rescheduled // Mark the first alloc as rescheduled
allocations[0].update({ allocations[0].update({
nextAllocation: allocations[1].id nextAllocation: allocations[1].id,
}); });
allocations[1].update({ allocations[1].update({
previousAllocation: allocations[0].id previousAllocation: allocations[0].id,
}); });
managementToken = server.create('token'); managementToken = server.create('token');
@ -76,16 +76,16 @@ module('Acceptance | task group detail', function(hooks) {
window.localStorage.clear(); window.localStorage.clear();
}); });
test('it passes an accessibility audit', async function(assert) { test('it passes an accessibility audit', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
await a11yAudit(assert); await a11yAudit(assert);
}); });
test('/jobs/:id/:task-group should list high-level metrics for the allocation', async function(assert) { test('/jobs/:id/:task-group should list high-level metrics for the allocation', async function (assert) {
const totalCPU = tasks.mapBy('resources.CPU').reduce(sum, 0); const totalCPU = tasks.mapBy('resources.CPU').reduce(sum, 0);
const totalMemory = tasks.mapBy('resources.MemoryMB').reduce(sum, 0); const totalMemory = tasks.mapBy('resources.MemoryMB').reduce(sum, 0);
const totalMemoryMax = tasks const totalMemoryMax = tasks
.map(t => t.resources.MemoryMaxMB || t.resources.MemoryMB) .map((t) => t.resources.MemoryMaxMB || t.resources.MemoryMB)
.reduce(sum, 0); .reduce(sum, 0);
const totalDisk = taskGroup.ephemeralDisk.SizeMB; const totalDisk = taskGroup.ephemeralDisk.SizeMB;
@ -127,7 +127,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', async function(assert) { test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
assert.equal( assert.equal(
@ -147,14 +147,14 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('/jobs/:id/:task-group first breadcrumb should link to jobs', async function(assert) { test('/jobs/:id/:task-group first breadcrumb should link to jobs', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
await Layout.breadcrumbFor('jobs.index').visit(); await Layout.breadcrumbFor('jobs.index').visit();
assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs'); assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs');
}); });
test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', async function(assert) { test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
await Layout.breadcrumbFor('jobs.job.index').visit(); await Layout.breadcrumbFor('jobs.job.index').visit();
@ -165,7 +165,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('when the user has a client token that has a namespace with a policy to run and scale a job the autoscaler options should be available', async function(assert) { test('when the user has a client token that has a namespace with a policy to run and scale a job the autoscaler options should be available', async function (assert) {
window.localStorage.clear(); window.localStorage.clear();
const SCALE_AND_WRITE_NAMESPACE = 'scale-and-write-namespace'; const SCALE_AND_WRITE_NAMESPACE = 'scale-and-write-namespace';
@ -174,7 +174,7 @@ module('Acceptance | task group detail', function(hooks) {
server.create('namespace', { id: SCALE_AND_WRITE_NAMESPACE }); server.create('namespace', { id: SCALE_AND_WRITE_NAMESPACE });
const secondNamespace = server.create('namespace', { const secondNamespace = server.create('namespace', {
id: READ_ONLY_NAMESPACE id: READ_ONLY_NAMESPACE,
}); });
job = server.create('job', { job = server.create('job', {
@ -182,14 +182,14 @@ module('Acceptance | task group detail', function(hooks) {
createAllocations: false, createAllocations: false,
shallow: true, shallow: true,
noActiveDeployment: true, noActiveDeployment: true,
namespaceId: SCALE_AND_WRITE_NAMESPACE namespaceId: SCALE_AND_WRITE_NAMESPACE,
}); });
const scalingGroup = server.create('task-group', { const scalingGroup = server.create('task-group', {
job, job,
name: 'scaling', name: 'scaling',
count: 1, count: 1,
shallow: true, shallow: true,
withScaling: true withScaling: true,
}); });
job.update({ taskGroupIds: [scalingGroup.id] }); job.update({ taskGroupIds: [scalingGroup.id] });
@ -198,14 +198,14 @@ module('Acceptance | task group detail', function(hooks) {
createAllocations: false, createAllocations: false,
shallow: true, shallow: true,
noActiveDeployment: true, noActiveDeployment: true,
namespaceId: READ_ONLY_NAMESPACE namespaceId: READ_ONLY_NAMESPACE,
}); });
const scalingGroup2 = server.create('task-group', { const scalingGroup2 = server.create('task-group', {
job: job2, job: job2,
name: 'scaling', name: 'scaling',
count: 1, count: 1,
shallow: true, shallow: true,
withScaling: true withScaling: true,
}); });
job2.update({ taskGroupIds: [scalingGroup2.id] }); job2.update({ taskGroupIds: [scalingGroup2.id] });
@ -216,14 +216,14 @@ module('Acceptance | task group detail', function(hooks) {
Namespaces: [ Namespaces: [
{ {
Name: SCALE_AND_WRITE_NAMESPACE, Name: SCALE_AND_WRITE_NAMESPACE,
Capabilities: ['scale-job', 'submit-job', 'read-job', 'list-jobs'] Capabilities: ['scale-job', 'submit-job', 'read-job', 'list-jobs'],
}, },
{ {
Name: READ_ONLY_NAMESPACE, Name: READ_ONLY_NAMESPACE,
Capabilities: ['list-jobs', 'read-job'] Capabilities: ['list-jobs', 'read-job'],
} },
] ],
} },
}); });
clientToken.policyIds = [policy.id]; clientToken.policyIds = [policy.id];
@ -234,7 +234,7 @@ module('Acceptance | task group detail', function(hooks) {
await TaskGroup.visit({ await TaskGroup.visit({
id: job.id, id: job.id,
name: scalingGroup.name, name: scalingGroup.name,
namespace: SCALE_AND_WRITE_NAMESPACE namespace: SCALE_AND_WRITE_NAMESPACE,
}); });
assert.equal( assert.equal(
@ -246,7 +246,7 @@ module('Acceptance | task group detail', function(hooks) {
await TaskGroup.visit({ await TaskGroup.visit({
id: job2.id, id: job2.id,
name: scalingGroup2.name, name: scalingGroup2.name,
namespace: secondNamespace.name namespace: secondNamespace.name,
}); });
assert.equal( assert.equal(
currentURL(), currentURL(),
@ -255,11 +255,11 @@ module('Acceptance | task group detail', function(hooks) {
assert.ok(TaskGroup.countStepper.increment.isDisabled); assert.ok(TaskGroup.countStepper.increment.isDisabled);
}); });
test('/jobs/:id/:task-group should list one page of allocations for the task group', async function(assert) { test('/jobs/:id/:task-group should list one page of allocations for the task group', async function (assert) {
server.createList('allocation', TaskGroup.pageSize, { server.createList('allocation', TaskGroup.pageSize, {
jobId: job.id, jobId: job.id,
taskGroup: taskGroup.name, taskGroup: taskGroup.name,
clientStatus: 'running' clientStatus: 'running',
}); });
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
@ -277,7 +277,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('each allocation should show basic information about the allocation', async function(assert) { test('each allocation should show basic information about the allocation', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
const allocation = allocations.sortBy('modifyIndex').reverse()[0]; const allocation = allocations.sortBy('modifyIndex').reverse()[0];
@ -320,16 +320,16 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('clicking the client ID in the allocation row naviates to the client page', async function(assert) { test('clicking the client ID in the allocation row naviates to the client page', async function (assert) {
// Navigating to the client page requires node:read permission. // Navigating to the client page requires node:read permission.
const policy = server.create('policy', { const policy = server.create('policy', {
id: 'node-read', id: 'node-read',
name: 'node-read', name: 'node-read',
rulesJSON: { rulesJSON: {
Node: { Node: {
Policy: 'read' Policy: 'read',
} },
} },
}); });
const clientToken = server.create('token', { type: 'client' }); const clientToken = server.create('token', { type: 'client' });
clientToken.policyIds = [policy.id]; clientToken.policyIds = [policy.id];
@ -349,14 +349,14 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('each allocation should show stats about the allocation', async function(assert) { test('each allocation should show stats about the allocation', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
const allocation = allocations.sortBy('name')[0]; const allocation = allocations.sortBy('name')[0];
const allocationRow = TaskGroup.allocations.objectAt(0); const allocationRow = TaskGroup.allocations.objectAt(0);
const allocStats = server.db.clientAllocationStats.find(allocation.id); const allocStats = server.db.clientAllocationStats.find(allocation.id);
const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id));
const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0);
const memoryUsed = tasks.reduce( const memoryUsed = tasks.reduce(
@ -395,7 +395,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('when the allocation search has no matches, there is an empty message', async function(assert) { test('when the allocation search has no matches, there is an empty message', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
await TaskGroup.search('zzzzzz'); await TaskGroup.search('zzzzzz');
@ -408,7 +408,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('when the allocation has reschedule events, the allocation row is denoted with an icon', async function(assert) { test('when the allocation has reschedule events, the allocation row is denoted with an icon', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
const rescheduleRow = TaskGroup.allocationFor(allocations[0].id); const rescheduleRow = TaskGroup.allocationFor(allocations[0].id);
@ -421,10 +421,10 @@ module('Acceptance | task group detail', function(hooks) {
assert.notOk(normalRow.rescheduled, 'Normal row has no reschedule icon'); assert.notOk(normalRow.rescheduled, 'Normal row has no reschedule icon');
}); });
test('/jobs/:id/:task-group should present task lifecycles', async function(assert) { test('/jobs/:id/:task-group should present task lifecycles', async function (assert) {
job = server.create('job', { job = server.create('job', {
groupsCount: 2, groupsCount: 2,
groupTaskCount: 3 groupTaskCount: 3,
}); });
const taskGroups = server.db.taskGroups.where({ jobId: job.id }); const taskGroups = server.db.taskGroups.where({ jobId: job.id });
@ -438,21 +438,21 @@ module('Acceptance | task group detail', function(hooks) {
'Task Lifecycle Configuration' 'Task Lifecycle Configuration'
); );
tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id));
const taskNames = tasks.mapBy('name'); const taskNames = tasks.mapBy('name');
// This is thoroughly tested in allocation detail tests, so this mostly checks whats different // This is thoroughly tested in allocation detail tests, so this mostly checks whats different
assert.equal(TaskGroup.lifecycleChart.tasks.length, 3); assert.equal(TaskGroup.lifecycleChart.tasks.length, 3);
TaskGroup.lifecycleChart.tasks.forEach(Task => { TaskGroup.lifecycleChart.tasks.forEach((Task) => {
assert.ok(taskNames.includes(Task.name)); assert.ok(taskNames.includes(Task.name));
assert.notOk(Task.isActive); assert.notOk(Task.isActive);
assert.notOk(Task.isFinished); assert.notOk(Task.isFinished);
}); });
}); });
test('when the task group depends on volumes, the volumes table is shown', async function(assert) { test('when the task group depends on volumes, the volumes table is shown', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
assert.ok(TaskGroup.hasVolumes); assert.ok(TaskGroup.hasVolumes);
@ -462,7 +462,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('when the task group does not depend on volumes, the volumes table is not shown', async function(assert) { test('when the task group does not depend on volumes, the volumes table is not shown', async function (assert) {
job = server.create('job', { noHostVolumes: true, shallow: true }); job = server.create('job', { noHostVolumes: true, shallow: true });
taskGroup = server.db.taskGroups.where({ jobId: job.id })[0]; taskGroup = server.db.taskGroups.where({ jobId: job.id })[0];
@ -471,10 +471,10 @@ module('Acceptance | task group detail', function(hooks) {
assert.notOk(TaskGroup.hasVolumes); assert.notOk(TaskGroup.hasVolumes);
}); });
test('each row in the volumes table lists information about the volume', async function(assert) { test('each row in the volumes table lists information about the volume', async function (assert) {
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
TaskGroup.volumes[0].as(volumeRow => { TaskGroup.volumes[0].as((volumeRow) => {
const volume = taskGroup.volumes[volumeRow.name]; const volume = taskGroup.volumes[volumeRow.name];
assert.equal(volumeRow.name, volume.Name); assert.equal(volumeRow.name, volume.Name);
assert.equal(volumeRow.type, volume.Type); assert.equal(volumeRow.type, volume.Type);
@ -486,21 +486,21 @@ module('Acceptance | task group detail', function(hooks) {
}); });
}); });
test('the count stepper sends the appropriate POST request', async function(assert) { test('the count stepper sends the appropriate POST request', async function (assert) {
window.localStorage.nomadTokenSecret = managementToken.secretId; window.localStorage.nomadTokenSecret = managementToken.secretId;
job = server.create('job', { job = server.create('job', {
groupCount: 0, groupCount: 0,
createAllocations: false, createAllocations: false,
shallow: true, shallow: true,
noActiveDeployment: true noActiveDeployment: true,
}); });
const scalingGroup = server.create('task-group', { const scalingGroup = server.create('task-group', {
job, job,
name: 'scaling', name: 'scaling',
count: 1, count: 1,
shallow: true, shallow: true,
withScaling: true withScaling: true,
}); });
job.update({ taskGroupIds: [scalingGroup.id] }); job.update({ taskGroupIds: [scalingGroup.id] });
@ -509,28 +509,28 @@ module('Acceptance | task group detail', function(hooks) {
await settled(); await settled();
const scaleRequest = server.pretender.handledRequests.find( const scaleRequest = server.pretender.handledRequests.find(
req => req.method === 'POST' && req.url.endsWith('/scale') (req) => req.method === 'POST' && req.url.endsWith('/scale')
); );
const requestBody = JSON.parse(scaleRequest.requestBody); const requestBody = JSON.parse(scaleRequest.requestBody);
assert.equal(requestBody.Target.Group, scalingGroup.name); assert.equal(requestBody.Target.Group, scalingGroup.name);
assert.equal(requestBody.Count, scalingGroup.count + 1); assert.equal(requestBody.Count, scalingGroup.count + 1);
}); });
test('the count stepper is disabled when a deployment is running', async function(assert) { test('the count stepper is disabled when a deployment is running', async function (assert) {
window.localStorage.nomadTokenSecret = managementToken.secretId; window.localStorage.nomadTokenSecret = managementToken.secretId;
job = server.create('job', { job = server.create('job', {
groupCount: 0, groupCount: 0,
createAllocations: false, createAllocations: false,
shallow: true, shallow: true,
activeDeployment: true activeDeployment: true,
}); });
const scalingGroup = server.create('task-group', { const scalingGroup = server.create('task-group', {
job, job,
name: 'scaling', name: 'scaling',
count: 1, count: 1,
shallow: true, shallow: true,
withScaling: true withScaling: true,
}); });
job.update({ taskGroupIds: [scalingGroup.id] }); job.update({ taskGroupIds: [scalingGroup.id] });
@ -541,15 +541,15 @@ module('Acceptance | task group detail', function(hooks) {
assert.ok(TaskGroup.countStepper.decrement.isDisabled); assert.ok(TaskGroup.countStepper.decrement.isDisabled);
}); });
test('when the job for the task group is not found, an error message is shown, but the URL persists', async function(assert) { test('when the job for the task group is not found, an error message is shown, but the URL persists', async function (assert) {
await TaskGroup.visit({ await TaskGroup.visit({
id: 'not-a-real-job', id: 'not-a-real-job',
name: 'not-a-real-task-group' name: 'not-a-real-task-group',
}); });
assert.equal( assert.equal(
server.pretender.handledRequests server.pretender.handledRequests
.filter(request => !request.url.includes('policy')) .filter((request) => !request.url.includes('policy'))
.findBy('status', 404).url, .findBy('status', 404).url,
'/v1/job/not-a-real-job', '/v1/job/not-a-real-job',
'A request to the nonexistent job is made' 'A request to the nonexistent job is made'
@ -567,7 +567,7 @@ module('Acceptance | task group detail', function(hooks) {
); );
}); });
test('when the task group is not found on the job, an error message is shown, but the URL persists', async function(assert) { test('when the task group is not found on the job, an error message is shown, but the URL persists', async function (assert) {
await TaskGroup.visit({ id: job.id, name: 'not-a-real-task-group' }); await TaskGroup.visit({ id: job.id, name: 'not-a-real-task-group' });
assert.ok( assert.ok(
@ -598,16 +598,16 @@ module('Acceptance | task group detail', function(hooks) {
server.createList('allocation', TaskGroup.pageSize, { server.createList('allocation', TaskGroup.pageSize, {
jobId: job.id, jobId: job.id,
taskGroup: taskGroup.name, taskGroup: taskGroup.name,
clientStatus: 'running' clientStatus: 'running',
}); });
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
} },
}); });
test('when a task group has no scaling events, there is no recent scaling events section', async function(assert) { test('when a task group has no scaling events, there is no recent scaling events section', async function (assert) {
const taskGroupScale = job.jobScale.taskGroupScales.models.find( const taskGroupScale = job.jobScale.taskGroupScales.models.find(
m => m.name === taskGroup.name (m) => m.name === taskGroup.name
); );
taskGroupScale.update({ events: [] }); taskGroupScale.update({ events: [] });
@ -616,9 +616,9 @@ module('Acceptance | task group detail', function(hooks) {
assert.notOk(TaskGroup.hasScaleEvents); assert.notOk(TaskGroup.hasScaleEvents);
}); });
test('the recent scaling events section shows all recent scaling events in reverse chronological order', async function(assert) { test('the recent scaling events section shows all recent scaling events in reverse chronological order', async function (assert) {
const taskGroupScale = job.jobScale.taskGroupScales.models.find( const taskGroupScale = job.jobScale.taskGroupScales.models.find(
m => m.name === taskGroup.name (m) => m.name === taskGroup.name
); );
taskGroupScale.update({ taskGroupScale.update({
events: [ events: [
@ -627,8 +627,8 @@ module('Acceptance | task group detail', function(hooks) {
server.create('scale-event', { error: true }), server.create('scale-event', { error: true }),
server.create('scale-event', { error: true }), server.create('scale-event', { error: true }),
server.create('scale-event', { count: 3, error: false }), server.create('scale-event', { count: 3, error: false }),
server.create('scale-event', { count: 1, error: false }) server.create('scale-event', { count: 1, error: false }),
] ],
}); });
const scaleEvents = taskGroupScale.events.models.sortBy('time').reverse(); const scaleEvents = taskGroupScale.events.models.sortBy('time').reverse();
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
@ -660,9 +660,9 @@ module('Acceptance | task group detail', function(hooks) {
}); });
}); });
test('when a task group has at least two count scaling events and the count scaling events outnumber the non-count scaling events, a timeline is shown in addition to the accordion', async function(assert) { test('when a task group has at least two count scaling events and the count scaling events outnumber the non-count scaling events, a timeline is shown in addition to the accordion', async function (assert) {
const taskGroupScale = job.jobScale.taskGroupScales.models.find( const taskGroupScale = job.jobScale.taskGroupScales.models.find(
m => m.name === taskGroup.name (m) => m.name === taskGroup.name
); );
taskGroupScale.update({ taskGroupScale.update({
events: [ events: [
@ -674,8 +674,8 @@ module('Acceptance | task group detail', function(hooks) {
server.create('scale-event', { count: 3, error: false }), server.create('scale-event', { count: 3, error: false }),
server.create('scale-event', { count: 2, error: false }), server.create('scale-event', { count: 2, error: false }),
server.create('scale-event', { count: 9, error: false }), server.create('scale-event', { count: 9, error: false }),
server.create('scale-event', { count: 1, error: false }) server.create('scale-event', { count: 1, error: false }),
] ],
}); });
const scaleEvents = taskGroupScale.events.models.sortBy('time').reverse(); const scaleEvents = taskGroupScale.events.models.sortBy('time').reverse();
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
@ -685,7 +685,7 @@ module('Acceptance | task group detail', function(hooks) {
assert.equal( assert.equal(
TaskGroup.scalingAnnotations.length, TaskGroup.scalingAnnotations.length,
scaleEvents.filter(ev => ev.count == null).length scaleEvents.filter((ev) => ev.count == null).length
); );
}); });
@ -694,7 +694,7 @@ module('Acceptance | task group detail', function(hooks) {
paramName: 'status', paramName: 'status',
expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'],
async beforeEach() { async beforeEach() {
['pending', 'running', 'complete', 'failed', 'lost'].forEach(s => { ['pending', 'running', 'complete', 'failed', 'lost'].forEach((s) => {
server.createList('allocation', 5, { clientStatus: s }); server.createList('allocation', 5, { clientStatus: s });
}); });
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
@ -702,7 +702,7 @@ module('Acceptance | task group detail', function(hooks) {
filter: (alloc, selection) => filter: (alloc, selection) =>
alloc.jobId == job.id && alloc.jobId == job.id &&
alloc.taskGroup == taskGroup.name && alloc.taskGroup == taskGroup.name &&
selection.includes(alloc.clientStatus) selection.includes(alloc.clientStatus),
}); });
testFacet('Client', { testFacet('Client', {
@ -713,21 +713,21 @@ module('Acceptance | task group detail', function(hooks) {
new Set( new Set(
allocs allocs
.filter( .filter(
alloc => (alloc) =>
alloc.jobId == job.id && alloc.taskGroup == taskGroup.name alloc.jobId == job.id && alloc.taskGroup == taskGroup.name
) )
.mapBy('nodeId') .mapBy('nodeId')
.map(id => id.split('-')[0]) .map((id) => id.split('-')[0])
) )
).sort(); ).sort();
}, },
async beforeEach() { async beforeEach() {
const nodes = server.createList('node', 3, 'forceIPv4'); const nodes = server.createList('node', 3, 'forceIPv4');
nodes.forEach(node => nodes.forEach((node) =>
server.createList('allocation', 5, { server.createList('allocation', 5, {
nodeId: node.id, nodeId: node.id,
jobId: job.id, jobId: job.id,
taskGroup: taskGroup.name taskGroup: taskGroup.name,
}) })
); );
await TaskGroup.visit({ id: job.id, name: taskGroup.name }); await TaskGroup.visit({ id: job.id, name: taskGroup.name });
@ -735,7 +735,7 @@ module('Acceptance | task group detail', function(hooks) {
filter: (alloc, selection) => filter: (alloc, selection) =>
alloc.jobId == job.id && alloc.jobId == job.id &&
alloc.taskGroup == taskGroup.name && alloc.taskGroup == taskGroup.name &&
selection.includes(alloc.nodeId.split('-')[0]) selection.includes(alloc.nodeId.split('-')[0]),
}); });
}); });
@ -743,7 +743,7 @@ function testFacet(
label, label,
{ facet, paramName, beforeEach, filter, expectedOptions } { facet, paramName, beforeEach, filter, expectedOptions }
) { ) {
test(`facet ${label} | the ${label} facet has the correct options`, async function(assert) { test(`facet ${label} | the ${label} facet has the correct options`, async function (assert) {
await beforeEach(); await beforeEach();
await facet.toggle(); await facet.toggle();
@ -755,13 +755,13 @@ function testFacet(
} }
assert.deepEqual( assert.deepEqual(
facet.options.map(option => option.label.trim()), facet.options.map((option) => option.label.trim()),
expectation, expectation,
'Options for facet are as expected' 'Options for facet are as expected'
); );
}); });
test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function(assert) { test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function (assert) {
let option; let option;
await beforeEach(); await beforeEach();
@ -772,7 +772,7 @@ function testFacet(
const selection = [option.key]; const selection = [option.key];
const expectedAllocs = server.db.allocations const expectedAllocs = server.db.allocations
.filter(alloc => filter(alloc, selection)) .filter((alloc) => filter(alloc, selection))
.sortBy('modifyIndex') .sortBy('modifyIndex')
.reverse(); .reverse();
@ -785,7 +785,7 @@ function testFacet(
}); });
}); });
test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function (assert) {
const selection = []; const selection = [];
await beforeEach(); await beforeEach();
@ -799,7 +799,7 @@ function testFacet(
selection.push(option2.key); selection.push(option2.key);
const expectedAllocs = server.db.allocations const expectedAllocs = server.db.allocations
.filter(alloc => filter(alloc, selection)) .filter((alloc) => filter(alloc, selection))
.sortBy('modifyIndex') .sortBy('modifyIndex')
.reverse(); .reverse();
@ -812,7 +812,7 @@ function testFacet(
}); });
}); });
test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function (assert) {
const selection = []; const selection = [];
await beforeEach(); await beforeEach();

View file

@ -16,10 +16,10 @@ export default function moduleForJob(
) { ) {
let job; let job;
module(title, function(hooks) { module(title, function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.before(function() { hooks.before(function () {
if (context !== 'allocations' && context !== 'children') { if (context !== 'allocations' && context !== 'children') {
throw new Error( throw new Error(
`Invalid context provided to moduleForJob, expected either "allocations" or "children", got ${context}` `Invalid context provided to moduleForJob, expected either "allocations" or "children", got ${context}`
@ -27,7 +27,7 @@ export default function moduleForJob(
} }
}); });
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
server.create('node'); server.create('node');
job = jobFactory(); job = jobFactory();
if (!job.namespace || job.namespace === 'default') { if (!job.namespace || job.namespace === 'default') {
@ -37,7 +37,7 @@ export default function moduleForJob(
} }
}); });
test('visiting /jobs/:job_id', async function(assert) { test('visiting /jobs/:job_id', async function (assert) {
const expectedURL = new URL( const expectedURL = new URL(
urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace), urlWithNamespace(`/jobs/${encodeURIComponent(job.id)}`, job.namespace),
window.location window.location
@ -49,7 +49,7 @@ export default function moduleForJob(
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();
const expectedURL = new URL( const expectedURL = new URL(
@ -62,7 +62,7 @@ export default function moduleForJob(
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); assert.deepEqual(gotURL.searchParams, expectedURL.searchParams);
}); });
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( assert.equal(
currentURL(), currentURL(),
@ -73,7 +73,7 @@ export default function moduleForJob(
); );
}); });
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( assert.equal(
currentURL(), currentURL(),
@ -84,7 +84,7 @@ export default function moduleForJob(
); );
}); });
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( assert.equal(
currentURL(), currentURL(),
@ -95,7 +95,7 @@ export default function moduleForJob(
); );
}); });
test('the title buttons are dependent on job status', async function(assert) { test('the title buttons are dependent on job status', async function (assert) {
if (job.status === 'dead') { if (job.status === 'dead') {
assert.ok(JobDetail.start.isPresent); assert.ok(JobDetail.start.isPresent);
assert.notOk(JobDetail.stop.isPresent); assert.notOk(JobDetail.stop.isPresent);
@ -108,7 +108,7 @@ export default function moduleForJob(
}); });
if (context === 'allocations') { if (context === 'allocations') {
test('allocations for the job are shown in the overview', async function(assert) { test('allocations for the job are shown in the overview', async function (assert) {
assert.ok( assert.ok(
JobDetail.allocationsSummary.isPresent, JobDetail.allocationsSummary.isPresent,
'Allocations are shown in the summary section' 'Allocations are shown in the summary section'
@ -119,7 +119,7 @@ export default function moduleForJob(
); );
}); });
test('clicking in an allocation row navigates to that allocation', async function(assert) { test('clicking in an allocation row navigates to that allocation', async function (assert) {
const allocationRow = JobDetail.allocations[0]; const allocationRow = JobDetail.allocations[0];
const allocationId = allocationRow.id; const allocationId = allocationRow.id;
@ -132,7 +132,7 @@ export default function moduleForJob(
); );
}); });
test('clicking legend item navigates to a pre-filtered allocations table', async function(assert) { test('clicking legend item navigates to a pre-filtered allocations table', async function (assert) {
const legendItem = const legendItem =
JobDetail.allocationsSummary.legend.clickableItems[1]; JobDetail.allocationsSummary.legend.clickableItems[1];
const status = legendItem.label; const status = legendItem.label;
@ -151,7 +151,7 @@ export default function moduleForJob(
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); assert.deepEqual(gotURL.searchParams, expectedURL.searchParams);
}); });
test('clicking in a slice takes you to a pre-filtered allocations table', async function(assert) { test('clicking in a slice takes you to a pre-filtered allocations table', async function (assert) {
const slice = JobDetail.allocationsSummary.slices[1]; const slice = JobDetail.allocationsSummary.slices[1];
const status = slice.label; const status = slice.label;
await slice.click(); await slice.click();
@ -180,7 +180,7 @@ export default function moduleForJob(
} }
if (context === 'children') { if (context === 'children') {
test('children for the job are shown in the overview', async function(assert) { test('children for the job are shown in the overview', async function (assert) {
assert.ok( assert.ok(
JobDetail.childrenSummary.isPresent, JobDetail.childrenSummary.isPresent,
'Children are shown in the summary section' 'Children are shown in the summary section'
@ -193,7 +193,7 @@ export default function moduleForJob(
} }
for (var testName in additionalTests) { for (var testName in additionalTests) {
test(testName, async function(assert) { test(testName, async function (assert) {
await additionalTests[testName].call(this, job, assert); await additionalTests[testName].call(this, job, assert);
}); });
} }
@ -208,20 +208,20 @@ export function moduleForJobWithClientStatus(
) { ) {
let job; let job;
module(title, function(hooks) { module(title, function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(async function() { hooks.beforeEach(async function () {
// Displaying the job status in client requires node:read permission. // Displaying the job status in client requires node:read permission.
const policy = server.create('policy', { const policy = server.create('policy', {
id: 'node-read', id: 'node-read',
name: 'node-read', name: 'node-read',
rulesJSON: { rulesJSON: {
Node: { Node: {
Policy: 'read' Policy: 'read',
} },
} },
}); });
const clientToken = server.create('token', { type: 'client' }); const clientToken = server.create('token', { type: 'client' });
clientToken.policyIds = [policy.id]; clientToken.policyIds = [policy.id];
@ -232,10 +232,10 @@ export function moduleForJobWithClientStatus(
const clients = server.createList('node', 3, { const clients = server.createList('node', 3, {
datacenter: 'dc1', datacenter: 'dc1',
status: 'ready' status: 'ready',
}); });
job = jobFactory(); job = jobFactory();
clients.forEach(c => { clients.forEach((c) => {
server.create('allocation', { jobId: job.id, nodeId: c.id }); server.create('allocation', { jobId: job.id, nodeId: c.id });
}); });
if (!job.namespace || job.namespace === 'default') { if (!job.namespace || job.namespace === 'default') {
@ -245,7 +245,7 @@ export function moduleForJobWithClientStatus(
} }
}); });
test('job status summary is collapsed when not authorized', async function(assert) { test('job status summary is collapsed when not authorized', async function (assert) {
const clientToken = server.create('token', { type: 'client' }); const clientToken = server.create('token', { type: 'client' });
await Tokens.visit(); await Tokens.visit();
await Tokens.secret(clientToken.secretId).submit(); await Tokens.secret(clientToken.secretId).submit();
@ -262,7 +262,7 @@ export function moduleForJobWithClientStatus(
); );
}); });
test('the subnav links to clients', async function(assert) { test('the subnav links to clients', async function (assert) {
await JobDetail.tabFor('clients').visit(); await JobDetail.tabFor('clients').visit();
assert.equal( assert.equal(
currentURL(), currentURL(),
@ -273,14 +273,14 @@ export function moduleForJobWithClientStatus(
); );
}); });
test('job status summary is shown in the overview', async function(assert) { test('job status summary is shown in the overview', async function (assert) {
assert.ok( assert.ok(
JobDetail.jobClientStatusSummary.statusBar.isPresent, JobDetail.jobClientStatusSummary.statusBar.isPresent,
'Summary bar is displayed in the Job Status in Client summary section' 'Summary bar is displayed in the Job Status in Client summary section'
); );
}); });
test('clicking legend item navigates to a pre-filtered clients table', async function(assert) { test('clicking legend item navigates to a pre-filtered clients table', async function (assert) {
const legendItem = const legendItem =
JobDetail.jobClientStatusSummary.statusBar.legend.clickableItems[0]; JobDetail.jobClientStatusSummary.statusBar.legend.clickableItems[0];
const status = legendItem.label; const status = legendItem.label;
@ -299,7 +299,7 @@ export function moduleForJobWithClientStatus(
assert.deepEqual(gotURL.searchParams, expectedURL.searchParams); assert.deepEqual(gotURL.searchParams, expectedURL.searchParams);
}); });
test('clicking in a slice takes you to a pre-filtered clients table', async function(assert) { test('clicking in a slice takes you to a pre-filtered clients table', async function (assert) {
const slice = JobDetail.jobClientStatusSummary.statusBar.slices[0]; const slice = JobDetail.jobClientStatusSummary.statusBar.slices[0];
const status = slice.label; const status = slice.label;
await slice.click(); await slice.click();
@ -325,7 +325,7 @@ export function moduleForJobWithClientStatus(
}); });
for (var testName in additionalTests) { for (var testName in additionalTests) {
test(testName, async function(assert) { test(testName, async function (assert) {
await additionalTests[testName].call(this, job, assert); await additionalTests[testName].call(this, job, assert);
}); });
} }

View file

@ -8,10 +8,10 @@ import Response from 'ember-cli-mirage/response';
import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer'; import { initialize as fragmentSerializerInitializer } from 'nomad-ui/initializers/fragment-serializer';
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit'; import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
module('Integration | Component | allocation row', function(hooks) { module('Integration | Component | allocation row', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function () {
fragmentSerializerInitializer(this.owner); fragmentSerializerInitializer(this.owner);
this.store = this.owner.lookup('service:store'); this.store = this.owner.lookup('service:store');
this.token = this.owner.lookup('service:token'); this.token = this.owner.lookup('service:token');
@ -22,11 +22,11 @@ module('Integration | Component | allocation row', function(hooks) {
window.localStorage.clear(); window.localStorage.clear();
}); });
hooks.afterEach(function() { hooks.afterEach(function () {
this.server.shutdown(); this.server.shutdown();
}); });
test('Allocation row polls for stats, even when it errors or has an invalid response', async function(assert) { test('Allocation row polls for stats, even when it errors or has an invalid response', async function (assert) {
const component = this; const component = this;
let currentFrame = 0; let currentFrame = 0;
@ -35,10 +35,10 @@ module('Integration | Component | allocation row', function(hooks) {
JSON.stringify({ ResourceUsage: generateResources() }), JSON.stringify({ ResourceUsage: generateResources() }),
null, null,
'<Not>Valid JSON</Not>', '<Not>Valid JSON</Not>',
JSON.stringify({ ResourceUsage: generateResources() }) JSON.stringify({ ResourceUsage: generateResources() }),
]; ];
this.server.get('/client/allocation/:id/stats', function() { this.server.get('/client/allocation/:id/stats', function () {
const response = frames[++currentFrame]; const response = frames[++currentFrame];
// Disable polling to stop the EC task in the component // Disable polling to stop the EC task in the component
@ -60,7 +60,7 @@ module('Integration | Component | allocation row', function(hooks) {
this.setProperties({ this.setProperties({
allocation, allocation,
context: 'job', context: 'job',
enablePolling: true enablePolling: true,
}); });
await render(hbs` await render(hbs`
@ -80,16 +80,18 @@ module('Integration | Component | allocation row', function(hooks) {
); );
}); });
test('Allocation row shows warning when it requires drivers that are unhealthy on the node it is running on', async function(assert) { test('Allocation row shows warning when it requires drivers that are unhealthy on the node it is running on', async function (assert) {
assert.expect(2);
// Driver health status require node:read permission. // Driver health status require node:read permission.
const policy = server.create('policy', { const policy = server.create('policy', {
id: 'node-read', id: 'node-read',
name: 'node-read', name: 'node-read',
rulesJSON: { rulesJSON: {
Node: { Node: {
Policy: 'read' Policy: 'read',
} },
} },
}); });
const clientToken = server.create('token', { type: 'client' }); const clientToken = server.create('token', { type: 'client' });
clientToken.policyIds = [policy.id]; clientToken.policyIds = [policy.id];
@ -101,7 +103,7 @@ module('Integration | Component | allocation row', function(hooks) {
const node = this.server.schema.nodes.first(); const node = this.server.schema.nodes.first();
const drivers = node.drivers; const drivers = node.drivers;
Object.values(drivers).forEach(driver => { Object.values(drivers).forEach((driver) => {
driver.Healthy = false; driver.Healthy = false;
driver.Detected = true; driver.Detected = true;
}); });
@ -116,7 +118,7 @@ module('Integration | Component | allocation row', function(hooks) {
this.setProperties({ this.setProperties({
allocation, allocation,
context: 'job' context: 'job',
}); });
await render(hbs` await render(hbs`
@ -132,7 +134,9 @@ module('Integration | Component | allocation row', function(hooks) {
await componentA11yAudit(this.element, assert); await componentA11yAudit(this.element, assert);
}); });
test('Allocation row shows an icon indicator when it was preempted', async function(assert) { test('Allocation row shows an icon indicator when it was preempted', async function (assert) {
assert.expect(2);
const allocId = this.server.create('allocation', 'preempted').id; const allocId = this.server.create('allocation', 'preempted').id;
const allocation = await this.store.findRecord('allocation', allocId); const allocation = await this.store.findRecord('allocation', allocId);
@ -147,14 +151,16 @@ module('Integration | Component | allocation row', function(hooks) {
await componentA11yAudit(this.element, assert); await componentA11yAudit(this.element, assert);
}); });
test('when an allocation is not running, the utilization graphs are omitted', async function(assert) { test('when an allocation is not running, the utilization graphs are omitted', async function (assert) {
assert.expect(8);
this.setProperties({ this.setProperties({
context: 'job', context: 'job',
enablePolling: false enablePolling: false,
}); });
// All non-running statuses need to be tested // All non-running statuses need to be tested
['pending', 'complete', 'failed', 'lost'].forEach(clientStatus => ['pending', 'complete', 'failed', 'lost'].forEach((clientStatus) =>
this.server.create('allocation', { clientStatus }) this.server.create('allocation', { clientStatus })
); );

View file

@ -1,20 +1,21 @@
/* eslint-disable ember-a11y-testing/a11y-audit-called */ /* eslint-disable ember-a11y-testing/a11y-audit-called */
import { setComponentTemplate } from '@ember/component'; import { setComponentTemplate } from '@ember/component';
import Component from '@glimmer/component'; import templateOnlyComponent from '@ember/component/template-only';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit'; import { setupRenderingTest } from 'ember-qunit';
import { findAll, render } from '@ember/test-helpers'; import { findAll, render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | app breadcrumbs', function(hooks) { module('Integration | Component | app breadcrumbs', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
const commonCrumbs = [ const commonCrumbs = [
{ label: 'Jobs', args: ['jobs.index'] }, { label: 'Jobs', args: ['jobs.index'] },
{ label: 'Job', args: ['jobs.job.index'] } { label: 'Job', args: ['jobs.job.index'] },
]; ];
test('every breadcrumb is rendered correctly', async function(assert) { test('every breadcrumb is rendered correctly', async function (assert) {
assert.expect(3);
this.set('commonCrumbs', commonCrumbs); this.set('commonCrumbs', commonCrumbs);
await render(hbs` await render(hbs`
<AppBreadcrumbs /> <AppBreadcrumbs />
@ -40,21 +41,20 @@ module('Integration | Component | app breadcrumbs', function(hooks) {
}); });
}); });
test('when we register a crumb with a type property, a dedicated breadcrumb/<type> component renders', async function(assert) { test('when we register a crumb with a type property, a dedicated breadcrumb/<type> component renders', async function (assert) {
const crumbs = [ const crumbs = [
{ label: 'Jobs', args: ['jobs.index'] }, { label: 'Jobs', args: ['jobs.index'] },
{ type: 'special', label: 'Job', args: ['jobs.job.index'] } { type: 'special', label: 'Job', args: ['jobs.job.index'] },
]; ];
this.set('crumbs', crumbs); this.set('crumbs', crumbs);
class MockComponent extends Component {}
this.owner.register( this.owner.register(
'component:breadcrumbs/special', 'component:breadcrumbs/special',
setComponentTemplate( setComponentTemplate(
hbs` hbs`
<div data-test-breadcrumb-special>Test</div> <div data-test-breadcrumb-special>Test</div>
`, `,
MockComponent templateOnlyComponent()
) )
); );

View file

@ -4,10 +4,10 @@ import { setupRenderingTest } from 'ember-qunit';
import { click, findAll, render } from '@ember/test-helpers'; import { click, findAll, render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars'; import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | breadcrumbs', function(hooks) { module('Integration | Component | breadcrumbs', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test('it declaratively renders a list of registered crumbs', async function(assert) { test('it declaratively renders a list of registered crumbs', async function (assert) {
this.set('isRegistered', false); this.set('isRegistered', false);
this.set('toggleCrumb', () => this.set('isRegistered', !this.isRegistered)); this.set('toggleCrumb', () => this.set('isRegistered', !this.isRegistered));
await render(hbs` await render(hbs`
@ -25,8 +25,12 @@ module('Integration | Component | breadcrumbs', function(hooks) {
{{/if}} {{/if}}
`); `);
assert.dom('[data-test-crumb]').exists({ count: 1 }, 'We register one crumb'); assert
assert.dom('[data-test-crumb]').hasText('Zoey', 'The first registered crumb is Zoey'); .dom('[data-test-crumb]')
.exists({ count: 1 }, 'We register one crumb');
assert
.dom('[data-test-crumb]')
.hasText('Zoey', 'The first registered crumb is Zoey');
await click('[data-test-button]'); await click('[data-test-button]');
const crumbs = await findAll('[data-test-crumb]'); const crumbs = await findAll('[data-test-crumb]');
@ -36,19 +40,30 @@ module('Integration | Component | breadcrumbs', function(hooks) {
.exists({ count: 2 }, 'The second crumb registered successfully'); .exists({ count: 2 }, 'The second crumb registered successfully');
assert assert
.dom(crumbs[0]) .dom(crumbs[0])
.hasText('Zoey', 'Breadcrumbs maintain the order in which they are declared'); .hasText(
'Zoey',
'Breadcrumbs maintain the order in which they are declared'
);
assert assert
.dom(crumbs[1]) .dom(crumbs[1])
.hasText('Tomster', 'Breadcrumbs maintain the order in which they are declared'); .hasText(
'Tomster',
'Breadcrumbs maintain the order in which they are declared'
);
await click('[data-test-button]'); await click('[data-test-button]');
assert.dom('[data-test-crumb]').exists({ count: 1 }, 'We deregister one crumb');
assert assert
.dom('[data-test-crumb]') .dom('[data-test-crumb]')
.hasText('Zoey', 'Zoey remains in the template after Tomster deregisters'); .exists({ count: 1 }, 'We deregister one crumb');
assert
.dom('[data-test-crumb]')
.hasText(
'Zoey',
'Zoey remains in the template after Tomster deregisters'
);
}); });
test('it can register complex crumb objects', async function(assert) { test('it can register complex crumb objects', async function (assert) {
await render(hbs` await render(hbs`
<Breadcrumbs as |bb|> <Breadcrumbs as |bb|>
<ul> <ul>
@ -62,6 +77,9 @@ module('Integration | Component | breadcrumbs', function(hooks) {
assert assert
.dom('[data-test-crumb]') .dom('[data-test-crumb]')
.hasText('Tomster', 'We can access the registered breadcrumbs in the template'); .hasText(
'Tomster',
'We can access the registered breadcrumbs in the template'
);
}); });
}); });

View file

@ -4,11 +4,11 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, click, waitFor } from '@ember/test-helpers'; import { render, click, waitFor } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars'; import { hbs } from 'ember-cli-htmlbars';
module('Integration | Component | trigger', function(hooks) { module('Integration | Component | trigger', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
module('Synchronous Interactions', function() { module('Synchronous Interactions', function () {
test('it can trigger a synchronous action', async function(assert) { test('it can trigger a synchronous action', async function (assert) {
this.set('name', 'Tomster'); this.set('name', 'Tomster');
this.set('changeName', () => this.set('name', 'Zoey')); this.set('changeName', () => this.set('name', 'Zoey'));
await render(hbs` await render(hbs`
@ -17,16 +17,21 @@ module('Integration | Component | trigger', function(hooks) {
<button data-test-button type="button" {{on "click" trigger.fns.do}}>Change my name.</button> <button data-test-button type="button" {{on "click" trigger.fns.do}}>Change my name.</button>
</Trigger> </Trigger>
`); `);
assert.dom('[data-test-name]').hasText('Tomster', 'Initial state renders correctly.'); assert
.dom('[data-test-name]')
.hasText('Tomster', 'Initial state renders correctly.');
await click('[data-test-button]'); await click('[data-test-button]');
assert assert
.dom('[data-test-name]') .dom('[data-test-name]')
.hasText('Zoey', 'The name property changes when the button is clicked'); .hasText(
'Zoey',
'The name property changes when the button is clicked'
);
}); });
test('it sets the result of the action', async function(assert) { test('it sets the result of the action', async function (assert) {
this.set('tomster', () => 'Tomster'); this.set('tomster', () => 'Tomster');
await render(hbs` await render(hbs`
<Trigger @do={{this.tomster}} as |trigger|> <Trigger @do={{this.tomster}} as |trigger|>
@ -38,22 +43,27 @@ module('Integration | Component | trigger', function(hooks) {
`); `);
assert assert
.dom('[data-test-name]') .dom('[data-test-name]')
.doesNotExist('Initial state does not render because there is no result yet.'); .doesNotExist(
'Initial state does not render because there is no result yet.'
);
await click('[data-test-button]'); await click('[data-test-button]');
assert assert
.dom('[data-test-name]') .dom('[data-test-name]')
.hasText('Tomster', 'The result state updates after the triggered action'); .hasText(
'Tomster',
'The result state updates after the triggered action'
);
}); });
}); });
module('Asynchronous Interactions', function() { module('Asynchronous Interactions', function () {
test('it can trigger an asynchronous action', async function(assert) { test('it can trigger an asynchronous action', async function (assert) {
this.set( this.set(
'onTrigger', 'onTrigger',
() => () =>
new Promise(resolve => { new Promise((resolve) => {
this.set('resolve', resolve); this.set('resolve', resolve);
}) })
); );
@ -72,15 +82,21 @@ module('Integration | Component | trigger', function(hooks) {
assert assert
.dom('[data-test-div]') .dom('[data-test-div]')
.doesNotExist('The div does not render until after the action dispatches successfully'); .doesNotExist(
'The div does not render until after the action dispatches successfully'
);
await click('[data-test-button]'); await click('[data-test-button]');
assert assert
.dom('[data-test-div-loading]') .dom('[data-test-div-loading]')
.exists('Loading state is displayed when the action hasnt resolved yet'); .exists(
'Loading state is displayed when the action hasnt resolved yet'
);
assert assert
.dom('[data-test-div]') .dom('[data-test-div]')
.doesNotExist('Success message does not display until after promise resolves'); .doesNotExist(
'Success message does not display until after promise resolves'
);
this.resolve(); this.resolve();
await waitFor('[data-test-div]'); await waitFor('[data-test-div]');
@ -91,15 +107,21 @@ module('Integration | Component | trigger', function(hooks) {
); );
assert assert
.dom('[data-test-div]') .dom('[data-test-div]')
.exists('Action has dispatched successfully after the promise resolves'); .exists(
'Action has dispatched successfully after the promise resolves'
);
await click('[data-test-button]'); await click('[data-test-button]');
assert assert
.dom('[data-test-div]') .dom('[data-test-div]')
.doesNotExist('Aftering clicking the button, again, the state is reset'); .doesNotExist(
'Aftering clicking the button, again, the state is reset'
);
assert assert
.dom('[data-test-div-loading]') .dom('[data-test-div-loading]')
.exists('After clicking the button, again, we are back in the loading state'); .exists(
'After clicking the button, again, we are back in the loading state'
);
this.resolve(); this.resolve();
await waitFor('[data-test-div]'); await waitFor('[data-test-div]');
@ -111,11 +133,11 @@ module('Integration | Component | trigger', function(hooks) {
); );
}); });
test('it handles the success state', async function(assert) { test('it handles the success state', async function (assert) {
this.set( this.set(
'onTrigger', 'onTrigger',
() => () =>
new Promise(resolve => { new Promise((resolve) => {
this.set('resolve', resolve); this.set('resolve', resolve);
}) })
); );
@ -132,14 +154,16 @@ module('Integration | Component | trigger', function(hooks) {
assert assert
.dom('[data-test-div]') .dom('[data-test-div]')
.doesNotExist('No text should appear until after the onSuccess callback is fired'); .doesNotExist(
'No text should appear until after the onSuccess callback is fired'
);
await click('[data-test-button]'); await click('[data-test-button]');
this.resolve(); this.resolve();
await waitFor('[data-test-div]'); await waitFor('[data-test-div]');
assert.verifySteps(['On success happened']); assert.verifySteps(['On success happened']);
}); });
test('it handles the error state', async function(assert) { test('it handles the error state', async function (assert) {
this.set( this.set(
'onTrigger', 'onTrigger',
() => () =>
@ -166,11 +190,15 @@ module('Integration | Component | trigger', function(hooks) {
await click('[data-test-button]'); await click('[data-test-button]');
assert assert
.dom('[data-test-div-loading]') .dom('[data-test-div-loading]')
.exists('Loading state is displayed when the action hasnt resolved yet'); .exists(
'Loading state is displayed when the action hasnt resolved yet'
);
assert assert
.dom('[data-test-div]') .dom('[data-test-div]')
.doesNotExist('No text should appear until after the onError callback is fired'); .doesNotExist(
'No text should appear until after the onError callback is fired'
);
this.reject(); this.reject();
await waitFor('[data-test-span]'); await waitFor('[data-test-span]');
@ -180,7 +208,9 @@ module('Integration | Component | trigger', function(hooks) {
assert assert
.dom('[data-test-div-loading]') .dom('[data-test-div-loading]')
.exists('The previous error state was cleared and we show loading, again.'); .exists(
'The previous error state was cleared and we show loading, again.'
);
assert.dom('[data-test-div]').doesNotExist('The error state is cleared'); assert.dom('[data-test-div]').doesNotExist('The error state is cleared');

View file

@ -7,7 +7,7 @@ import {
isPresent, isPresent,
property, property,
text, text,
visitable visitable,
} from 'ember-cli-page-object'; } from 'ember-cli-page-object';
import allocations from 'nomad-ui/tests/pages/components/allocations'; import allocations from 'nomad-ui/tests/pages/components/allocations';
@ -22,7 +22,7 @@ export default create({
tabs: collection('[data-test-tab]', { tabs: collection('[data-test-tab]', {
id: attribute('data-test-tab'), id: attribute('data-test-tab'),
visit: clickable('a') visit: clickable('a'),
}), }),
tabFor(id) { tabFor(id) {
@ -44,22 +44,22 @@ export default create({
scope: '[data-test-exec-button]', scope: '[data-test-exec-button]',
isDisabled: property('disabled'), isDisabled: property('disabled'),
hasTooltip: hasClass('tooltip'), hasTooltip: hasClass('tooltip'),
tooltipText: attribute('aria-label') tooltipText: attribute('aria-label'),
}, },
incrementButton: { incrementButton: {
scope: '[data-test-scale-controls-increment]', scope: '[data-test-scale-controls-increment]',
isDisabled: property('disabled') isDisabled: property('disabled'),
}, },
dispatchButton: { dispatchButton: {
scope: '[data-test-dispatch-button]', scope: '[data-test-dispatch-button]',
isDisabled: property('disabled') isDisabled: property('disabled'),
}, },
stats: collection('[data-test-job-stat]', { stats: collection('[data-test-job-stat]', {
id: attribute('data-test-job-stat'), id: attribute('data-test-job-stat'),
text: text() text: text(),
}), }),
statFor(id) { statFor(id) {
@ -68,7 +68,7 @@ export default create({
packStats: collection('[data-test-pack-stat]', { packStats: collection('[data-test-pack-stat]', {
id: attribute('data-test-pack-stat'), id: attribute('data-test-pack-stat'),
text: text() text: text(),
}), }),
packStatFor(id) { packStatFor(id) {
@ -82,8 +82,8 @@ export default create({
scope: '[data-test-accordion-head] [data-test-accordion-toggle]', scope: '[data-test-accordion-head] [data-test-accordion-toggle]',
click: clickable(), click: clickable(),
isDisabled: attribute('disabled'), isDisabled: attribute('disabled'),
tooltip: attribute('aria-label') tooltip: attribute('aria-label'),
} },
}, },
childrenSummary: jobClientStatusBar( childrenSummary: jobClientStatusBar(
'[data-test-job-summary] [data-test-children-status-bar]' '[data-test-job-summary] [data-test-children-status-bar]'
@ -98,7 +98,7 @@ export default create({
jobsHeader: { jobsHeader: {
scope: '[data-test-jobs-header]', scope: '[data-test-jobs-header]',
hasSubmitTime: isPresent('[data-test-jobs-submit-time-header]'), hasSubmitTime: isPresent('[data-test-jobs-submit-time-header]'),
hasNamespace: isPresent('[data-test-jobs-namespace-header]') hasNamespace: isPresent('[data-test-jobs-namespace-header]'),
}, },
jobs: collection('[data-test-job-row]', { jobs: collection('[data-test-job-row]', {
@ -113,17 +113,17 @@ export default create({
taskGroups: text('[data-test-job-task-groups]'), taskGroups: text('[data-test-job-task-groups]'),
clickRow: clickable(), clickRow: clickable(),
clickName: clickable('[data-test-job-name] a') clickName: clickable('[data-test-job-name] a'),
}), }),
error: { error: {
isPresent: isPresent('[data-test-error]'), isPresent: isPresent('[data-test-error]'),
title: text('[data-test-error-title]'), title: text('[data-test-error-title]'),
message: text('[data-test-error-message]'), message: text('[data-test-error-message]'),
seekHelp: clickable('[data-test-error-message] a') seekHelp: clickable('[data-test-error-message] a'),
}, },
recentAllocationsEmptyState: { recentAllocationsEmptyState: {
headline: text('[data-test-empty-recent-allocations-headline]') headline: text('[data-test-empty-recent-allocations-headline]'),
} },
}); });

View file

@ -4,11 +4,11 @@ import { setupTest } from 'ember-qunit';
import Service from '@ember/service'; import Service from '@ember/service';
import setupAbility from 'nomad-ui/tests/helpers/setup-ability'; import setupAbility from 'nomad-ui/tests/helpers/setup-ability';
module('Unit | Ability | client', function(hooks) { module('Unit | Ability | client', function (hooks) {
setupTest(hooks); setupTest(hooks);
setupAbility('client')(hooks); setupAbility('client')(hooks);
test('it permits client read and write when ACLs are disabled', function(assert) { test('it permits client read and write when ACLs are disabled', function (assert) {
const mockToken = Service.extend({ const mockToken = Service.extend({
aclEnabled: false, aclEnabled: false,
}); });
@ -18,7 +18,7 @@ module('Unit | Ability | client', function(hooks) {
assert.ok(this.ability.canWrite); assert.ok(this.ability.canWrite);
}); });
test('it permits client read and write for management tokens', function(assert) { test('it permits client read and write for management tokens', function (assert) {
const mockToken = Service.extend({ const mockToken = Service.extend({
aclEnabled: true, aclEnabled: true,
selfToken: { type: 'management' }, selfToken: { type: 'management' },
@ -29,7 +29,7 @@ module('Unit | Ability | client', function(hooks) {
assert.ok(this.ability.canWrite); assert.ok(this.ability.canWrite);
}); });
test('it permits client read and write for tokens with a policy that has node-write', function(assert) { test('it permits client read and write for tokens with a policy that has node-write', function (assert) {
const mockToken = Service.extend({ const mockToken = Service.extend({
aclEnabled: true, aclEnabled: true,
selfToken: { type: 'client' }, selfToken: { type: 'client' },
@ -49,7 +49,7 @@ module('Unit | Ability | client', function(hooks) {
assert.ok(this.ability.canWrite); assert.ok(this.ability.canWrite);
}); });
test('it permits client read and write for tokens with a policy that allows write and another policy that disallows it', function(assert) { test('it permits client read and write for tokens with a policy that allows write and another policy that disallows it', function (assert) {
const mockToken = Service.extend({ const mockToken = Service.extend({
aclEnabled: true, aclEnabled: true,
selfToken: { type: 'client' }, selfToken: { type: 'client' },
@ -76,7 +76,7 @@ module('Unit | Ability | client', function(hooks) {
assert.ok(this.ability.canWrite); assert.ok(this.ability.canWrite);
}); });
test('it permits client read and blocks client write for tokens with a policy that does not allow node-write', function(assert) { test('it permits client read and blocks client write for tokens with a policy that does not allow node-write', function (assert) {
const mockToken = Service.extend({ const mockToken = Service.extend({
aclEnabled: true, aclEnabled: true,
selfToken: { type: 'client' }, selfToken: { type: 'client' },
@ -96,7 +96,7 @@ module('Unit | Ability | client', function(hooks) {
assert.notOk(this.ability.canWrite); assert.notOk(this.ability.canWrite);
}); });
test('it blocks client read and write for tokens without a node policy', function(assert) { test('it blocks client read and write for tokens without a node policy', function (assert) {
const mockToken = Service.extend({ const mockToken = Service.extend({
aclEnabled: true, aclEnabled: true,
selfToken: { type: 'client' }, selfToken: { type: 'client' },

View file

@ -51,8 +51,8 @@ class AllocationMock {
} }
} }
module('Unit | Util | JobClientStatus', function() { module('Unit | Util | JobClientStatus', function () {
test('it handles the case where all nodes are running', async function(assert) { test('it handles the case where all nodes are running', async function (assert) {
const node = new NodeMock('node-1', 'dc1'); const node = new NodeMock('node-1', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -84,7 +84,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it handles the degraded case where a node has a failing allocation', async function(assert) { test('it handles the degraded case where a node has a failing allocation', async function (assert) {
const node = new NodeMock('node-2', 'dc1'); const node = new NodeMock('node-2', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -120,7 +120,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it handles the case where a node has all lost allocations', async function(assert) { test('it handles the case where a node has all lost allocations', async function (assert) {
const node = new NodeMock('node-1', 'dc1'); const node = new NodeMock('node-1', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -156,7 +156,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it handles the case where a node has all failed allocations', async function(assert) { test('it handles the case where a node has all failed allocations', async function (assert) {
const node = new NodeMock('node-1', 'dc1'); const node = new NodeMock('node-1', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -192,7 +192,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it handles the degraded case where the expected number of allocations doesnt match the actual number of allocations', async function(assert) { test('it handles the degraded case where the expected number of allocations doesnt match the actual number of allocations', async function (assert) {
const node = new NodeMock('node-1', 'dc1'); const node = new NodeMock('node-1', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -228,7 +228,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it handles the not scheduled case where a node has no allocations', async function(assert) { test('it handles the not scheduled case where a node has no allocations', async function (assert) {
const node = new NodeMock('node-1', 'dc1'); const node = new NodeMock('node-1', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -260,7 +260,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it handles the queued case where the job is pending', async function(assert) { test('it handles the queued case where the job is pending', async function (assert) {
const node = new NodeMock('node-1', 'dc1'); const node = new NodeMock('node-1', 'dc1');
const nodes = [node]; const nodes = [node];
const job = { const job = {
@ -296,7 +296,7 @@ module('Unit | Util | JobClientStatus', function() {
assert.deepEqual(result, expected); assert.deepEqual(result, expected);
}); });
test('it filters nodes by the datacenter of the job', async function(assert) { test('it filters nodes by the datacenter of the job', async function (assert) {
const node1 = new NodeMock('node-1', 'dc1'); const node1 = new NodeMock('node-1', 'dc1');
const node2 = new NodeMock('node-2', 'dc2'); const node2 = new NodeMock('node-2', 'dc2');
const nodes = [node1, node2]; const nodes = [node1, node2];