From 1d773d0d9e3e624deb7707b44d59515f9af29c4a Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Fri, 17 Dec 2021 18:45:31 -0500 Subject: [PATCH] ui: fix task group alloc filter and add tests --- ui/app/controllers/jobs/job/task-group.js | 25 ++-- ui/app/templates/jobs/job/task-group.hbs | 2 +- ui/tests/acceptance/task-group-detail-test.js | 140 ++++++++++++++++++ ui/tests/pages/jobs/job/task-group.js | 6 + 4 files changed, 160 insertions(+), 13 deletions(-) diff --git a/ui/app/controllers/jobs/job/task-group.js b/ui/app/controllers/jobs/job/task-group.js index b0fdb6d58cc..807fa71725e 100644 --- a/ui/app/controllers/jobs/job/task-group.js +++ b/ui/app/controllers/jobs/job/task-group.js @@ -13,10 +13,10 @@ import classic from 'ember-classic-decorator'; @classic export default class TaskGroupController extends Controller.extend( - Sortable, - Searchable, - WithNamespaceResetting - ) { + Sortable, + Searchable, + WithNamespaceResetting +) { @service userSettings; @service can; @@ -54,14 +54,16 @@ export default class TaskGroupController extends Controller.extend( return ['shortId', 'name']; } - @computed('model.allocations.[]', 'selectionStatus', 'selectionClient') + @computed('model.allocations.[]') get allocations() { - const allocations = this.get('model.allocations') || []; - const { selectionStatus, selectionClient } = this; + return this.get('model.allocations') || []; + } - if (!allocations.length) return allocations; + @computed('allocations.[]', 'selectionStatus', 'selectionClient') + get filteredAllocations() { + const { selectionStatus, selectionClient } = this; - return allocations.filter(alloc => { + return this.allocations.filter(alloc => { if (selectionStatus.length && !selectionStatus.includes(alloc.clientStatus)) { return false; } @@ -73,7 +75,7 @@ export default class TaskGroupController extends Controller.extend( }); } - @alias('allocations') listToSort; + @alias('filteredAllocations') listToSort; @alias('listSorted') listToSearch; @alias('listSearched') sortedAllocations; @@ -115,8 +117,7 @@ export default class TaskGroupController extends Controller.extend( get optionsAllocationStatus() { return [ - { key: 'queued', label: 'Queued' }, - { key: 'starting', label: 'Starting' }, + { key: 'pending', label: 'Pending' }, { key: 'running', label: 'Running' }, { key: 'complete', label: 'Complete' }, { key: 'failed', label: 'Failed' }, diff --git a/ui/app/templates/jobs/job/task-group.hbs b/ui/app/templates/jobs/job/task-group.hbs index 87ef5d87a1f..2c00dcfc2d6 100644 --- a/ui/app/templates/jobs/job/task-group.hbs +++ b/ui/app/templates/jobs/job/task-group.hbs @@ -78,7 +78,7 @@ @onSelect={{action "setFacetQueryParam" "qpStatus"}} /> ev.count == null).length ); }); + + testFacet('Status', { + facet: TaskGroup.facets.status, + paramName: 'status', + expectedOptions: ['Pending', 'Running', 'Complete', 'Failed', 'Lost'], + async beforeEach() { + ['pending', 'running', 'complete', 'failed', 'lost'].forEach(s => { + server.createList('allocation', 5, { clientStatus: s }); + }); + await TaskGroup.visit({ id: job.id, name: taskGroup.name }); + }, + filter: (alloc, selection) => + alloc.jobId == job.id && + alloc.taskGroup == taskGroup.name && + selection.includes(alloc.clientStatus), + }); + + testFacet('Client', { + facet: TaskGroup.facets.client, + paramName: 'client', + expectedOptions(allocs) { + return Array.from( + new Set( + allocs + .filter(alloc => alloc.jobId == job.id && alloc.taskGroup == taskGroup.name) + .mapBy('nodeId') + .map(id => id.split('-')[0]) + ) + ).sort(); + }, + async beforeEach() { + const nodes = server.createList('node', 3, 'forceIPv4'); + nodes.forEach(node => + server.createList('allocation', 5, { + nodeId: node.id, + jobId: job.id, + taskGroup: taskGroup.name, + }) + ); + await TaskGroup.visit({ id: job.id, name: taskGroup.name }); + }, + filter: (alloc, selection) => + alloc.jobId == job.id && + alloc.taskGroup == taskGroup.name && + selection.includes(alloc.nodeId.split('-')[0]), + }); }); + +function testFacet(label, { facet, paramName, beforeEach, filter, expectedOptions }) { + test(`facet ${label} | the ${label} facet has the correct options`, async function(assert) { + await beforeEach(); + await facet.toggle(); + + let expectation; + if (typeof expectedOptions === 'function') { + expectation = expectedOptions(server.db.allocations); + } else { + expectation = expectedOptions; + } + + assert.deepEqual( + facet.options.map(option => option.label.trim()), + expectation, + 'Options for facet are as expected' + ); + }); + + test(`facet ${label} | the ${label} facet filters the allocations list by ${label}`, async function(assert) { + let option; + + await beforeEach(); + + await facet.toggle(); + option = facet.options.objectAt(0); + await option.toggle(); + + const selection = [option.key]; + const expectedAllocs = server.db.allocations + .filter(alloc => filter(alloc, selection)) + .sortBy('modifyIndex') + .reverse(); + + TaskGroup.allocations.forEach((alloc, index) => { + assert.equal( + alloc.id, + expectedAllocs[index].id, + `Allocation at ${index} is ${expectedAllocs[index].id}` + ); + }); + }); + + test(`facet ${label} | selecting multiple options in the ${label} facet results in a broader search`, async function(assert) { + const selection = []; + + await beforeEach(); + await facet.toggle(); + + const option1 = facet.options.objectAt(0); + const option2 = facet.options.objectAt(1); + await option1.toggle(); + selection.push(option1.key); + await option2.toggle(); + selection.push(option2.key); + + const expectedAllocs = server.db.allocations + .filter(alloc => filter(alloc, selection)) + .sortBy('modifyIndex') + .reverse(); + + TaskGroup.allocations.forEach((alloc, index) => { + assert.equal( + alloc.id, + expectedAllocs[index].id, + `Allocation at ${index} is ${expectedAllocs[index].id}` + ); + }); + }); + + test(`facet ${label} | selecting options in the ${label} facet updates the ${paramName} query param`, async function(assert) { + const selection = []; + + await beforeEach(); + await facet.toggle(); + + const option1 = facet.options.objectAt(0); + const option2 = facet.options.objectAt(1); + await option1.toggle(); + selection.push(option1.key); + await option2.toggle(); + selection.push(option2.key); + + assert.equal( + currentURL(), + `/jobs/${job.id}/${taskGroup.name}?${paramName}=${encodeURIComponent( + JSON.stringify(selection) + )}`, + 'URL has the correct query param key and value' + ); + }); +} diff --git a/ui/tests/pages/jobs/job/task-group.js b/ui/tests/pages/jobs/job/task-group.js index 192ea152a2f..28bd08dbd21 100644 --- a/ui/tests/pages/jobs/job/task-group.js +++ b/ui/tests/pages/jobs/job/task-group.js @@ -13,6 +13,7 @@ import error from 'nomad-ui/tests/pages/components/error'; import pageSizeSelect from 'nomad-ui/tests/pages/components/page-size-select'; import stepperInput from 'nomad-ui/tests/pages/components/stepper-input'; import LifecycleChart from 'nomad-ui/tests/pages/components/lifecycle-chart'; +import { multiFacet } from 'nomad-ui/tests/pages/components/facet'; export default create({ pageSize: 25, @@ -33,6 +34,11 @@ export default create({ isEmpty: isPresent('[data-test-empty-allocations-list]'), + facets: { + status: multiFacet('[data-test-allocation-status-facet]'), + client: multiFacet('[data-test-allocation-client-facet]'), + }, + lifecycleChart: LifecycleChart, hasVolumes: isPresent('[data-test-volumes]'),