Merge pull request #9167 from hashicorp/f-ui/topo-viz-refinements
UI: topo viz refinements
This commit is contained in:
commit
4a36f522ce
|
@ -15,16 +15,26 @@ export default class FlexMasonry extends Component {
|
|||
@action
|
||||
reflow() {
|
||||
run.next(() => {
|
||||
// There's nothing to do if this is a single column layout
|
||||
if (!this.element || this.args.columns === 1 || !this.args.columns) return;
|
||||
// There's nothing to do if there is no element
|
||||
if (!this.element) return;
|
||||
|
||||
const items = this.element.querySelectorAll(':scope > .flex-masonry-item');
|
||||
|
||||
// Clear out specified order and flex-basis values in case this was once a multi-column layout
|
||||
if (this.args.columns === 1 || !this.args.columns) {
|
||||
for (let item of items) {
|
||||
item.style.flexBasis = null;
|
||||
item.style.order = null;
|
||||
}
|
||||
this.element.style.maxHeight = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const columns = new Array(this.args.columns).fill(null).map(() => ({
|
||||
height: 0,
|
||||
elements: [],
|
||||
}));
|
||||
|
||||
const items = this.element.querySelectorAll('.flex-masonry-item');
|
||||
|
||||
// First pass: assign each element to a column based on the running heights of each column
|
||||
for (let item of items) {
|
||||
const styles = window.getComputedStyle(item);
|
||||
|
|
|
@ -14,9 +14,10 @@ export default class TopoViz extends Component {
|
|||
@tracked activeAllocation = null;
|
||||
@tracked activeEdges = [];
|
||||
@tracked edgeOffset = { x: 0, y: 0 };
|
||||
@tracked viewportColumns = 2;
|
||||
|
||||
get isSingleColumn() {
|
||||
if (this.topology.datacenters.length <= 1) return true;
|
||||
if (this.topology.datacenters.length <= 1 || this.viewportColumns === 1) return true;
|
||||
|
||||
// Compute the coefficient of variance to determine if it would be
|
||||
// better to stack datacenters or place them in columns
|
||||
|
@ -32,6 +33,7 @@ export default class TopoViz extends Component {
|
|||
get datacenterIsSingleColumn() {
|
||||
// If there are enough nodes, use two columns of nodes within
|
||||
// a single column layout of datacenters to increase density.
|
||||
if (this.viewportColumns === 1) return true;
|
||||
return !this.isSingleColumn || (this.isSingleColumn && this.args.nodes.length <= 20);
|
||||
}
|
||||
|
||||
|
@ -124,6 +126,7 @@ export default class TopoViz extends Component {
|
|||
@action
|
||||
captureElement(element) {
|
||||
this.element = element;
|
||||
this.determineViewportColumns();
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -177,13 +180,23 @@ export default class TopoViz extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
this.computedActiveEdges();
|
||||
// Only show the lines if the selected allocations are sparse (low count relative to the client count).
|
||||
if (newAllocations.length < this.args.nodes.length * 0.75) {
|
||||
this.computedActiveEdges();
|
||||
} else {
|
||||
this.activeEdges = [];
|
||||
}
|
||||
}
|
||||
if (this.args.onAllocationSelect)
|
||||
this.args.onAllocationSelect(this.activeAllocation && this.activeAllocation.allocation);
|
||||
if (this.args.onNodeSelect) this.args.onNodeSelect(this.activeNode);
|
||||
}
|
||||
|
||||
@action
|
||||
determineViewportColumns() {
|
||||
this.viewportColumns = this.element.clientWidth < 900 ? 1 : 2;
|
||||
}
|
||||
|
||||
@action
|
||||
computedActiveEdges() {
|
||||
// Wait a render cycle
|
||||
|
|
|
@ -49,7 +49,7 @@ export default class Allocation extends Model {
|
|||
|
||||
@computed('clientStatus')
|
||||
get isScheduled() {
|
||||
return ['pending', 'running', 'failed'].includes(this.clientStatus);
|
||||
return ['pending', 'running'].includes(this.clientStatus);
|
||||
}
|
||||
|
||||
// An allocation model created from any allocation list response will be lacking
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
.bar {
|
||||
cursor: pointer;
|
||||
|
||||
&.is-selected {
|
||||
&.is-selected,
|
||||
&.is-selected .layer-0 {
|
||||
stroke-width: 1px;
|
||||
stroke: $blue;
|
||||
fill: $blue-light;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
.dashboard-metric {
|
||||
width: 350px;
|
||||
max-width: 350px;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
@ -7,6 +10,11 @@
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.column {
|
||||
width: auto;
|
||||
max-width: auto;
|
||||
}
|
||||
|
||||
.metric {
|
||||
text-align: left;
|
||||
font-weight: $weight-bold;
|
||||
|
@ -40,6 +48,14 @@
|
|||
padding-top: 1.5em;
|
||||
}
|
||||
|
||||
.pair,
|
||||
.minor-pair {
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pair {
|
||||
font-size: $size-5;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class="flex-masonry {{if @withSpacing "with-spacing"}} flex-masonry-columns-{{@columns}}"
|
||||
{{did-insert this.captureElement}}
|
||||
{{did-insert this.reflow}}
|
||||
{{did-update this.reflow}}
|
||||
{{did-update this.reflow @columns}}
|
||||
{{window-resize this.reflow}}>
|
||||
{{#each @items as |item|}}
|
||||
<div data-test-flex-masonry-item class="flex-masonry-item">
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
<div data-test-topo-viz class="topo-viz {{if this.isSingleColumn "is-single-column"}}" {{did-insert this.buildTopology}} {{did-insert this.captureElement}}>
|
||||
<div
|
||||
data-test-topo-viz
|
||||
class="topo-viz {{if this.isSingleColumn "is-single-column"}}"
|
||||
{{did-insert this.buildTopology}}
|
||||
{{did-insert this.captureElement}}
|
||||
{{window-resize this.determineViewportColumns}}>
|
||||
<FlexMasonry
|
||||
@columns={{if this.isSingleColumn 1 2}}
|
||||
@items={{this.topology.datacenters}}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<PageLayout>
|
||||
<section class="section is-full-width">
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter">
|
||||
<div class="column is-narrow">
|
||||
<div class="boxed-section">
|
||||
<div class="boxed-section-head">Legend</div>
|
||||
<div class="boxed-section-body">
|
||||
|
@ -17,7 +17,6 @@
|
|||
<h3 class="legend-label">Allocation Status</h3>
|
||||
<dl class="legend-terms">
|
||||
<div class="legend-term"><dt><span class="color-swatch is-wide running" title="Running" /></dt><dd>Running</dd></div>
|
||||
<div class="legend-term"><dt><span class="color-swatch is-wide failed" title="Failed" /></dt><dd>Failed</dd></div>
|
||||
<div class="legend-term"><dt><span class="color-swatch is-wide pending" title="Starting" /></dt><dd>Starting</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
|
@ -40,9 +39,9 @@
|
|||
{{node.shortId}}
|
||||
</LinkTo>
|
||||
</h3>
|
||||
<p><strong>Name:</strong> {{node.name}}</p>
|
||||
<p><strong>Address:</strong> {{node.httpAddr}}</p>
|
||||
<p><strong>Status:</strong> {{node.status}}</p>
|
||||
<p class="minor-pair"><strong>Name:</strong> {{node.name}}</p>
|
||||
<p class="minor-pair"><strong>Address:</strong> {{node.httpAddr}}</p>
|
||||
<p class="minor-pair"><strong>Status:</strong> {{node.status}}</p>
|
||||
</div>
|
||||
<div class="dashboard-metric">
|
||||
<h3 class="pair">
|
||||
|
@ -105,8 +104,8 @@
|
|||
<strong>Allocation:</strong>
|
||||
<LinkTo @route="allocations.allocation" @model={{this.activeAllocation}} class="is-primary">{{this.activeAllocation.shortId}}</LinkTo>
|
||||
</h3>
|
||||
<p><strong>Sibling Allocations:</strong> {{this.siblingAllocations.length}}</p>
|
||||
<p><strong>Unique Client Placements:</strong> {{this.uniqueActiveAllocationNodes.length}}</p>
|
||||
<p class="minor-pair"><strong>Sibling Allocations:</strong> {{this.siblingAllocations.length}}</p>
|
||||
<p class="minor-pair"><strong>Unique Client Placements:</strong> {{this.uniqueActiveAllocationNodes.length}}</p>
|
||||
</div>
|
||||
<div class="dashboard-metric with-divider">
|
||||
<h3 class="pair">
|
||||
|
@ -118,8 +117,8 @@
|
|||
{{this.activeAllocation.job.name}}</LinkTo>
|
||||
<span class="is-faded" data-test-task-group> / {{this.activeAllocation.taskGroupName}}</span>
|
||||
</h3>
|
||||
<p><strong>Type:</strong> {{this.activeAllocation.job.type}}</p>
|
||||
<p><strong>Priority:</strong> {{this.activeAllocation.job.priority}}</p>
|
||||
<p class="minor-pair"><strong>Type:</strong> {{this.activeAllocation.job.type}}</p>
|
||||
<p class="minor-pair"><strong>Priority:</strong> {{this.activeAllocation.job.priority}}</p>
|
||||
</div>
|
||||
<div class="dashboard-metric with-divider">
|
||||
<h3 class="pair">
|
||||
|
@ -128,8 +127,8 @@
|
|||
{{this.activeAllocation.node.shortId}}
|
||||
</LinkTo>
|
||||
</h3>
|
||||
<p><strong>Name:</strong> {{this.activeAllocation.node.name}}</p>
|
||||
<p><strong>Address:</strong> {{this.activeAllocation.node.httpAddr}}</p>
|
||||
<p class="minor-pair"><strong>Name:</strong> {{this.activeAllocation.node.name}}</p>
|
||||
<p class="minor-pair"><strong>Address:</strong> {{this.activeAllocation.node.httpAddr}}</p>
|
||||
</div>
|
||||
<div class="dashboard-metric with-divider">
|
||||
<PrimaryMetric @resource={{this.activeAllocation}} @metric="memory" class="is-short" />
|
||||
|
|
|
@ -165,4 +165,38 @@ module('Integration | Component | FlexMasonry', function(hooks) {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('when a multi-column layout becomes a single column layout, all inline-styles are reset', async function(assert) {
|
||||
this.setProperties({
|
||||
items: [
|
||||
{ text: 'One', height: h(20) },
|
||||
{ text: 'Two', height: h(100) },
|
||||
{ text: 'Three', height: h(20) },
|
||||
{ text: 'Four', height: h(100) },
|
||||
{ text: 'Five', height: h(20) },
|
||||
{ text: 'Six', height: h(20) },
|
||||
],
|
||||
columns: 4,
|
||||
});
|
||||
|
||||
await this.render(hbs`
|
||||
<FlexMasonry
|
||||
@items={{this.items}}
|
||||
@columns={{this.columns}} as |item|>
|
||||
<div style={{item.height}}>{{item.text}}</div>
|
||||
</FlexMasonry>
|
||||
`);
|
||||
|
||||
assert.equal(find('[data-test-flex-masonry]').style.maxHeight, '101px');
|
||||
|
||||
this.set('columns', 1);
|
||||
await settled();
|
||||
|
||||
findAll('[data-test-flex-masonry-item]').forEach(el => {
|
||||
assert.equal(el.style.flexBasis, '');
|
||||
assert.equal(el.style.order, '');
|
||||
});
|
||||
|
||||
assert.equal(find('[data-test-flex-masonry]').style.maxHeight, '');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -110,7 +110,10 @@ module('Integration | Component | TopoViz', function(hooks) {
|
|||
nodes: [
|
||||
node('dc1', 'node0', 1000, 500),
|
||||
node('dc1', 'node1', 1000, 500),
|
||||
node('dc2', 'node2', 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),
|
||||
|
@ -141,4 +144,25 @@ module('Integration | Component | TopoViz', function(hooks) {
|
|||
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: [
|
||||
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(),
|
||||
});
|
||||
|
||||
await this.render(commonTemplate);
|
||||
assert.notOk(TopoViz.allocationAssociationsArePresent);
|
||||
|
||||
await TopoViz.datacenters[0].nodes[0].memoryRects[0].select();
|
||||
assert.equal(TopoViz.allocationAssociations.length, 0);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue