feat: add pagination and filtering to evaluations view
This commit is contained in:
parent
1f80081c9d
commit
e5b154e295
|
@ -1,9 +1,12 @@
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default class EvaluationsController extends Controller {
|
export default class EvaluationsController extends Controller {
|
||||||
queryParams = ['nextToken', 'pageSize'];
|
@service userSettings;
|
||||||
|
|
||||||
|
queryParams = ['nextToken', 'pageSize', 'status'];
|
||||||
|
|
||||||
get shouldDisableNext() {
|
get shouldDisableNext() {
|
||||||
return !this.model.meta?.nextToken;
|
return !this.model.meta?.nextToken;
|
||||||
|
@ -13,9 +16,21 @@ export default class EvaluationsController extends Controller {
|
||||||
return !this.previousTokens.length;
|
return !this.previousTokens.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@tracked pageSize = 25;
|
get optionsEvaluationsStatus() {
|
||||||
|
return [
|
||||||
|
{ key: null, label: 'All' },
|
||||||
|
{ key: 'blocked', label: 'Blocked' },
|
||||||
|
{ key: 'pending', label: 'Pending' },
|
||||||
|
{ key: 'complete', label: 'Complete' },
|
||||||
|
{ key: 'failed', label: 'Failed' },
|
||||||
|
{ key: 'canceled', label: 'Canceled' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@tracked pageSize = this.userSettings.pageSize;
|
||||||
@tracked nextToken = null;
|
@tracked nextToken = null;
|
||||||
@tracked previousTokens = [];
|
@tracked previousTokens = [];
|
||||||
|
@tracked status = null;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onChange(newPageSize) {
|
onChange(newPageSize) {
|
||||||
|
@ -29,14 +44,26 @@ export default class EvaluationsController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onPrev(lastToken) {
|
onPrev() {
|
||||||
this.previousTokens.pop();
|
const lastToken = this.previousTokens.pop();
|
||||||
this.previousTokens = [...this.previousTokens];
|
this.previousTokens = [...this.previousTokens];
|
||||||
this.nextToken = lastToken;
|
this.nextToken = lastToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
refresh() {
|
refresh() {
|
||||||
|
this._resetTokens();
|
||||||
|
this.status = null;
|
||||||
|
this.pageSize = this.userSettings.pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
setStatus(selection) {
|
||||||
|
this._resetTokens();
|
||||||
|
this.status = selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
_resetTokens() {
|
||||||
this.nextToken = null;
|
this.nextToken = null;
|
||||||
this.previousTokens = [];
|
this.previousTokens = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import shortUUIDProperty from '../utils/properties/short-uuid';
|
||||||
|
|
||||||
export default class Evaluation extends Model {
|
export default class Evaluation extends Model {
|
||||||
@shortUUIDProperty('id') shortId;
|
@shortUUIDProperty('id') shortId;
|
||||||
|
@shortUUIDProperty('nodeId') shortNodeId;
|
||||||
@attr('number') priority;
|
@attr('number') priority;
|
||||||
@attr('string') type;
|
@attr('string') type;
|
||||||
@attr('string') triggeredBy;
|
@attr('string') triggeredBy;
|
||||||
|
@ -18,6 +19,7 @@ export default class Evaluation extends Model {
|
||||||
@equal('status', 'blocked') isBlocked;
|
@equal('status', 'blocked') isBlocked;
|
||||||
|
|
||||||
@belongsTo('job') job;
|
@belongsTo('job') job;
|
||||||
|
@belongsTo('node') node;
|
||||||
|
|
||||||
@attr('number') modifyIndex;
|
@attr('number') modifyIndex;
|
||||||
@attr('date') modifyTime;
|
@attr('date') modifyTime;
|
||||||
|
@ -26,4 +28,18 @@ export default class Evaluation extends Model {
|
||||||
@attr('date') createTime;
|
@attr('date') createTime;
|
||||||
|
|
||||||
@attr('date') waitUntil;
|
@attr('date') waitUntil;
|
||||||
|
@attr('string') namespace;
|
||||||
|
@attr('string') plainJobId;
|
||||||
|
|
||||||
|
get hasJob() {
|
||||||
|
return !!this.plainJobId;
|
||||||
|
}
|
||||||
|
|
||||||
|
get hasNode() {
|
||||||
|
return !!this.belongsTo('node').id();
|
||||||
|
}
|
||||||
|
|
||||||
|
get nodeId() {
|
||||||
|
return this.belongsTo('node').id();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,13 +13,17 @@ export default class EvaluationsIndexRoute extends Route {
|
||||||
nextToken: {
|
nextToken: {
|
||||||
refreshModel: true,
|
refreshModel: true,
|
||||||
},
|
},
|
||||||
|
status: {
|
||||||
|
refreshModel: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
model({ pageSize, nextToken }) {
|
model({ pageSize, status, nextToken }) {
|
||||||
return this.store.query('evaluation', {
|
return this.store.query('evaluation', {
|
||||||
namespace: ALL_NAMESPACE_WILDCARD,
|
namespace: ALL_NAMESPACE_WILDCARD,
|
||||||
per_page: pageSize,
|
per_page: pageSize,
|
||||||
next_token: nextToken,
|
next_token: nextToken,
|
||||||
|
status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
<div class="field is-horizontal" data-test-page-size-select-parent>
|
<div class="field is-horizontal" data-test-page-size-select-parent ...attributes>
|
||||||
<span class="field-label is-small">Per page</span>
|
<span class="field-label is-small">
|
||||||
|
Per page
|
||||||
|
</span>
|
||||||
<PowerSelect
|
<PowerSelect
|
||||||
@tagName="div"
|
@tagName="div"
|
||||||
class="field-body"
|
class="field-body"
|
||||||
data-test-page-size-select
|
data-test-page-size-select
|
||||||
@options={{this.pageSizeOptions}}
|
@options={{this.pageSizeOptions}}
|
||||||
@selected={{this.userSettings.pageSize}}
|
@selected={{this.userSettings.pageSize}}
|
||||||
@onChange={{action (queue
|
@onChange={{action (queue (action (mut this.userSettings.pageSize)) (action this.onChange))
|
||||||
(action (mut this.userSettings.pageSize))
|
}} as |option|
|
||||||
(action this.onChange)
|
>
|
||||||
)}} as |option|>
|
|
||||||
{{option}}
|
{{option}}
|
||||||
</PowerSelect>
|
</PowerSelect>
|
||||||
</div>
|
</div>
|
|
@ -1,79 +1,136 @@
|
||||||
{{page-title "Evaluations"}}
|
{{page-title "Evaluations"}}
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<ListTable @source={{@model}} as |t|>
|
<div class="toolbar">
|
||||||
<t.head>
|
<div class="toolbar-item">
|
||||||
<th>
|
<SingleSelectDropdown
|
||||||
Evaluation ID
|
data-test-evaluation-status-facet
|
||||||
</th>
|
@label="Status"
|
||||||
<th>
|
@options={{this.optionsEvaluationsStatus}}
|
||||||
Resource
|
@selection={{this.status}}
|
||||||
</th>
|
@onSelect={{action this.setStatus}}
|
||||||
<th>
|
/>
|
||||||
Priority
|
</div>
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Created
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Triggered By
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Status
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Placement Failures
|
|
||||||
</th>
|
|
||||||
</t.head>
|
|
||||||
<t.body as |row|>
|
|
||||||
<tr data-test-evaluation="{{row.model.shortId}}">
|
|
||||||
<td data-test-id>
|
|
||||||
{{row.model.shortId}}
|
|
||||||
</td>
|
|
||||||
<td data-test-id>
|
|
||||||
Resource Placeholder
|
|
||||||
</td>
|
|
||||||
<td data-test-priority>
|
|
||||||
{{row.model.priority}}
|
|
||||||
</td>
|
|
||||||
<td data-test-create-time>
|
|
||||||
{{format-month-ts row.model.createTime}}
|
|
||||||
</td>
|
|
||||||
<td data-test-triggered-by>
|
|
||||||
{{row.model.triggeredBy}}
|
|
||||||
</td>
|
|
||||||
<td data-test-status>
|
|
||||||
<StatusCell @status={{row.model.status}} />
|
|
||||||
</td>
|
|
||||||
<td data-test-blocked>
|
|
||||||
{{#if (eq row.model.status "blocked")}}
|
|
||||||
N/A - In Progress
|
|
||||||
{{else if row.model.hasPlacementFailures}}
|
|
||||||
True
|
|
||||||
{{else}}
|
|
||||||
False
|
|
||||||
{{/if}}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</t.body>
|
|
||||||
</ListTable>
|
|
||||||
<div class="table-foot">
|
|
||||||
<PageSizeSelect @onChange={{this.onChange}} />
|
|
||||||
<button type="button" {{on "click" this.refresh}}>
|
|
||||||
Refresh
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={{this.shouldDisablePrev}}
|
|
||||||
{{on "click" (fn this.onPrev this.lastToken)}}
|
|
||||||
>
|
|
||||||
Prev
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={{this.shouldDisableNext}}
|
|
||||||
{{on "click" (fn this.onNext @model.meta.nextToken)}}
|
|
||||||
>
|
|
||||||
Next >
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{{#if @model.length}}
|
||||||
|
<ListTable data-test-eval-table @source={{@model}} as |t|>
|
||||||
|
<t.head>
|
||||||
|
<th>
|
||||||
|
Evaluation ID
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Resource
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Priority
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Created
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Triggered By
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th>
|
||||||
|
Placement Failures
|
||||||
|
</th>
|
||||||
|
</t.head>
|
||||||
|
<t.body as |row|>
|
||||||
|
<tr data-test-evaluation="{{row.model.shortId}}">
|
||||||
|
<td data-test-id>
|
||||||
|
{{row.model.shortId}}
|
||||||
|
</td>
|
||||||
|
<td data-test-id>
|
||||||
|
{{#if row.model.hasJob}}
|
||||||
|
<LinkTo
|
||||||
|
data-test-evaluation-resource
|
||||||
|
@model={{row.model.plainJobId}}
|
||||||
|
@route="jobs.job"
|
||||||
|
@query={{hash namespace=row.model.namespace}}
|
||||||
|
>
|
||||||
|
{{row.model.plainJobId}}
|
||||||
|
</LinkTo>
|
||||||
|
{{else}}
|
||||||
|
<LinkTo
|
||||||
|
data-test-evaluation-resource
|
||||||
|
@model={{row.model.nodeId}}
|
||||||
|
@route="clients.client"
|
||||||
|
>
|
||||||
|
{{row.model.shortNodeId}}
|
||||||
|
</LinkTo>
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
<td data-test-priority>
|
||||||
|
{{row.model.priority}}
|
||||||
|
</td>
|
||||||
|
<td data-test-create-time>
|
||||||
|
{{format-month-ts row.model.createTime}}
|
||||||
|
</td>
|
||||||
|
<td data-test-triggered-by>
|
||||||
|
{{row.model.triggeredBy}}
|
||||||
|
</td>
|
||||||
|
<td data-test-status class="is-one-line">
|
||||||
|
<StatusCell @status={{row.model.status}} />
|
||||||
|
</td>
|
||||||
|
<td data-test-blocked>
|
||||||
|
{{#if (eq row.model.status "blocked")}}
|
||||||
|
N/A - In Progress
|
||||||
|
{{else if row.model.hasPlacementFailures}}
|
||||||
|
True
|
||||||
|
{{else}}
|
||||||
|
False
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</t.body>
|
||||||
|
</ListTable>
|
||||||
|
<div class="table-foot">
|
||||||
|
<PageSizeSelect data-test-per-page @onChange={{this.onChange}} />
|
||||||
|
<button data-test-eval-refresh type="button" {{on "click" this.refresh}}>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-test-eval-pagination-prev
|
||||||
|
type="button"
|
||||||
|
disabled={{this.shouldDisablePrev}}
|
||||||
|
{{on "click" (fn this.onPrev this.lastToken)}}
|
||||||
|
>
|
||||||
|
Prev
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-test-eval-pagination-next
|
||||||
|
type="button"
|
||||||
|
disabled={{this.shouldDisableNext}}
|
||||||
|
{{on "click" (fn this.onNext @model.meta.nextToken)}}
|
||||||
|
>
|
||||||
|
Next >
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="boxed-section-body">
|
||||||
|
<div class="empty-message" data-test-empty-evaluations-list>
|
||||||
|
<h3 class="empty-message-headline" data-test-empty-evalations-list-headline>
|
||||||
|
No Matches
|
||||||
|
</h3>
|
||||||
|
<p class="empty-message-body">
|
||||||
|
{{#if this.status}}
|
||||||
|
<span data-test-no-eval-match>
|
||||||
|
No evaluations match the status
|
||||||
|
<strong>
|
||||||
|
{{this.status}}
|
||||||
|
</strong>
|
||||||
|
</span>
|
||||||
|
<button type="button" {{on "click" this.refresh}}>
|
||||||
|
Show all evaluations
|
||||||
|
</button>
|
||||||
|
{{else}}
|
||||||
|
<span data-test-no-eval>
|
||||||
|
There are no evaluations
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
</section>
|
</section>
|
|
@ -261,6 +261,7 @@ export default function () {
|
||||||
return this.serialize(evaluations.where({ jobId: params.id }));
|
return this.serialize(evaluations.where({ jobId: params.id }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.get('/evaluations');
|
||||||
this.get('/evaluation/:id');
|
this.get('/evaluation/:id');
|
||||||
|
|
||||||
this.get('/deployment/allocations/:id', function (schema, { params }) {
|
this.get('/deployment/allocations/:id', function (schema, { params }) {
|
||||||
|
|
|
@ -91,7 +91,9 @@ export default Factory.extend({
|
||||||
}),
|
}),
|
||||||
|
|
||||||
afterCreate(evaluation, server) {
|
afterCreate(evaluation, server) {
|
||||||
assignJob(evaluation, server);
|
if (!evaluation.nodeId) {
|
||||||
|
assignJob(evaluation, server);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,397 @@
|
||||||
|
import { click, currentRouteName, visit } from '@ember/test-helpers';
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupApplicationTest } from 'ember-qunit';
|
||||||
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||||
|
import { Response } from 'ember-cli-mirage';
|
||||||
|
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
||||||
|
import { selectChoose, clickTrigger } from 'ember-power-select/test-support/helpers';
|
||||||
|
|
||||||
|
const getStandardRes = () => [
|
||||||
|
{
|
||||||
|
CreateIndex: 1249,
|
||||||
|
CreateTime: 1640181894162724000,
|
||||||
|
DeploymentID: '12efbb28-840e-7794-b215-a7b112e40a4f',
|
||||||
|
ID: '5fb1b8cd-00f8-fff8-de0c-197dc37f5053',
|
||||||
|
JobID: 'cores-example',
|
||||||
|
JobModifyIndex: 694,
|
||||||
|
ModifyIndex: 1251,
|
||||||
|
ModifyTime: 1640181894167194000,
|
||||||
|
Namespace: 'ted-lasso',
|
||||||
|
Priority: 50,
|
||||||
|
QueuedAllocations: {
|
||||||
|
lb: 0,
|
||||||
|
webapp: 0,
|
||||||
|
},
|
||||||
|
SnapshotIndex: 1249,
|
||||||
|
Status: 'complete',
|
||||||
|
TriggeredBy: 'job-register',
|
||||||
|
Type: 'service',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateIndex: 1304,
|
||||||
|
CreateTime: 1640183201719510000,
|
||||||
|
DeploymentID: '878435bf-7265-62b1-7902-d45c44b23b79',
|
||||||
|
ID: '66cb98a6-7740-d5ef-37e4-fa0f8b1de44b',
|
||||||
|
JobID: 'cores-example',
|
||||||
|
JobModifyIndex: 1304,
|
||||||
|
ModifyIndex: 1306,
|
||||||
|
ModifyTime: 1640183201721418000,
|
||||||
|
Namespace: 'default',
|
||||||
|
Priority: 50,
|
||||||
|
QueuedAllocations: {
|
||||||
|
webapp: 0,
|
||||||
|
lb: 0,
|
||||||
|
},
|
||||||
|
SnapshotIndex: 1304,
|
||||||
|
Status: 'complete',
|
||||||
|
TriggeredBy: 'job-register',
|
||||||
|
Type: 'service',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateIndex: 1267,
|
||||||
|
CreateTime: 1640182198255685000,
|
||||||
|
DeploymentID: '12efbb28-840e-7794-b215-a7b112e40a4f',
|
||||||
|
ID: '78009518-574d-eee6-919a-e83879175dd3',
|
||||||
|
JobID: 'cores-example',
|
||||||
|
JobModifyIndex: 1250,
|
||||||
|
ModifyIndex: 1274,
|
||||||
|
ModifyTime: 1640182228112823000,
|
||||||
|
Namespace: 'ted-lasso',
|
||||||
|
PreviousEval: '84f1082f-3e6e-034d-6df4-c6a321e7bd63',
|
||||||
|
Priority: 50,
|
||||||
|
QueuedAllocations: {
|
||||||
|
lb: 0,
|
||||||
|
},
|
||||||
|
SnapshotIndex: 1272,
|
||||||
|
Status: 'complete',
|
||||||
|
TriggeredBy: 'alloc-failure',
|
||||||
|
Type: 'service',
|
||||||
|
WaitUntil: '2021-12-22T14:10:28.108136Z',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CreateIndex: 1322,
|
||||||
|
CreateTime: 1640183505760099000,
|
||||||
|
DeploymentID: '878435bf-7265-62b1-7902-d45c44b23b79',
|
||||||
|
ID: 'c184f72b-68a3-5180-afd6-af01860ad371',
|
||||||
|
JobID: 'cores-example',
|
||||||
|
JobModifyIndex: 1305,
|
||||||
|
ModifyIndex: 1329,
|
||||||
|
ModifyTime: 1640183535540881000,
|
||||||
|
Namespace: 'default',
|
||||||
|
PreviousEval: '9a917a93-7bc3-6991-ffc9-15919a38f04b',
|
||||||
|
Priority: 50,
|
||||||
|
QueuedAllocations: {
|
||||||
|
lb: 0,
|
||||||
|
},
|
||||||
|
SnapshotIndex: 1326,
|
||||||
|
Status: 'complete',
|
||||||
|
TriggeredBy: 'alloc-failure',
|
||||||
|
Type: 'service',
|
||||||
|
WaitUntil: '2021-12-22T14:32:15.539556Z',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
module('Acceptance | evaluations list', function(hooks) {
|
||||||
|
setupApplicationTest(hooks);
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
|
test('it passes an accessibility audit', async function(assert) {
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
currentRouteName(),
|
||||||
|
'evaluations.index',
|
||||||
|
'The default route in evaluations is evaluations index'
|
||||||
|
);
|
||||||
|
|
||||||
|
await a11yAudit(assert);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders an empty message if there are no evaluations rendered', async function(assert) {
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
assert.dom('[data-test-empty-evaluations-list]').exists('We display empty table message.');
|
||||||
|
assert
|
||||||
|
.dom('[data-test-no-eval]')
|
||||||
|
.exists('We display a message saying there are no evaluations.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it renders a list of evaluations', async function(assert) {
|
||||||
|
assert.expect(3);
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: '',
|
||||||
|
next_token: '',
|
||||||
|
},
|
||||||
|
'Forwards the correct query parameters on default query when route initially loads'
|
||||||
|
);
|
||||||
|
return getStandardRes();
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
assert.dom('[data-test-eval-table]').exists('Evaluations table should render');
|
||||||
|
assert
|
||||||
|
.dom('[data-test-evaluation]')
|
||||||
|
.exists({ count: 4 }, 'Should render the correct number of evaluations');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should enable filtering by evaluation status', async function(assert) {
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
server.get('/evaluations', getStandardRes);
|
||||||
|
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: 'pending',
|
||||||
|
next_token: '',
|
||||||
|
},
|
||||||
|
'It makes another server request using the options selected by the user'
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
await clickTrigger('[data-test-evaluation-status-facet]');
|
||||||
|
await selectChoose('[data-test-evaluation-status-facet]', 'Pending');
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-no-eval-match]')
|
||||||
|
.exists('Renders a message saying no evaluations match filter status');
|
||||||
|
});
|
||||||
|
|
||||||
|
module('page size', function(hooks) {
|
||||||
|
hooks.afterEach(function() {
|
||||||
|
// PageSizeSelect and the Evaluations Controller are both using localStorage directly
|
||||||
|
// Will come back and invert the dependency
|
||||||
|
window.localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it is possible to change page size', async function(assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
|
||||||
|
server.get('/evaluations', getStandardRes);
|
||||||
|
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '50',
|
||||||
|
status: '',
|
||||||
|
next_token: '',
|
||||||
|
},
|
||||||
|
'It makes a request with the per_page set by the user'
|
||||||
|
);
|
||||||
|
return getStandardRes();
|
||||||
|
});
|
||||||
|
|
||||||
|
await clickTrigger('[data-test-per-page]');
|
||||||
|
await selectChoose('[data-test-per-page]', 50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('pagination', function() {
|
||||||
|
test('it should enable pagination by using next tokens', async function(assert) {
|
||||||
|
assert.expect(7);
|
||||||
|
|
||||||
|
server.get('/evaluations', function() {
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-1' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: '',
|
||||||
|
next_token: 'next-token-1',
|
||||||
|
},
|
||||||
|
'It makes another server request using the options selected by the user'
|
||||||
|
);
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-2' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-eval-pagination-next]')
|
||||||
|
.isEnabled(
|
||||||
|
'If there is a next-token in the API response the next button should be enabled.'
|
||||||
|
);
|
||||||
|
await click('[data-test-eval-pagination-next]');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: '',
|
||||||
|
next_token: 'next-token-2',
|
||||||
|
},
|
||||||
|
'It makes another server request using the options selected by the user'
|
||||||
|
);
|
||||||
|
return getStandardRes();
|
||||||
|
});
|
||||||
|
await click('[data-test-eval-pagination-next]');
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-eval-pagination-next]')
|
||||||
|
.isDisabled('If there is no next-token, the next button is disabled.');
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-eval-pagination-prev]')
|
||||||
|
.isEnabled('After we transition to the next page, the previous page button is enabled.');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: '',
|
||||||
|
next_token: 'next-token-1',
|
||||||
|
},
|
||||||
|
'It makes a request using the stored old token.'
|
||||||
|
);
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-2' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await click('[data-test-eval-pagination-prev]');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: '',
|
||||||
|
next_token: '',
|
||||||
|
},
|
||||||
|
'When there are no more stored previous tokens, we will request with no next-token.'
|
||||||
|
);
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-1' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await click('[data-test-eval-pagination-prev]');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should clear all query parameters on refresh', async function(assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
|
||||||
|
server.get('/evaluations', function() {
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-1' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
server.get('/evaluations', function() {
|
||||||
|
return getStandardRes();
|
||||||
|
});
|
||||||
|
|
||||||
|
await click('[data-test-eval-pagination-next]');
|
||||||
|
|
||||||
|
await clickTrigger('[data-test-evaluation-status-facet]');
|
||||||
|
await selectChoose('[data-test-evaluation-status-facet]', 'Pending');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: '',
|
||||||
|
next_token: '',
|
||||||
|
},
|
||||||
|
'It clears all query parameters when making a refresh'
|
||||||
|
);
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-1' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await click('[data-test-eval-refresh]');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should reset pagination when filters are applied', async function(assert) {
|
||||||
|
assert.expect(1);
|
||||||
|
|
||||||
|
server.get('/evaluations', function() {
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-1' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
server.get('/evaluations', function() {
|
||||||
|
return new Response(200, { 'x-nomad-nexttoken': 'next-token-2' }, getStandardRes());
|
||||||
|
});
|
||||||
|
|
||||||
|
await click('[data-test-eval-pagination-next]');
|
||||||
|
|
||||||
|
server.get('/evaluations', getStandardRes);
|
||||||
|
await click('[data-test-eval-pagination-next]');
|
||||||
|
|
||||||
|
server.get('/evaluations', function(_server, fakeRequest) {
|
||||||
|
assert.deepEqual(
|
||||||
|
fakeRequest.queryParams,
|
||||||
|
{
|
||||||
|
namespace: '*',
|
||||||
|
per_page: '25',
|
||||||
|
status: 'pending',
|
||||||
|
next_token: '',
|
||||||
|
},
|
||||||
|
'It clears all next token when filtered request is made'
|
||||||
|
);
|
||||||
|
return getStandardRes();
|
||||||
|
});
|
||||||
|
await clickTrigger('[data-test-evaluation-status-facet]');
|
||||||
|
await selectChoose('[data-test-evaluation-status-facet]', 'Pending');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('resource linking', function() {
|
||||||
|
test('it should generate a link to the job resource', async function(assert) {
|
||||||
|
server.create('node');
|
||||||
|
const job = server.create('job', { shallow: true });
|
||||||
|
server.create('evaluation', { jobId: job.id });
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-evaluation-resource]')
|
||||||
|
.hasText(job.name, 'It conditionally renders the correct resource name');
|
||||||
|
await click('[data-test-evaluation-resource]');
|
||||||
|
assert
|
||||||
|
.dom('[data-test-job-name]')
|
||||||
|
.includesText(job.name, 'We navigate to the correct job page.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it should generate a link to the node resource', async function(assert) {
|
||||||
|
const node = server.create('node');
|
||||||
|
server.create('evaluation', { nodeId: node.id });
|
||||||
|
await visit('/evaluations');
|
||||||
|
|
||||||
|
const shortNodeId = node.id.split('-')[0];
|
||||||
|
assert
|
||||||
|
.dom('[data-test-evaluation-resource]')
|
||||||
|
.hasText(shortNodeId, 'It conditionally renders the correct resource name');
|
||||||
|
|
||||||
|
await click('[data-test-evaluation-resource]');
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-title]')
|
||||||
|
.includesText(node.name, 'We navigate to the correct client page.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue