610 lines
17 KiB
JavaScript
610 lines
17 KiB
JavaScript
import { module, test } from 'qunit';
|
||
import { setupRenderingTest } from 'ember-qunit';
|
||
import { render, settled } from '@ember/test-helpers';
|
||
import { hbs } from 'ember-cli-htmlbars';
|
||
import Service from '@ember/service';
|
||
import { componentA11yAudit } from 'nomad-ui/tests/helpers/a11y-audit';
|
||
|
||
import RecommendationCardComponent from 'nomad-ui/tests/pages/components/recommendation-card';
|
||
import { create } from 'ember-cli-page-object';
|
||
const RecommendationCard = create(RecommendationCardComponent);
|
||
|
||
import { tracked } from '@glimmer/tracking';
|
||
import { action } from '@ember/object';
|
||
import { set } from '@ember/object';
|
||
|
||
module('Integration | Component | das/recommendation-card', function(hooks) {
|
||
setupRenderingTest(hooks);
|
||
|
||
hooks.beforeEach(function() {
|
||
const mockRouter = Service.extend({
|
||
init() {
|
||
this._super(...arguments);
|
||
},
|
||
|
||
urlFor(route, slug, { queryParams: { namespace } }) {
|
||
return `${route}:${slug}?namespace=${namespace}`;
|
||
},
|
||
});
|
||
|
||
this.owner.register('service:router', mockRouter);
|
||
});
|
||
|
||
test('it renders a recommendation card', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
const task2 = {
|
||
name: 'tortle',
|
||
reservedCPU: 125,
|
||
reservedMemory: 256,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
jobNamespace: 'namespace',
|
||
recommendations: [
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task1,
|
||
value: 192,
|
||
currentValue: task1.reservedMemory,
|
||
},
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
currentValue: task1.reservedCPU,
|
||
},
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task2,
|
||
value: 150,
|
||
currentValue: task2.reservedCPU,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task2,
|
||
value: 320,
|
||
currentValue: task2.reservedMemory,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 2,
|
||
name: 'group-name',
|
||
job: {
|
||
name: 'job-name',
|
||
namespace: {
|
||
name: 'namespace',
|
||
},
|
||
},
|
||
reservedCPU: task1.reservedCPU + task2.reservedCPU,
|
||
reservedMemory: task1.reservedMemory + task2.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
assert.equal(RecommendationCard.slug.jobName, 'job-name');
|
||
assert.equal(RecommendationCard.slug.groupName, 'group-name');
|
||
|
||
assert.equal(RecommendationCard.namespace, 'namespace');
|
||
|
||
assert.equal(RecommendationCard.totalsTable.current.cpu.text, '275 MHz');
|
||
assert.equal(RecommendationCard.totalsTable.current.memory.text, '384 MiB');
|
||
|
||
RecommendationCard.totalsTable.recommended.cpu.as(RecommendedCpu => {
|
||
assert.equal(RecommendedCpu.text, '200 MHz');
|
||
assert.ok(RecommendedCpu.isDecrease);
|
||
});
|
||
|
||
RecommendationCard.totalsTable.recommended.memory.as(RecommendedMemory => {
|
||
assert.equal(RecommendedMemory.text, '512 MiB');
|
||
assert.ok(RecommendedMemory.isIncrease);
|
||
});
|
||
|
||
assert.equal(RecommendationCard.totalsTable.unitDiff.cpu, '-75 MHz');
|
||
assert.equal(RecommendationCard.totalsTable.unitDiff.memory, '+128 MiB');
|
||
|
||
// Expected signal has a minus character, not a hyphen.
|
||
assert.equal(RecommendationCard.totalsTable.percentDiff.cpu, '−27%');
|
||
assert.equal(RecommendationCard.totalsTable.percentDiff.memory, '+33%');
|
||
|
||
assert.equal(RecommendationCard.copyButton.text, 'job-name / group-name');
|
||
assert.ok(
|
||
RecommendationCard.copyButton.clipboardText.endsWith(
|
||
'optimize.summary:job-name/group-name?namespace=namespace'
|
||
)
|
||
);
|
||
|
||
assert.equal(RecommendationCard.activeTask.totalsTable.current.cpu.text, '150 MHz');
|
||
assert.equal(RecommendationCard.activeTask.totalsTable.current.memory.text, '128 MiB');
|
||
|
||
RecommendationCard.activeTask.totalsTable.recommended.cpu.as(RecommendedCpu => {
|
||
assert.equal(RecommendedCpu.text, '50 MHz');
|
||
assert.ok(RecommendedCpu.isDecrease);
|
||
});
|
||
|
||
RecommendationCard.activeTask.totalsTable.recommended.memory.as(RecommendedMemory => {
|
||
assert.equal(RecommendedMemory.text, '192 MiB');
|
||
assert.ok(RecommendedMemory.isIncrease);
|
||
});
|
||
|
||
assert.equal(RecommendationCard.activeTask.charts.length, 2);
|
||
assert.equal(
|
||
RecommendationCard.activeTask.charts[0].resource,
|
||
'CPU',
|
||
'CPU chart should be first when present'
|
||
);
|
||
|
||
assert.ok(RecommendationCard.activeTask.cpuChart.isDecrease);
|
||
assert.ok(RecommendationCard.activeTask.memoryChart.isIncrease);
|
||
|
||
assert.equal(RecommendationCard.togglesTable.tasks.length, 2);
|
||
|
||
await RecommendationCard.togglesTable.tasks[0].as(async FirstTask => {
|
||
assert.equal(FirstTask.name, 'jortle');
|
||
assert.ok(FirstTask.isActive);
|
||
|
||
assert.equal(FirstTask.cpu.title, 'CPU for jortle');
|
||
assert.ok(FirstTask.cpu.isActive);
|
||
|
||
assert.equal(FirstTask.memory.title, 'Memory for jortle');
|
||
assert.ok(FirstTask.memory.isActive);
|
||
|
||
await FirstTask.cpu.toggle();
|
||
|
||
assert.notOk(FirstTask.cpu.isActive);
|
||
assert.ok(RecommendationCard.activeTask.cpuChart.isDisabled);
|
||
});
|
||
|
||
assert.notOk(RecommendationCard.togglesTable.tasks[1].isActive);
|
||
|
||
assert.equal(RecommendationCard.activeTask.name, 'jortle task');
|
||
|
||
RecommendationCard.totalsTable.recommended.cpu.as(RecommendedCpu => {
|
||
assert.equal(RecommendedCpu.text, '300 MHz');
|
||
assert.ok(RecommendedCpu.isIncrease);
|
||
});
|
||
|
||
RecommendationCard.activeTask.totalsTable.recommended.cpu.as(RecommendedCpu => {
|
||
assert.equal(RecommendedCpu.text, '150 MHz');
|
||
assert.ok(RecommendedCpu.isNeutral);
|
||
});
|
||
|
||
await RecommendationCard.togglesTable.toggleAllMemory.toggle();
|
||
|
||
assert.notOk(RecommendationCard.togglesTable.tasks[0].memory.isActive);
|
||
assert.notOk(RecommendationCard.togglesTable.tasks[1].memory.isActive);
|
||
|
||
RecommendationCard.totalsTable.recommended.memory.as(RecommendedMemory => {
|
||
assert.equal(RecommendedMemory.text, '384 MiB');
|
||
assert.ok(RecommendedMemory.isNeutral);
|
||
});
|
||
|
||
await RecommendationCard.togglesTable.tasks[1].click();
|
||
|
||
assert.notOk(RecommendationCard.togglesTable.tasks[0].isActive);
|
||
assert.ok(RecommendationCard.togglesTable.tasks[1].isActive);
|
||
|
||
assert.equal(RecommendationCard.activeTask.name, 'tortle task');
|
||
assert.equal(RecommendationCard.activeTask.totalsTable.current.cpu.text, '125 MHz');
|
||
|
||
await componentA11yAudit(this.element, assert);
|
||
});
|
||
|
||
test('it doesn’t have header toggles when there’s only one task', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
recommendations: [
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task1,
|
||
value: 192,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 1,
|
||
reservedCPU: task1.reservedCPU,
|
||
reservedMemory: task1.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
assert.notOk(RecommendationCard.togglesTable.toggleAllIsPresent);
|
||
assert.notOk(RecommendationCard.togglesTable.toggleAllCPU.isPresent);
|
||
assert.notOk(RecommendationCard.togglesTable.toggleAllMemory.isPresent);
|
||
});
|
||
|
||
test('it disables the accept button when all recommendations are disabled', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
recommendations: [
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task1,
|
||
value: 192,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 1,
|
||
reservedCPU: task1.reservedCPU,
|
||
reservedMemory: task1.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
await RecommendationCard.togglesTable.tasks[0].cpu.toggle();
|
||
await RecommendationCard.togglesTable.tasks[0].memory.toggle();
|
||
|
||
assert.ok(RecommendationCard.acceptButton.isDisabled);
|
||
});
|
||
|
||
test('it doesn’t show a toggle or chart when there’s no recommendation for that resource', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
recommendations: [
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 2,
|
||
name: 'group-name',
|
||
job: {
|
||
name: 'job-name',
|
||
},
|
||
reservedCPU: task1.reservedCPU,
|
||
reservedMemory: task1.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
assert.equal(RecommendationCard.totalsTable.recommended.memory.text, '128 MiB');
|
||
assert.equal(RecommendationCard.totalsTable.unitDiff.memory, '0 MiB');
|
||
assert.equal(RecommendationCard.totalsTable.percentDiff.memory, '+0%');
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will save an aggregate 200 MHz of CPU across 2 allocations.'
|
||
);
|
||
|
||
assert.ok(RecommendationCard.togglesTable.tasks[0].memory.isDisabled);
|
||
assert.notOk(RecommendationCard.activeTask.memoryChart.isPresent);
|
||
});
|
||
|
||
test('it disables a resource’s toggle all toggle when there are no recommendations for it', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
const task2 = {
|
||
name: 'tortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
recommendations: [
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
},
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task2,
|
||
value: 50,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 2,
|
||
name: 'group-name',
|
||
job: {
|
||
name: 'job-name',
|
||
},
|
||
reservedCPU: task1.reservedCPU + task2.reservedCPU,
|
||
reservedMemory: task1.reservedMemory + task2.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
assert.ok(RecommendationCard.togglesTable.toggleAllMemory.isDisabled);
|
||
assert.notOk(RecommendationCard.togglesTable.toggleAllMemory.isActive);
|
||
assert.notOk(RecommendationCard.activeTask.memoryChart.isPresent);
|
||
});
|
||
|
||
test('it renders diff calculations in a sentence', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
const task2 = {
|
||
name: 'tortle',
|
||
reservedCPU: 125,
|
||
reservedMemory: 256,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
recommendations: [
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
currentValue: task1.reservedCPU,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task1,
|
||
value: 192,
|
||
currentValue: task1.reservedMemory,
|
||
},
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task2,
|
||
value: 150,
|
||
currentValue: task2.reservedCPU,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task2,
|
||
value: 320,
|
||
currentValue: task2.reservedMemory,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 10,
|
||
name: 'group-name',
|
||
job: {
|
||
name: 'job-name',
|
||
namespace: {
|
||
name: 'namespace',
|
||
},
|
||
},
|
||
reservedCPU: task1.reservedCPU + task2.reservedCPU,
|
||
reservedMemory: task1.reservedMemory + task2.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
const [cpuRec1, memRec1, cpuRec2, memRec2] = this.summary.recommendations;
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will save an aggregate 750 MHz of CPU and add an aggregate 1.25 GiB of memory across 10 allocations.'
|
||
);
|
||
|
||
this.summary.toggleRecommendation(cpuRec1);
|
||
await settled();
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will add an aggregate 250 MHz of CPU and 1.25 GiB of memory across 10 allocations.'
|
||
);
|
||
|
||
this.summary.toggleRecommendation(memRec1);
|
||
await settled();
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will add an aggregate 250 MHz of CPU and 640 MiB of memory across 10 allocations.'
|
||
);
|
||
|
||
this.summary.toggleRecommendation(cpuRec2);
|
||
await settled();
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will add an aggregate 640 MiB of memory across 10 allocations.'
|
||
);
|
||
|
||
this.summary.toggleRecommendation(cpuRec1);
|
||
this.summary.toggleRecommendation(memRec2);
|
||
await settled();
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will save an aggregate 1 GHz of CPU across 10 allocations.'
|
||
);
|
||
|
||
this.summary.toggleRecommendation(cpuRec1);
|
||
await settled();
|
||
|
||
assert.equal(RecommendationCard.narrative.trim(), '');
|
||
|
||
this.summary.toggleRecommendation(cpuRec1);
|
||
await settled();
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will save an aggregate 1 GHz of CPU across 10 allocations.'
|
||
);
|
||
|
||
this.summary.toggleRecommendation(memRec2);
|
||
set(memRec2, 'value', 128);
|
||
await settled();
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will save an aggregate 1 GHz of CPU and 1.25 GiB of memory across 10 allocations.'
|
||
);
|
||
});
|
||
|
||
test('it renders diff calculations in a sentence with no aggregation for one allocatio', async function(assert) {
|
||
const task1 = {
|
||
name: 'jortle',
|
||
reservedCPU: 150,
|
||
reservedMemory: 128,
|
||
};
|
||
|
||
const task2 = {
|
||
name: 'tortle',
|
||
reservedCPU: 125,
|
||
reservedMemory: 256,
|
||
};
|
||
|
||
this.set(
|
||
'summary',
|
||
new MockRecommendationSummary({
|
||
recommendations: [
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task1,
|
||
value: 50,
|
||
currentValue: task1.reservedCPU,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task1,
|
||
value: 192,
|
||
currentValue: task1.reservedMemory,
|
||
},
|
||
{
|
||
resource: 'CPU',
|
||
stats: {},
|
||
task: task2,
|
||
value: 150,
|
||
currentValue: task2.reservedCPU,
|
||
},
|
||
{
|
||
resource: 'MemoryMB',
|
||
stats: {},
|
||
task: task2,
|
||
value: 320,
|
||
currentValue: task2.reservedMemory,
|
||
},
|
||
],
|
||
|
||
taskGroup: {
|
||
count: 1,
|
||
name: 'group-name',
|
||
job: {
|
||
name: 'job-name',
|
||
namespace: {
|
||
name: 'namespace',
|
||
},
|
||
},
|
||
reservedCPU: task1.reservedCPU + task2.reservedCPU,
|
||
reservedMemory: task1.reservedMemory + task2.reservedMemory,
|
||
},
|
||
})
|
||
);
|
||
|
||
await render(hbs`<Das::RecommendationCard @summary={{this.summary}} />`);
|
||
|
||
assert.equal(
|
||
RecommendationCard.narrative.trim(),
|
||
'Applying the selected recommendations will save 75 MHz of CPU and add 128 MiB of memory.'
|
||
);
|
||
});
|
||
});
|
||
|
||
class MockRecommendationSummary {
|
||
@tracked excludedRecommendations = [];
|
||
|
||
constructor(attributes) {
|
||
Object.assign(this, attributes);
|
||
}
|
||
|
||
get slug() {
|
||
return `${this.taskGroup?.job?.name}/${this.taskGroup?.name}`;
|
||
}
|
||
|
||
@action
|
||
toggleRecommendation(recommendation) {
|
||
if (this.excludedRecommendations.includes(recommendation)) {
|
||
this.excludedRecommendations.removeObject(recommendation);
|
||
} else {
|
||
this.excludedRecommendations.pushObject(recommendation);
|
||
}
|
||
}
|
||
|
||
@action
|
||
toggleAllRecommendationsForResource(resource, enabled) {
|
||
if (enabled) {
|
||
this.excludedRecommendations = this.excludedRecommendations.rejectBy('resource', resource);
|
||
} else {
|
||
this.excludedRecommendations.pushObjects(this.recommendations.filterBy('resource', resource));
|
||
}
|
||
}
|
||
}
|