Skip to content

Commit

Permalink
Test coverage for TopoViz::Datacenter
Browse files Browse the repository at this point in the history
  • Loading branch information
DingoEatingFuzz committed Oct 14, 2020
1 parent 25f1f2d commit 28a7cc2
Show file tree
Hide file tree
Showing 8 changed files with 184 additions and 22 deletions.
1 change: 0 additions & 1 deletion ui/app/components/topo-viz.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action, set } from '@ember/object';
import { run } from '@ember/runloop';
import { task } from 'ember-concurrency';
import { scaleLinear } from 'd3-scale';
import { extent, deviation, mean } from 'd3-array';
import { line, curveBasis } from 'd3-shape';
Expand Down
22 changes: 8 additions & 14 deletions ui/app/components/topo-viz/datacenter.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';

export default class TopoVizDatacenter extends Component {
@tracked scheduledAllocations = [];
@tracked aggregatedNodeResources = { cpu: 0, memory: 0 };
get scheduledAllocations() {
return this.args.datacenter.nodes.reduce(
(all, node) => all.concat(node.allocations.filterBy('allocation.isScheduled')),
[]
);
}

get aggregatedAllocationResources() {
return this.scheduledAllocations.reduce(
Expand All @@ -17,22 +19,14 @@ export default class TopoVizDatacenter extends Component {
);
}

@action
loadAllocations() {
this.scheduledAllocations = this.args.datacenter.nodes.reduce(
(all, node) => all.concat(node.allocations.filterBy('allocation.isScheduled')),
[]
);

this.aggregatedNodeResources = this.args.datacenter.nodes.reduce(
get aggregatedNodeResources() {
return this.args.datacenter.nodes.reduce(
(totals, node) => {
totals.cpu += node.cpu;
totals.memory += node.memory;
return totals;
},
{ cpu: 0, memory: 0 }
);

this.args.onLoad && this.args.onLoad();
}
}
5 changes: 2 additions & 3 deletions ui/app/templates/components/topo-viz.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
<FlexMasonry
@columns={{if this.isSingleColumn 1 2}}
@items={{this.topology.datacenters}}
@withSpacing={{true}} as |dc reflow|>
@withSpacing={{true}} as |dc|>
<TopoViz::Datacenter
@datacenter={{dc}}
@isSingleColumn={{this.datacenterIsSingleColumn}}
@isDense={{this.isDense}}
@heightScale={{this.topology.heightScale}}
@onAllocationSelect={{this.associateAllocations}}
@onNodeSelect={{this.showNodeDetails}}
@onLoad={{action reflow}}/>
@onNodeSelect={{this.showNodeDetails}} />
</FlexMasonry>

{{#if this.activeAllocation}}
Expand Down
4 changes: 2 additions & 2 deletions ui/app/templates/components/topo-viz/datacenter.hbs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="boxed-section topo-viz-datacenter" {{did-insert this.loadAllocations}}>
<div class="boxed-section-head is-hollow">
<div data-test-topo-viz-datacenter class="boxed-section topo-viz-datacenter">
<div data-test-topo-viz-datacenter-label class="boxed-section-head is-hollow">
<strong>{{@datacenter.name}}</strong>
<span class="bumper-left">{{this.scheduledAllocations.length}} Allocs</span>
<span class="bumper-left">{{@datacenter.nodes.length}} Nodes</span>
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/topo-viz/node.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div data-topo-viz-node class="topo-viz-node {{unless this.allocations.length "is-empty"}}" {{did-insert this.reloadNode}}>
<div data-test-topo-viz-node class="topo-viz-node {{unless this.allocations.length "is-empty"}}" {{did-insert this.reloadNode}}>
{{#unless @isDense}}
<p data-test-label class="label">
{{#if @node.node.isDraining}}
Expand Down
160 changes: 160 additions & 0 deletions ui/tests/integration/components/topo-viz/datacenter-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { find } from '@ember/test-helpers';
import { module, test } from 'qunit';
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 sinon from 'sinon';
import faker from 'nomad-ui/mirage/faker';
import topoVizDatacenterPageObject from 'nomad-ui/tests/pages/components/topo-viz/datacenter';

const TopoVizDatacenter = create(topoVizDatacenterPageObject());

const nodeGen = (name, datacenter, memory, cpu, allocations = []) => ({
datacenter,
memory,
cpu,
node: { name },
allocations: allocations.map(alloc => ({
memory: alloc.memory,
cpu: alloc.cpu,
memoryPercent: alloc.memory / memory,
cpuPercent: alloc.cpu / cpu,
allocation: {
id: faker.random.uuid(),
isScheduled: true,
},
})),
});

// Used in Array#reduce to sum by a property common to an array of objects
const sumBy = prop => (sum, obj) => (sum += obj[prop]);

module('Integration | Component | TopoViz::Datacenter', function(hooks) {
setupRenderingTest(hooks);

const commonProps = props => ({
isSingleColumn: true,
isDense: false,
heightScale: () => 50,
onAllocationSelect: sinon.spy(),
onNodeSelect: sinon.spy(),
...props,
});

const commonTemplate = hbs`
<TopoViz::Datacenter
@datacenter={{this.datacenter}}
@isSingleColumn={{this.isSingleColumn}}
@isDense={{this.isDense}}
@heightScale={{this.heightScale}}
@onAllocationSelect={{this.onAllocationSelect}}
@onNodeSelect={{this.onNodeSelect}} />
`;

test('presents as a div with a label and a FlexMasonry with a collection of nodes', async function(assert) {
this.setProperties(
commonProps({
datacenter: {
name: 'dc1',
nodes: [nodeGen('node-1', 'dc1', 1000, 500)],
},
})
);

await this.render(commonTemplate);

assert.ok(TopoVizDatacenter.isPresent);
assert.equal(TopoVizDatacenter.nodes.length, this.datacenter.nodes.length);

await componentA11yAudit(this.element, assert);
});

test('datacenter stats are an aggregate of node stats', async function(assert) {
this.setProperties(
commonProps({
datacenter: {
name: 'dc1',
nodes: [
nodeGen('node-1', 'dc1', 1000, 500, [
{ memory: 100, cpu: 300 },
{ memory: 200, cpu: 50 },
]),
nodeGen('node-2', 'dc1', 1500, 100, [
{ memory: 50, cpu: 80 },
{ memory: 100, cpu: 20 },
]),
nodeGen('node-3', 'dc1', 2000, 300),
nodeGen('node-4', 'dc1', 3000, 200),
],
},
})
);

await this.render(commonTemplate);

const allocs = this.datacenter.nodes.reduce(
(allocs, node) => allocs.concat(node.allocations),
[]
);
const memoryReserved = allocs.reduce(sumBy('memory'), 0);
const cpuReserved = allocs.reduce(sumBy('cpu'), 0);
const memoryTotal = this.datacenter.nodes.reduce(sumBy('memory'), 0);
const cpuTotal = this.datacenter.nodes.reduce(sumBy('cpu'), 0);

assert.ok(TopoVizDatacenter.label.includes(this.datacenter.name));
assert.ok(TopoVizDatacenter.label.includes(`${this.datacenter.nodes.length} Nodes`));
assert.ok(TopoVizDatacenter.label.includes(`${allocs.length} Allocs`));
assert.ok(TopoVizDatacenter.label.includes(`${memoryReserved}/${memoryTotal} MiB`));
assert.ok(TopoVizDatacenter.label.includes(`${cpuReserved}/${cpuTotal} Mhz`));
});

test('when @isSingleColumn is true, the FlexMasonry layout gets one column, otherwise it gets two', async function(assert) {
this.setProperties(
commonProps({
isSingleColumn: true,
datacenter: {
name: 'dc1',
nodes: [nodeGen('node-1', 'dc1', 1000, 500), nodeGen('node-2', 'dc1', 1000, 500)],
},
})
);

await this.render(commonTemplate);

assert.ok(find('[data-test-flex-masonry].flex-masonry-columns-1'));

this.set('isSingleColumn', false);
assert.ok(find('[data-test-flex-masonry].flex-masonry-columns-2'));
});

test('args get passed down to the TopViz::Node children', async function(assert) {
const heightSpy = sinon.spy();
this.setProperties(
commonProps({
isDense: true,
heightScale: (...args) => {
heightSpy(...args);
return 50;
},
datacenter: {
name: 'dc1',
nodes: [nodeGen('node-1', 'dc1', 1000, 500, [{ memory: 100, cpu: 300 }])],
},
})
);

await this.render(commonTemplate);

TopoVizDatacenter.nodes[0].as(async TopoVizNode => {
assert.notOk(TopoVizNode.labelIsPresent);
assert.ok(heightSpy.calledWith(this.datacenter.nodes[0].memory));

await TopoVizNode.selectNode();
assert.ok(this.onNodeSelect.calledWith(this.datacenter.nodes[0]));

await TopoVizNode.memoryRects[0].select();
assert.ok(this.onAllocationSelect.calledWith(this.datacenter.nodes[0].allocations[0]));
});
});
});
9 changes: 9 additions & 0 deletions ui/tests/pages/components/topo-viz/datacenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { collection, text } from 'ember-cli-page-object';
import TopoVizNode from './node';

export default scope => ({
scope,

label: text('[data-test-topo-viz-datacenter-label]'),
nodes: collection('[data-test-topo-viz-node]', TopoVizNode()),
});
3 changes: 2 additions & 1 deletion ui/tests/pages/components/topo-viz/node.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { attribute, collection, clickable, hasClass, text } from 'ember-cli-page-object';
import { attribute, collection, clickable, hasClass, isPresent, text } from 'ember-cli-page-object';

const allocationRect = {
select: clickable(),
Expand All @@ -15,6 +15,7 @@ export default scope => ({
scope,

label: text('[data-test-label]'),
labelIsPresent: isPresent('[data-test-label]'),
statusIcon: attribute('class', '[data-test-status-icon] .icon'),
statusIconLabel: attribute('aria-label', '[data-test-status-icon]'),

Expand Down

0 comments on commit 28a7cc2

Please sign in to comment.