Merge pull request #4481 from hashicorp/f-ui-move-evals-to-tab

UI: Move evals to a tab
This commit is contained in:
Michael Lange 2018-07-06 19:01:46 -07:00 committed by GitHub
commit 8b9bc7834c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 160 additions and 126 deletions

View File

@ -1,12 +0,0 @@
import Component from '@ember/component';
import { computed } from '@ember/object';
export default Component.extend({
job: null,
classNames: ['boxed-section'],
sortedEvaluations: computed('job.evaluations.@each.modifyIndex', function() {
return (this.get('job.evaluations') || []).sortBy('modifyIndex').reverse();
}),
});

View File

@ -0,0 +1,24 @@
import { alias } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting';
import Sortable from 'nomad-ui/mixins/sortable';
export default Controller.extend(WithNamespaceResetting, Sortable, {
jobController: controller('jobs.job'),
queryParams: {
sortProperty: 'sort',
sortDescending: 'desc',
},
sortProperty: 'modifyIndex',
sortDescending: true,
job: alias('model'),
evaluations: alias('model.evaluations'),
breadcrumbs: alias('jobController.breadcrumbs'),
listToSort: alias('evaluations'),
sortedEvaluations: alias('listSorted'),
});

View File

@ -13,6 +13,7 @@ Router.map(function() {
this.route('definition');
this.route('versions');
this.route('deployments');
this.route('evaluations');
});
});

View File

@ -0,0 +1,19 @@
import Route from '@ember/routing/route';
import { collect } from '@ember/object/computed';
import { watchRelationship } from 'nomad-ui/utils/properties/watch';
import WithWatchers from 'nomad-ui/mixins/with-watchers';
export default Route.extend(WithWatchers, {
model() {
const job = this.modelFor('jobs.job');
return job.get('evaluations').then(() => job);
},
startWatchers(controller, model) {
controller.set('watchEvaluations', this.get('watchEvaluations').perform(model));
},
watchEvaluations: watchRelationship('evaluations'),
watchers: collect('watchEvaluations'),
});

View File

@ -29,6 +29,4 @@
sortProperty=sortProperty
sortDescending=sortDescending
gotoTaskGroup=gotoTaskGroup}}
{{job-page/parts/evaluations job=job}}
{{/job-page/parts/body}}

View File

@ -36,8 +36,6 @@
sortDescending=sortDescending
gotoTaskGroup=gotoTaskGroup}}
{{job-page/parts/evaluations job=job}}
<div class="boxed-section">
<div class="boxed-section-head">Payload</div>
<div class="boxed-section-body is-dark">

View File

@ -1,38 +0,0 @@
<div class="boxed-section-head">
Evaluations
</div>
<div class="boxed-section-body {{if sortedEvaluations.length "is-full-bleed"}} evaluations">
{{#if sortedEvaluations.length}}
{{#list-table source=sortedEvaluations as |t|}}
{{#t.head}}
<th>ID</th>
<th>Priority</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-priority>{{row.model.priority}}</td>
<td data-test-triggered-by>{{row.model.triggeredBy}}</td>
<td data-test-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}}
{{/list-table}}
{{else}}
<div data-test-empty-evaluations-list class="empty-message">
<h3 data-test-empty-evaluations-list-headline class="empty-message-headline">No Evaluations</h3>
<p class="empty-message-body">This is most likely due to garbage collection.</p>
</div>
{{/if}}
</div>

View File

@ -35,6 +35,4 @@
sortProperty=sortProperty
sortDescending=sortDescending
gotoTaskGroup=gotoTaskGroup}}
{{job-page/parts/evaluations job=job}}
{{/job-page/parts/body}}

View File

@ -31,6 +31,4 @@
sortProperty=sortProperty
sortDescending=sortDescending
gotoTaskGroup=gotoTaskGroup}}
{{job-page/parts/evaluations job=job}}
{{/job-page/parts/body}}

View File

@ -29,6 +29,4 @@
sortProperty=sortProperty
sortDescending=sortDescending
gotoTaskGroup=gotoTaskGroup}}
{{job-page/parts/evaluations job=job}}
{{/job-page/parts/body}}

View File

@ -1,3 +1,5 @@
{{#link-to (query-params sortProperty=prop sortDescending=shouldSortDescending)}}
{{#link-to
(query-params sortProperty=prop sortDescending=shouldSortDescending)
data-test-sort-by=prop}}
{{yield}}
{{/link-to}}

View File

@ -0,0 +1,56 @@
{{#global-header class="page-header"}}
{{#each breadcrumbs as |breadcrumb index|}}
<li class="{{if (eq (inc index) breadcrumbs.length) "is-active"}}">
{{#link-to params=breadcrumb.args}}{{breadcrumb.label}}{{/link-to}}
</li>
{{/each}}
{{/global-header}}
{{#gutter-menu class="page-body" onNamespaceChange=(action "gotoJobs")}}
{{partial "jobs/job/subnav"}}
<section class="section">
<div class="boxed-section">
<div class="boxed-section-head">
Evaluations
</div>
<div class="boxed-section-body {{if sortedEvaluations.length "is-full-bleed"}}">
{{#if sortedEvaluations.length}}
{{#list-table
source=sortedEvaluations
sortProperty=sortProperty
sortDescending=sortDescending as |t|}}
{{#t.head}}
<th>ID</th>
{{#t.sort-by prop="priority"}}Priority{{/t.sort-by}}
{{#t.sort-by prop="triggeredBy"}}Triggered By{{/t.sort-by}}
{{#t.sort-by prop="status"}}Status{{/t.sort-by}}
{{#t.sort-by prop="hasPlacementFailures"}}Placement Failures{{/t.sort-by}}
{{/t.head}}
{{#t.body as |row|}}
<tr data-test-evaluation="{{row.model.shortId}}">
<td data-test-id>{{row.model.shortId}}</td>
<td data-test-priority>{{row.model.priority}}</td>
<td data-test-triggered-by>{{row.model.triggeredBy}}</td>
<td data-test-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}}
{{/list-table}}
{{else}}
<div data-test-empty-evaluations-list class="empty-message">
<h3 data-test-empty-evaluations-list-headline class="empty-message-headline">No Evaluations</h3>
<p class="empty-message-body">This is most likely due to garbage collection.</p>
</div>
{{/if}}
</div>
</div>
</section>
{{/gutter-menu}}

View File

@ -6,5 +6,6 @@
{{#if job.supportsDeployments}}
<li data-test-tab="deployments">{{#link-to "jobs.job.deployments" job activeClass="is-active"}}Deployments{{/link-to}}</li>
{{/if}}
<li data-test-tab="evaluations">{{#link-to "jobs.job.evaluations" job activeClass="is-active"}}Evaluations{{/link-to}}</li>
</ul>
</div>

View File

@ -0,0 +1,56 @@
import { test } from 'qunit';
import { findAll, click } from 'ember-native-dom-helpers';
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
let job;
let evaluations;
moduleForAcceptance('Acceptance | job evaluations', {
beforeEach() {
job = server.create('job', { noFailedPlacements: true, createAllocations: false });
evaluations = server.db.evaluations.where({ jobId: job.id });
visit(`/jobs/${job.id}/evaluations`);
},
});
test('lists all evaluations for the job', function(assert) {
const evaluationRows = findAll('[data-test-evaluation]');
assert.equal(evaluationRows.length, evaluations.length, 'All evaluations are listed');
evaluations
.sortBy('modifyIndex')
.reverse()
.forEach((evaluation, index) => {
const shortId = evaluation.id.split('-')[0];
assert.equal(
evaluationRows[index].querySelector('[data-test-id]').textContent.trim(),
shortId,
`Evaluation ${index} is ${shortId}`
);
});
});
test('evaluations table is sortable', function(assert) {
click('[data-test-sort-by="priority"]');
andThen(() => {
assert.equal(
currentURL(),
`/jobs/${job.id}/evaluations?sort=priority`,
'the URL persists the sort parameter'
);
const evaluationRows = findAll('[data-test-evaluation]');
evaluations
.sortBy('priority')
.reverse()
.forEach((evaluation, index) => {
const shortId = evaluation.id.split('-')[0];
assert.equal(
evaluationRows[index].querySelector('[data-test-id]').textContent.trim(),
shortId,
`Evaluation ${index} is ${shortId} with priority ${evaluation.priority}`
);
});
});
});

View File

@ -1,65 +0,0 @@
import { run } from '@ember/runloop';
import { getOwner } from '@ember/application';
import { test, moduleForComponent } from 'ember-qunit';
import { findAll } from 'ember-native-dom-helpers';
import wait from 'ember-test-helpers/wait';
import hbs from 'htmlbars-inline-precompile';
import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
moduleForComponent(
'job-page/parts/evaluations',
'Integration | Component | job-page/parts/evaluations',
{
integration: true,
beforeEach() {
window.localStorage.clear();
this.store = getOwner(this).lookup('service:store');
this.server = startMirage();
this.server.create('namespace');
},
afterEach() {
this.server.shutdown();
window.localStorage.clear();
},
}
);
test('lists all evaluations for the job', function(assert) {
let job;
this.server.create('job', { noFailedPlacements: true, createAllocations: false });
this.store.findAll('job');
return wait().then(() => {
run(() => {
job = this.store.peekAll('job').get('firstObject');
});
this.setProperties({ job });
this.render(hbs`
{{job-page/parts/evaluations job=job}}
`);
return wait().then(() => {
const evaluationRows = findAll('[data-test-evaluation]');
assert.equal(
evaluationRows.length,
job.get('evaluations.length'),
'All evaluations are listed'
);
job
.get('evaluations')
.sortBy('modifyIndex')
.reverse()
.forEach((evaluation, index) => {
assert.equal(
evaluationRows[index].querySelector('[data-test-id]').textContent.trim(),
evaluation.get('shortId'),
`Evaluation ${index} is ${evaluation.get('shortId')}`
);
});
});
});
});