open-nomad/ui/tests/integration/components/topo-viz-test.js
Michael Lange 7a9a208c89 Adjust the no-association-lines logic
On very small clusters, the node count heuristic is impractical and
leads to confusion. By additionally requiring 10+ sibling allocs, the
lines will be shown more often.
2021-01-27 10:24:03 -08:00

221 lines
8 KiB
JavaScript

import { module, test } from 'qunit';
import { triggerEvent } from '@ember/test-helpers';
import { setupRenderingTest } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
import { create } from 'ember-cli-page-object';
import { setupMirage } from 'ember-cli-mirage/test-support';
import sinon from 'sinon';
import faker from 'nomad-ui/mirage/faker';
import topoVizPageObject from 'nomad-ui/tests/pages/components/topo-viz';
const TopoViz = create(topoVizPageObject());
const alloc = (nodeId, jobId, taskGroupName, memory, cpu, props = {}) => ({
id: faker.random.uuid(),
taskGroupName,
isScheduled: true,
allocatedResources: {
cpu,
memory,
},
belongsTo: type => ({
id: () => (type === 'job' ? jobId : nodeId),
}),
...props,
});
const node = (datacenter, id, memory, cpu) => ({
datacenter,
id,
resources: { memory, cpu },
});
module('Integration | Component | TopoViz', function(hooks) {
setupRenderingTest(hooks);
setupMirage(hooks);
const commonTemplate = hbs`
<TopoViz
@nodes={{this.nodes}}
@allocations={{this.allocations}}
@onAllocationSelect={{this.onAllocationSelect}}
@onNodeSelect={{this.onNodeSelect}}
@onDataError={{this.onDataError}} />
`;
test('presents as a FlexMasonry of datacenters', async function(assert) {
this.setProperties({
nodes: [node('dc1', 'node0', 1000, 500), node('dc2', 'node1', 1000, 500)],
allocations: [
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
],
});
await this.render(commonTemplate);
assert.equal(TopoViz.datacenters.length, 2);
assert.equal(TopoViz.datacenters[0].nodes.length, 1);
assert.equal(TopoViz.datacenters[1].nodes.length, 1);
assert.equal(TopoViz.datacenters[0].nodes[0].memoryRects.length, 2);
assert.equal(TopoViz.datacenters[1].nodes[0].memoryRects.length, 1);
await componentA11yAudit(this.element, assert);
});
test('clicking on a node in a deeply nested TopoViz::Node will toggle node selection and call @onNodeSelect', async function(assert) {
this.setProperties({
// TopoViz must be dense for node selection to be a feature
nodes: Array(55)
.fill(null)
.map((_, index) => node('dc1', `node${index}`, 1000, 500)),
allocations: [],
onNodeSelect: sinon.spy(),
});
await this.render(commonTemplate);
await TopoViz.datacenters[0].nodes[0].selectNode();
assert.ok(this.onNodeSelect.calledOnce);
assert.equal(this.onNodeSelect.getCall(0).args[0].node, this.nodes[0]);
await TopoViz.datacenters[0].nodes[0].selectNode();
assert.ok(this.onNodeSelect.calledTwice);
assert.equal(this.onNodeSelect.getCall(1).args[0], null);
});
test('clicking on an allocation in a deeply nested TopoViz::Node will update the topology object with selections and call @onAllocationSelect and @onNodeSelect', async function(assert) {
this.setProperties({
nodes: [node('dc1', 'node0', 1000, 500)],
allocations: [alloc('node0', 'job1', 'group', 100, 100)],
onNodeSelect: sinon.spy(),
onAllocationSelect: sinon.spy(),
});
await this.render(commonTemplate);
await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.ok(this.onAllocationSelect.calledOnce);
assert.equal(this.onAllocationSelect.getCall(0).args[0], this.allocations[0]);
assert.ok(this.onNodeSelect.calledOnce);
await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.ok(this.onAllocationSelect.calledTwice);
assert.equal(this.onAllocationSelect.getCall(1).args[0], null);
assert.ok(this.onNodeSelect.calledTwice);
assert.ok(this.onNodeSelect.alwaysCalledWith(null));
});
test('clicking on an allocation in a deeply nested TopoViz::Node will associate sibling allocations with curves', async function(assert) {
this.setProperties({
nodes: [
node('dc1', 'node0', 1000, 500),
node('dc1', 'node1', 1000, 500),
node('dc1', 'node2', 1000, 500),
node('dc2', 'node3', 1000, 500),
node('dc2', 'node4', 1000, 500),
node('dc2', 'node5', 1000, 500),
],
allocations: [
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node2', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'groupTwo', 100, 100),
alloc('node1', 'job2', 'group', 100, 100),
alloc('node2', 'job2', 'groupTwo', 100, 100),
],
onNodeSelect: sinon.spy(),
onAllocationSelect: sinon.spy(),
});
const selectedAllocations = this.allocations.filter(
alloc => alloc.belongsTo('job').id() === 'job1' && alloc.taskGroupName === 'group'
);
await this.render(commonTemplate);
assert.notOk(TopoViz.allocationAssociationsArePresent);
await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.ok(TopoViz.allocationAssociationsArePresent);
assert.equal(TopoViz.allocationAssociations.length, selectedAllocations.length * 2);
// Lines get redrawn when the window resizes; make sure the lines persist.
await triggerEvent(window, 'resize');
assert.equal(TopoViz.allocationAssociations.length, selectedAllocations.length * 2);
await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.notOk(TopoViz.allocationAssociationsArePresent);
});
test('when the count of sibling allocations is high enough relative to the node count, curves are not rendered', async function(assert) {
this.setProperties({
nodes: [node('dc1', 'node0', 1000, 500), node('dc1', 'node1', 1000, 500)],
allocations: [
// There need to be at least 10 sibling allocations to trigger this behavior
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'groupTwo', 100, 100),
],
onNodeSelect: sinon.spy(),
onAllocationSelect: sinon.spy(),
});
await this.render(commonTemplate);
assert.notOk(TopoViz.allocationAssociationsArePresent);
await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
assert.equal(TopoViz.allocationAssociations.length, 0);
// Lines get redrawn when the window resizes; make sure that doesn't make the lines show up again
await triggerEvent(window, 'resize');
assert.equal(TopoViz.allocationAssociations.length, 0);
});
test('when one or more nodes are missing the resources property, those nodes are filtered out of the topology view and onDataError is called', async function(assert) {
const badNode = node('dc1', 'node0', 1000, 500);
delete badNode.resources;
this.setProperties({
nodes: [badNode, node('dc1', 'node1', 1000, 500)],
allocations: [
alloc('node0', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node1', 'job1', 'group', 100, 100),
alloc('node0', 'job1', 'groupTwo', 100, 100),
],
onNodeSelect: sinon.spy(),
onAllocationSelect: sinon.spy(),
onDataError: sinon.spy(),
});
await this.render(commonTemplate);
assert.ok(this.onDataError.calledOnce);
assert.deepEqual(this.onDataError.getCall(0).args[0], [
{
type: 'filtered-nodes',
context: [this.nodes[0]],
},
]);
assert.equal(TopoViz.datacenters[0].nodes.length, 1);
});
});