Merge pull request #4529 from hashicorp/f-ui-job-overview-allocs
UI: Recent allocs + job allocs view
This commit is contained in:
commit
4f60b2dc6c
|
@ -10,6 +10,9 @@ module.exports = {
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2017,
|
ecmaVersion: 2017,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
|
ecmaFeatures: {
|
||||||
|
experimentalObjectRestSpread: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
indent: ['error', 2, { SwitchCase: 1 }],
|
indent: ['error', 2, { SwitchCase: 1 }],
|
||||||
|
|
24
ui/app/components/job-page/parts/recent-allocations.js
Normal file
24
ui/app/components/job-page/parts/recent-allocations.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
import PromiseArray from 'nomad-ui/utils/classes/promise-array';
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
sortProperty: 'modifyIndex',
|
||||||
|
sortDescending: true,
|
||||||
|
sortedAllocations: computed('job.allocations.@each.modifyIndex', function() {
|
||||||
|
return new PromiseArray({
|
||||||
|
promise: this.get('job.allocations').then(allocations =>
|
||||||
|
allocations
|
||||||
|
.sortBy('modifyIndex')
|
||||||
|
.reverse()
|
||||||
|
.slice(0, 5)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
gotoAllocation(allocation) {
|
||||||
|
this.transitionToRoute('allocations.allocation', allocation);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
39
ui/app/controllers/jobs/job/allocations.js
Normal file
39
ui/app/controllers/jobs/job/allocations.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { alias } from '@ember/object/computed';
|
||||||
|
import Controller from '@ember/controller';
|
||||||
|
import { computed } from '@ember/object';
|
||||||
|
import Sortable from 'nomad-ui/mixins/sortable';
|
||||||
|
import Searchable from 'nomad-ui/mixins/searchable';
|
||||||
|
import WithNamespaceResetting from 'nomad-ui/mixins/with-namespace-resetting';
|
||||||
|
|
||||||
|
export default Controller.extend(Sortable, Searchable, WithNamespaceResetting, {
|
||||||
|
queryParams: {
|
||||||
|
currentPage: 'page',
|
||||||
|
searchTerm: 'search',
|
||||||
|
sortProperty: 'sort',
|
||||||
|
sortDescending: 'desc',
|
||||||
|
},
|
||||||
|
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 25,
|
||||||
|
|
||||||
|
sortProperty: 'modifyIndex',
|
||||||
|
sortDescending: true,
|
||||||
|
|
||||||
|
job: alias('model'),
|
||||||
|
|
||||||
|
searchProps: computed(() => ['shortId', 'name', 'taskGroupName']),
|
||||||
|
|
||||||
|
allocations: computed('model.allocations.[]', function() {
|
||||||
|
return this.get('model.allocations') || [];
|
||||||
|
}),
|
||||||
|
|
||||||
|
listToSort: alias('allocations'),
|
||||||
|
listToSearch: alias('listSorted'),
|
||||||
|
sortedAllocations: alias('listSearched'),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
gotoAllocation(allocation) {
|
||||||
|
this.transitionToRoute('allocations.allocation', allocation);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -25,9 +25,13 @@ export default Model.extend({
|
||||||
name: attr('string'),
|
name: attr('string'),
|
||||||
taskGroupName: attr('string'),
|
taskGroupName: attr('string'),
|
||||||
resources: fragment('resources'),
|
resources: fragment('resources'),
|
||||||
|
jobVersion: attr('number'),
|
||||||
|
|
||||||
modifyIndex: attr('number'),
|
modifyIndex: attr('number'),
|
||||||
modifyTime: attr('date'),
|
modifyTime: attr('date'),
|
||||||
jobVersion: attr('number'),
|
|
||||||
|
createIndex: attr('number'),
|
||||||
|
createTime: attr('date'),
|
||||||
|
|
||||||
clientStatus: attr('string'),
|
clientStatus: attr('string'),
|
||||||
desiredStatus: attr('string'),
|
desiredStatus: attr('string'),
|
||||||
|
|
|
@ -14,6 +14,7 @@ Router.map(function() {
|
||||||
this.route('versions');
|
this.route('versions');
|
||||||
this.route('deployments');
|
this.route('deployments');
|
||||||
this.route('evaluations');
|
this.route('evaluations');
|
||||||
|
this.route('allocations');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
19
ui/app/routes/jobs/job/allocations.js
Normal file
19
ui/app/routes/jobs/job/allocations.js
Normal 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('allocations').then(() => job);
|
||||||
|
},
|
||||||
|
|
||||||
|
startWatchers(controller, model) {
|
||||||
|
controller.set('watchAllocations', this.get('watchAllocations').perform(model));
|
||||||
|
},
|
||||||
|
|
||||||
|
watchAllocations: watchRelationship('allocations'),
|
||||||
|
|
||||||
|
watchers: collect('watchAllocations'),
|
||||||
|
});
|
|
@ -11,6 +11,7 @@ export default Route.extend(WithWatchers, {
|
||||||
controller.set('watchers', {
|
controller.set('watchers', {
|
||||||
model: this.get('watch').perform(model),
|
model: this.get('watch').perform(model),
|
||||||
summary: this.get('watchSummary').perform(model.get('summary')),
|
summary: this.get('watchSummary').perform(model.get('summary')),
|
||||||
|
allocations: this.get('watchAllocations').perform(model),
|
||||||
evaluations: this.get('watchEvaluations').perform(model),
|
evaluations: this.get('watchEvaluations').perform(model),
|
||||||
latestDeployment:
|
latestDeployment:
|
||||||
model.get('supportsDeployments') && this.get('watchLatestDeployment').perform(model),
|
model.get('supportsDeployments') && this.get('watchLatestDeployment').perform(model),
|
||||||
|
@ -21,6 +22,7 @@ export default Route.extend(WithWatchers, {
|
||||||
watch: watchRecord('job'),
|
watch: watchRecord('job'),
|
||||||
watchAll: watchAll('job'),
|
watchAll: watchAll('job'),
|
||||||
watchSummary: watchRecord('job-summary'),
|
watchSummary: watchRecord('job-summary'),
|
||||||
|
watchAllocations: watchRelationship('allocations'),
|
||||||
watchEvaluations: watchRelationship('evaluations'),
|
watchEvaluations: watchRelationship('evaluations'),
|
||||||
watchLatestDeployment: watchRelationship('latestDeployment'),
|
watchLatestDeployment: watchRelationship('latestDeployment'),
|
||||||
|
|
||||||
|
@ -28,6 +30,7 @@ export default Route.extend(WithWatchers, {
|
||||||
'watch',
|
'watch',
|
||||||
'watchAll',
|
'watchAll',
|
||||||
'watchSummary',
|
'watchSummary',
|
||||||
|
'watchAllocations',
|
||||||
'watchEvaluations',
|
'watchEvaluations',
|
||||||
'watchLatestDeployment'
|
'watchLatestDeployment'
|
||||||
),
|
),
|
||||||
|
|
|
@ -34,6 +34,9 @@ export default ApplicationSerializer.extend({
|
||||||
hash.ModifyTimeNanos = hash.ModifyTime % 1000000;
|
hash.ModifyTimeNanos = hash.ModifyTime % 1000000;
|
||||||
hash.ModifyTime = Math.floor(hash.ModifyTime / 1000000);
|
hash.ModifyTime = Math.floor(hash.ModifyTime / 1000000);
|
||||||
|
|
||||||
|
hash.CreateTimeNanos = hash.CreateTime % 1000000;
|
||||||
|
hash.CreateTime = Math.floor(hash.CreateTime / 1000000);
|
||||||
|
|
||||||
hash.RescheduleEvents = (hash.RescheduleTracker || {}).Events;
|
hash.RescheduleEvents = (hash.RescheduleTracker || {}).Events;
|
||||||
|
|
||||||
// API returns empty strings instead of null
|
// API returns empty strings instead of null
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
@import "./charts/distribution-bar";
|
@import './charts/distribution-bar';
|
||||||
@import "./charts/tooltip";
|
@import './charts/tooltip';
|
||||||
@import "./charts/colors";
|
@import './charts/colors';
|
||||||
|
|
||||||
.inline-chart {
|
.inline-chart {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&.is-small {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patterns are templates referenced by other SVG fill properties.
|
// Patterns are templates referenced by other SVG fill properties.
|
||||||
|
|
|
@ -100,7 +100,7 @@
|
||||||
<th class="is-narrow"></th>
|
<th class="is-narrow"></th>
|
||||||
{{#t.sort-by prop="shortId"}}ID{{/t.sort-by}}
|
{{#t.sort-by prop="shortId"}}ID{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="modifyIndex" title="Modify Index"}}Modified{{/t.sort-by}}
|
{{#t.sort-by prop="modifyIndex" title="Modify Index"}}Modified{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="name"}}Name{{/t.sort-by}}
|
{{#t.sort-by prop="createIndex" title="Create Index"}}Created{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="statusIndex"}}Status{{/t.sort-by}}
|
{{#t.sort-by prop="statusIndex"}}Status{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="job.name"}}Job{{/t.sort-by}}
|
{{#t.sort-by prop="job.name"}}Job{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}}
|
{{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}}
|
||||||
|
|
|
@ -15,12 +15,19 @@
|
||||||
{{allocation.shortId}}
|
{{allocation.shortId}}
|
||||||
{{/link-to}}
|
{{/link-to}}
|
||||||
</td>
|
</td>
|
||||||
<td data-test-modify-time>{{moment-format allocation.modifyTime "MM/DD HH:mm:ss"}}</td>
|
{{#if (eq context "job")}}
|
||||||
<td data-test-name>{{allocation.name}}</td>
|
<td data-test-task-group>
|
||||||
|
{{#link-to "jobs.job.task-group" allocation.job allocation.taskGroupName (query-params jobNamespace=allocation.job.namespace.id)}}
|
||||||
|
{{allocation.taskGroupName}}
|
||||||
|
{{/link-to}}
|
||||||
|
</td>
|
||||||
|
{{/if}}
|
||||||
|
<td data-test-create-time>{{moment-format allocation.createTime "MM/DD HH:mm:ss"}}</td>
|
||||||
|
<td data-test-modify-time>{{moment-from-now allocation.modifyTime}}</td>
|
||||||
<td data-test-client-status class="is-one-line">
|
<td data-test-client-status class="is-one-line">
|
||||||
<span class="color-swatch {{allocation.clientStatus}}" /> {{allocation.clientStatus}}
|
<span class="color-swatch {{allocation.clientStatus}}" /> {{allocation.clientStatus}}
|
||||||
</td>
|
</td>
|
||||||
{{#if (eq context "job")}}
|
{{#if (or (eq context "taskGroup") (eq context "job"))}}
|
||||||
<td data-test-job-version>{{allocation.jobVersion}}</td>
|
<td data-test-job-version>{{allocation.jobVersion}}</td>
|
||||||
<td data-test-client>{{#link-to "clients.client" allocation.node}}{{allocation.node.shortId}}{{/link-to}}</td>
|
<td data-test-client>{{#link-to "clients.client" allocation.node}}{{allocation.node.shortId}}{{/link-to}}</td>
|
||||||
{{else if (eq context "node")}}
|
{{else if (eq context "node")}}
|
||||||
|
@ -32,9 +39,9 @@
|
||||||
<span class="is-faded" data-test-task-group>/ {{allocation.taskGroup.name}}</span>
|
<span class="is-faded" data-test-task-group>/ {{allocation.taskGroup.name}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td data-test-job-version>{{allocation.jobVersion}}</td>
|
<td data-test-job-version class="is-1">{{allocation.jobVersion}}</td>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<td data-test-cpu class="has-text-centered">
|
<td data-test-cpu class="is-1 has-text-centered">
|
||||||
{{#if (and (not stats) fetchStats.isRunning)}}
|
{{#if (and (not stats) fetchStats.isRunning)}}
|
||||||
...
|
...
|
||||||
{{else if (not allocation)}}
|
{{else if (not allocation)}}
|
||||||
|
@ -44,7 +51,7 @@
|
||||||
{{x-icon "warning" class="is-warning"}}
|
{{x-icon "warning" class="is-warning"}}
|
||||||
</span>
|
</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="inline-chart tooltip" aria-label="{{stats.cpuUsed}} / {{stats.reservedCPU}} MHz">
|
<div class="inline-chart is-small tooltip" aria-label="{{stats.cpuUsed}} / {{stats.reservedCPU}} MHz">
|
||||||
<progress
|
<progress
|
||||||
class="progress is-info is-small"
|
class="progress is-info is-small"
|
||||||
value="{{stats.percentCPU}}"
|
value="{{stats.percentCPU}}"
|
||||||
|
@ -54,13 +61,13 @@
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
<td data-test-mem class="has-text-centered">
|
<td data-test-mem class="is-1 has-text-centered">
|
||||||
{{#if (and (not stats) fetchStats.isRunning)}}
|
{{#if (and (not stats) fetchStats.isRunning)}}
|
||||||
...
|
...
|
||||||
{{else if (not allocation)}}
|
{{else if (not allocation)}}
|
||||||
{{! nothing when there's no allocation}}
|
{{! nothing when there's no allocation}}
|
||||||
{{else if statsError}}
|
{{else if statsError}}
|
||||||
<span class="tooltip text-center" aria-label="Couldn't collect stats">
|
<span class="tooltip is-small text-center" aria-label="Couldn't collect stats">
|
||||||
{{x-icon "warning" class="is-warning"}}
|
{{x-icon "warning" class="is-warning"}}
|
||||||
</span>
|
</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
|
@ -9,8 +9,9 @@
|
||||||
{{#t.head}}
|
{{#t.head}}
|
||||||
<th class="is-narrow"></th>
|
<th class="is-narrow"></th>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
<th>Task Group</th>
|
||||||
|
<th>Created</th>
|
||||||
<th>Modified</th>
|
<th>Modified</th>
|
||||||
<th>Name</th>
|
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Version</th>
|
<th>Version</th>
|
||||||
<th>Node</th>
|
<th>Node</th>
|
||||||
|
|
|
@ -22,4 +22,6 @@
|
||||||
sortProperty=sortProperty
|
sortProperty=sortProperty
|
||||||
sortDescending=sortDescending
|
sortDescending=sortDescending
|
||||||
gotoTaskGroup=gotoTaskGroup}}
|
gotoTaskGroup=gotoTaskGroup}}
|
||||||
|
|
||||||
|
{{job-page/parts/recent-allocations job=job}}
|
||||||
{{/job-page/parts/body}}
|
{{/job-page/parts/body}}
|
||||||
|
|
|
@ -29,6 +29,8 @@
|
||||||
sortDescending=sortDescending
|
sortDescending=sortDescending
|
||||||
gotoTaskGroup=gotoTaskGroup}}
|
gotoTaskGroup=gotoTaskGroup}}
|
||||||
|
|
||||||
|
{{job-page/parts/recent-allocations job=job}}
|
||||||
|
|
||||||
<div class="boxed-section">
|
<div class="boxed-section">
|
||||||
<div class="boxed-section-head">Payload</div>
|
<div class="boxed-section-head">Payload</div>
|
||||||
<div class="boxed-section-body is-dark">
|
<div class="boxed-section-body is-dark">
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<div class="boxed-section">
|
||||||
|
<div class="boxed-section-head">
|
||||||
|
Recent Allocations
|
||||||
|
</div>
|
||||||
|
<div class="boxed-section-body is-full-bleed">
|
||||||
|
{{#if job.allocations.length}}
|
||||||
|
{{#list-table
|
||||||
|
source=sortedAllocations
|
||||||
|
sortProperty=sortProperty
|
||||||
|
sortDescending=sortDescending
|
||||||
|
class="with-foot" as |t|}}
|
||||||
|
{{#t.head}}
|
||||||
|
<th class="is-narrow"></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Task Group</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Modified</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th>Client</th>
|
||||||
|
<th>CPU</th>
|
||||||
|
<th>Memory</th>
|
||||||
|
{{/t.head}}
|
||||||
|
{{#t.body as |row|}}
|
||||||
|
{{allocation-row
|
||||||
|
data-test-allocation=row.model.id
|
||||||
|
allocation=row.model
|
||||||
|
context="job"
|
||||||
|
onClick=(action "gotoAllocation" row.model)}}
|
||||||
|
{{/t.body}}
|
||||||
|
{{/list-table}}
|
||||||
|
{{else}}
|
||||||
|
<div class="empty-message" data-test-empty-recent-allocations>
|
||||||
|
<h3 class="empty-message-headline" data-test-empty-recent-allocations-headline>No Allocations</h3>
|
||||||
|
<p class="empty-message-body" data-test-empty-recent-allocations-message>No allocations have been placed.</p>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{#if job.allocations.length}}
|
||||||
|
<div class="boxed-section-foot">
|
||||||
|
<p class="pull-right" data-test-view-all-allocations>{{#link-to "jobs.job.allocations" job}}
|
||||||
|
View all {{job.allocations.length}} {{pluralize "allocation" job.allocations.length}}
|
||||||
|
{{/link-to}}</p>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
|
@ -28,4 +28,6 @@
|
||||||
sortProperty=sortProperty
|
sortProperty=sortProperty
|
||||||
sortDescending=sortDescending
|
sortDescending=sortDescending
|
||||||
gotoTaskGroup=gotoTaskGroup}}
|
gotoTaskGroup=gotoTaskGroup}}
|
||||||
|
|
||||||
|
{{job-page/parts/recent-allocations job=job}}
|
||||||
{{/job-page/parts/body}}
|
{{/job-page/parts/body}}
|
||||||
|
|
|
@ -24,4 +24,6 @@
|
||||||
sortProperty=sortProperty
|
sortProperty=sortProperty
|
||||||
sortDescending=sortDescending
|
sortDescending=sortDescending
|
||||||
gotoTaskGroup=gotoTaskGroup}}
|
gotoTaskGroup=gotoTaskGroup}}
|
||||||
|
|
||||||
|
{{job-page/parts/recent-allocations job=job}}
|
||||||
{{/job-page/parts/body}}
|
{{/job-page/parts/body}}
|
||||||
|
|
|
@ -22,4 +22,6 @@
|
||||||
sortProperty=sortProperty
|
sortProperty=sortProperty
|
||||||
sortDescending=sortDescending
|
sortDescending=sortDescending
|
||||||
gotoTaskGroup=gotoTaskGroup}}
|
gotoTaskGroup=gotoTaskGroup}}
|
||||||
|
|
||||||
|
{{job-page/parts/recent-allocations job=job}}
|
||||||
{{/job-page/parts/body}}
|
{{/job-page/parts/body}}
|
||||||
|
|
71
ui/app/templates/jobs/job/allocations.hbs
Normal file
71
ui/app/templates/jobs/job/allocations.hbs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
{{#gutter-menu class="page-body" onNamespaceChange=(action "gotoJobs")}}
|
||||||
|
{{partial "jobs/job/subnav"}}
|
||||||
|
<section class="section">
|
||||||
|
{{#if allocations.length}}
|
||||||
|
<div class="content">
|
||||||
|
<div>
|
||||||
|
{{search-box
|
||||||
|
data-test-allocations-search
|
||||||
|
searchTerm=(mut searchTerm)
|
||||||
|
placeholder="Search allocations..."}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{#list-pagination
|
||||||
|
source=sortedAllocations
|
||||||
|
size=pageSize
|
||||||
|
page=currentPage
|
||||||
|
class="allocations" as |p|}}
|
||||||
|
{{#list-table
|
||||||
|
source=p.list
|
||||||
|
sortProperty=sortProperty
|
||||||
|
sortDescending=sortDescending
|
||||||
|
class="with-foot" as |t|}}
|
||||||
|
{{#t.head}}
|
||||||
|
<th class="is-narrow"></th>
|
||||||
|
{{#t.sort-by prop="shortId"}}ID{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="taskGroupName"}}Task Group{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="createIndex" title="Create Index"}}Created{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="modifyIndex" title="Modify Index"}}Modified{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="statusIndex"}}Status{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="node.shortId"}}Client{{/t.sort-by}}
|
||||||
|
<th>CPU</th>
|
||||||
|
<th>Memory</th>
|
||||||
|
{{/t.head}}
|
||||||
|
{{#t.body as |row|}}
|
||||||
|
{{allocation-row
|
||||||
|
data-test-allocation=row.model.id
|
||||||
|
allocation=row.model
|
||||||
|
context="job"
|
||||||
|
onClick=(action "gotoAllocation" row.model)}}
|
||||||
|
{{/t.body}}
|
||||||
|
{{/list-table}}
|
||||||
|
<div class="table-foot">
|
||||||
|
<nav class="pagination">
|
||||||
|
<div class="pagination-numbers">
|
||||||
|
{{p.startsAt}}–{{p.endsAt}} of {{sortedAllocations.length}}
|
||||||
|
</div>
|
||||||
|
{{#p.prev class="pagination-previous"}} < {{/p.prev}}
|
||||||
|
{{#p.next class="pagination-next"}} > {{/p.next}}
|
||||||
|
<ul class="pagination-list"></ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="boxed-section-body">
|
||||||
|
<div class="empty-message" data-test-empty-allocations-list>
|
||||||
|
<h3 class="empty-message-headline" data-test-empty-allocations-list-headline>No Matches</h3>
|
||||||
|
<p class="empty-message-body">No allocations match the term <strong>{{searchTerm}}</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/list-pagination}}
|
||||||
|
{{else}}
|
||||||
|
<div class="boxed-section-body">
|
||||||
|
<div class="empty-message" data-test-empty-allocations-list>
|
||||||
|
<h3 class="empty-message-headline" data-test-empty-allocations-list-headline>No Allocations</h3>
|
||||||
|
<p class="empty-message-body">No allocations have been placed.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</section>
|
||||||
|
{{/gutter-menu}}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
{{partial "jobs/job/subnav"}}
|
{{partial "jobs/job/subnav"}}
|
||||||
<section class="section">
|
<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}}
|
{{#if sortedEvaluations.length}}
|
||||||
{{#list-table
|
{{#list-table
|
||||||
source=sortedEvaluations
|
source=sortedEvaluations
|
||||||
|
@ -41,6 +36,4 @@
|
||||||
<p class="empty-message-body">This is most likely due to garbage collection.</p>
|
<p class="empty-message-body">This is most likely due to garbage collection.</p>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
{{#if job.supportsDeployments}}
|
{{#if job.supportsDeployments}}
|
||||||
<li data-test-tab="deployments">{{#link-to "jobs.job.deployments" job activeClass="is-active"}}Deployments{{/link-to}}</li>
|
<li data-test-tab="deployments">{{#link-to "jobs.job.deployments" job activeClass="is-active"}}Deployments{{/link-to}}</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<li data-test-tab="allocations">{{#link-to "jobs.job.allocations" job activeClass="is-active"}}Allocations{{/link-to}}</li>
|
||||||
<li data-test-tab="evaluations">{{#link-to "jobs.job.evaluations" job activeClass="is-active"}}Evaluations{{/link-to}}</li>
|
<li data-test-tab="evaluations">{{#link-to "jobs.job.evaluations" job activeClass="is-active"}}Evaluations{{/link-to}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,8 +63,8 @@
|
||||||
{{#t.head}}
|
{{#t.head}}
|
||||||
<th class="is-narrow"></th>
|
<th class="is-narrow"></th>
|
||||||
{{#t.sort-by prop="shortId"}}ID{{/t.sort-by}}
|
{{#t.sort-by prop="shortId"}}ID{{/t.sort-by}}
|
||||||
|
{{#t.sort-by prop="createIndex" title="Create Index"}}Created{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="modifyIndex" title="Modify Index"}}Modified{{/t.sort-by}}
|
{{#t.sort-by prop="modifyIndex" title="Modify Index"}}Modified{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="name"}}Name{{/t.sort-by}}
|
|
||||||
{{#t.sort-by prop="statusIndex"}}Status{{/t.sort-by}}
|
{{#t.sort-by prop="statusIndex"}}Status{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}}
|
{{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}}
|
||||||
{{#t.sort-by prop="node.shortId"}}Client{{/t.sort-by}}
|
{{#t.sort-by prop="node.shortId"}}Client{{/t.sort-by}}
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
<th>Memory</th>
|
<th>Memory</th>
|
||||||
{{/t.head}}
|
{{/t.head}}
|
||||||
{{#t.body as |row|}}
|
{{#t.body as |row|}}
|
||||||
{{allocation-row data-test-allocation=row.model.id allocation=row.model context="job" onClick=(action "gotoAllocation" row.model)}}
|
{{allocation-row data-test-allocation=row.model.id allocation=row.model context="taskGroup" onClick=(action "gotoAllocation" row.model)}}
|
||||||
{{/t.body}}
|
{{/t.body}}
|
||||||
{{/list-table}}
|
{{/list-table}}
|
||||||
<div class="table-foot">
|
<div class="table-foot">
|
||||||
|
|
4
ui/app/utils/classes/promise-array.js
Normal file
4
ui/app/utils/classes/promise-array.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
import ArrayProxy from '@ember/array/proxy';
|
||||||
|
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
|
||||||
|
|
||||||
|
export default ArrayProxy.extend(PromiseProxyMixin);
|
|
@ -19,6 +19,9 @@ module.exports = function(defaults) {
|
||||||
`${defaults.project.pkg.name}/templates/components/freestyle/**/*`,
|
`${defaults.project.pkg.name}/templates/components/freestyle/**/*`,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
babel: {
|
||||||
|
plugins: ['transform-object-rest-spread'],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Use `app.import` to add additional libraries to the generated
|
// Use `app.import` to add additional libraries to the generated
|
||||||
|
|
|
@ -11,11 +11,16 @@ const REF_TIME = new Date();
|
||||||
export default Factory.extend({
|
export default Factory.extend({
|
||||||
id: i => (i >= 100 ? `${UUIDS[i % 100]}-${i}` : UUIDS[i]),
|
id: i => (i >= 100 ? `${UUIDS[i % 100]}-${i}` : UUIDS[i]),
|
||||||
|
|
||||||
modifyIndex: () => faker.random.number({ min: 10, max: 2000 }),
|
|
||||||
jobVersion: () => faker.random.number(10),
|
jobVersion: () => faker.random.number(10),
|
||||||
|
|
||||||
|
modifyIndex: () => faker.random.number({ min: 10, max: 2000 }),
|
||||||
modifyTime: () => faker.date.past(2 / 365, REF_TIME) * 1000000,
|
modifyTime: () => faker.date.past(2 / 365, REF_TIME) * 1000000,
|
||||||
|
|
||||||
|
createIndex: () => faker.random.number({ min: 10, max: 2000 }),
|
||||||
|
createTime() {
|
||||||
|
return faker.date.past(2 / 365, new Date(this.modifyTime / 1000000)) * 1000000;
|
||||||
|
},
|
||||||
|
|
||||||
namespace: null,
|
namespace: null,
|
||||||
|
|
||||||
clientStatus: faker.list.random(...CLIENT_STATUSES),
|
clientStatus: faker.list.random(...CLIENT_STATUSES),
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||||
"broccoli-asset-rev": "^2.4.5",
|
"broccoli-asset-rev": "^2.4.5",
|
||||||
"bulma": "0.6.1",
|
"bulma": "0.6.1",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
|
|
|
@ -128,13 +128,17 @@ test('each allocation should have high-level details for the allocation', functi
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
const allocationRow = ClientDetail.allocations.objectAt(0);
|
const allocationRow = ClientDetail.allocations.objectAt(0);
|
||||||
|
|
||||||
assert.equal(allocationRow.id, allocation.id.split('-')[0], 'Allocation short ID');
|
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short ID');
|
||||||
|
assert.equal(
|
||||||
|
allocationRow.createTime,
|
||||||
|
moment(allocation.createTime / 1000000).format('MM/DD HH:mm:ss'),
|
||||||
|
'Allocation create time'
|
||||||
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
allocationRow.modifyTime,
|
allocationRow.modifyTime,
|
||||||
moment(allocation.modifyTime / 1000000).format('MM/DD HH:mm:ss'),
|
moment(allocation.modifyTime / 1000000).fromNow(),
|
||||||
'Allocation modify time'
|
'Allocation modify time'
|
||||||
);
|
);
|
||||||
assert.equal(allocationRow.name, allocation.name, 'Allocation name');
|
|
||||||
assert.equal(allocationRow.status, allocation.clientStatus, 'Client status');
|
assert.equal(allocationRow.status, allocation.clientStatus, 'Client status');
|
||||||
assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name');
|
assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name');
|
||||||
assert.ok(allocationRow.taskGroup, 'Task group name');
|
assert.ok(allocationRow.taskGroup, 'Task group name');
|
||||||
|
|
110
ui/tests/acceptance/job-allocations-test.js
Normal file
110
ui/tests/acceptance/job-allocations-test.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { test } from 'qunit';
|
||||||
|
import moduleForAcceptance from 'nomad-ui/tests/helpers/module-for-acceptance';
|
||||||
|
import Allocations from 'nomad-ui/tests/pages/jobs/job/allocations';
|
||||||
|
|
||||||
|
let job;
|
||||||
|
let allocations;
|
||||||
|
|
||||||
|
const makeSearchAllocations = server => {
|
||||||
|
Array(10)
|
||||||
|
.fill(null)
|
||||||
|
.map((_, index) => {
|
||||||
|
server.create('allocation', {
|
||||||
|
id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
moduleForAcceptance('Acceptance | job allocations', {
|
||||||
|
beforeEach() {
|
||||||
|
server.create('node');
|
||||||
|
|
||||||
|
job = server.create('job', { noFailedPlacements: true, createAllocations: false });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lists all allocations for the job', function(assert) {
|
||||||
|
server.createList('allocation', Allocations.pageSize - 1);
|
||||||
|
allocations = server.schema.allocations.where({ jobId: job.id }).models;
|
||||||
|
|
||||||
|
Allocations.visit({ id: job.id });
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
Allocations.allocations.length,
|
||||||
|
Allocations.pageSize - 1,
|
||||||
|
'Allocations are shown in a table'
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortedAllocations = allocations.sortBy('modifyIndex').reverse();
|
||||||
|
|
||||||
|
Allocations.allocations.forEach((allocation, index) => {
|
||||||
|
const shortId = sortedAllocations[index].id.split('-')[0];
|
||||||
|
assert.equal(allocation.shortId, shortId, `Allocation ${index} is ${shortId}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allocations table is sortable', function(assert) {
|
||||||
|
server.createList('allocation', Allocations.pageSize - 1);
|
||||||
|
allocations = server.schema.allocations.where({ jobId: job.id }).models;
|
||||||
|
|
||||||
|
Allocations.visit({ id: job.id });
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
Allocations.sortBy('taskGroupName');
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
currentURL(),
|
||||||
|
`/jobs/${job.id}/allocations?sort=taskGroupName`,
|
||||||
|
'the URL persists the sort parameter'
|
||||||
|
);
|
||||||
|
const sortedAllocations = allocations.sortBy('taskGroup').reverse();
|
||||||
|
Allocations.allocations.forEach((allocation, index) => {
|
||||||
|
const shortId = sortedAllocations[index].id.split('-')[0];
|
||||||
|
assert.equal(
|
||||||
|
allocation.shortId,
|
||||||
|
shortId,
|
||||||
|
`Allocation ${index} is ${shortId} with task group ${sortedAllocations[index].taskGroup}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allocations table is searchable', function(assert) {
|
||||||
|
makeSearchAllocations(server);
|
||||||
|
|
||||||
|
allocations = server.schema.allocations.where({ jobId: job.id }).models;
|
||||||
|
Allocations.visit({ id: job.id });
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
Allocations.search('ffffff');
|
||||||
|
});
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(Allocations.allocations.length, 5, 'List is filtered by search term');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when a search yields no results, the search box remains', function(assert) {
|
||||||
|
makeSearchAllocations(server);
|
||||||
|
|
||||||
|
allocations = server.schema.allocations.where({ jobId: job.id }).models;
|
||||||
|
Allocations.visit({ id: job.id });
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
Allocations.search('^nothing will ever match this long regex$');
|
||||||
|
});
|
||||||
|
|
||||||
|
andThen(() => {
|
||||||
|
assert.equal(
|
||||||
|
Allocations.emptyState.headline,
|
||||||
|
'No Matches',
|
||||||
|
'List is empty and the empty state is about search'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(Allocations.hasSearchBox, 'Search box is still shown');
|
||||||
|
});
|
||||||
|
});
|
|
@ -232,7 +232,7 @@ test('when open, a deployment shows a list of all allocations for the deployment
|
||||||
const allocation = allocations[0];
|
const allocation = allocations[0];
|
||||||
const allocationRow = deploymentRow.allocations.objectAt(0);
|
const allocationRow = deploymentRow.allocations.objectAt(0);
|
||||||
|
|
||||||
assert.equal(allocationRow.id, allocation.id.split('-')[0], 'Allocation is as expected');
|
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation is as expected');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -143,12 +143,16 @@ test('each allocation should show basic information about the allocation', funct
|
||||||
|
|
||||||
andThen(() => {
|
andThen(() => {
|
||||||
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short id');
|
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short id');
|
||||||
|
assert.equal(
|
||||||
|
allocationRow.createTime,
|
||||||
|
moment(allocation.createTime / 1000000).format('MM/DD HH:mm:ss'),
|
||||||
|
'Allocation create time'
|
||||||
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
allocationRow.modifyTime,
|
allocationRow.modifyTime,
|
||||||
moment(allocation.modifyTime / 1000000).format('MM/DD HH:mm:ss'),
|
moment(allocation.modifyTime / 1000000).fromNow(),
|
||||||
'Allocation modify time'
|
'Allocation modify time'
|
||||||
);
|
);
|
||||||
assert.equal(allocationRow.name, allocation.name, 'Allocation name');
|
|
||||||
assert.equal(allocationRow.status, allocation.clientStatus, 'Client status');
|
assert.equal(allocationRow.status, allocation.clientStatus, 'Client status');
|
||||||
assert.equal(allocationRow.jobVersion, allocation.jobVersion, 'Job Version');
|
assert.equal(allocationRow.jobVersion, allocation.jobVersion, 'Job Version');
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
|
|
@ -1,19 +1,23 @@
|
||||||
import { getOwner } from '@ember/application';
|
import { getOwner } from '@ember/application';
|
||||||
|
import { assign } from '@ember/polyfills';
|
||||||
import { test, moduleForComponent } from 'ember-qunit';
|
import { test, moduleForComponent } from 'ember-qunit';
|
||||||
import wait from 'ember-test-helpers/wait';
|
import wait from 'ember-test-helpers/wait';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
|
import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage';
|
||||||
import { stopJob, expectStopError, expectDeleteRequest } from './helpers';
|
import { stopJob, expectStopError, expectDeleteRequest } from './helpers';
|
||||||
|
import Job from 'nomad-ui/tests/pages/jobs/detail';
|
||||||
|
|
||||||
moduleForComponent('job-page/service', 'Integration | Component | job-page/service', {
|
moduleForComponent('job-page/service', 'Integration | Component | job-page/service', {
|
||||||
integration: true,
|
integration: true,
|
||||||
beforeEach() {
|
beforeEach() {
|
||||||
|
Job.setContext(this);
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
this.store = getOwner(this).lookup('service:store');
|
this.store = getOwner(this).lookup('service:store');
|
||||||
this.server = startMirage();
|
this.server = startMirage();
|
||||||
this.server.create('namespace');
|
this.server.create('namespace');
|
||||||
},
|
},
|
||||||
afterEach() {
|
afterEach() {
|
||||||
|
Job.removeContext();
|
||||||
this.server.shutdown();
|
this.server.shutdown();
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
},
|
},
|
||||||
|
@ -36,12 +40,18 @@ const commonProperties = job => ({
|
||||||
gotoJob() {},
|
gotoJob() {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMirageJob = server =>
|
const makeMirageJob = (server, props = {}) =>
|
||||||
server.create('job', {
|
server.create(
|
||||||
|
'job',
|
||||||
|
assign(
|
||||||
|
{
|
||||||
type: 'service',
|
type: 'service',
|
||||||
createAllocations: false,
|
createAllocations: false,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
});
|
},
|
||||||
|
props
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
test('Stopping a job sends a delete request for the job', function(assert) {
|
test('Stopping a job sends a delete request for the job', function(assert) {
|
||||||
let job;
|
let job;
|
||||||
|
@ -80,3 +90,78 @@ test('Stopping a job without proper permissions shows an error message', functio
|
||||||
.then(stopJob)
|
.then(stopJob)
|
||||||
.then(expectStopError(assert));
|
.then(expectStopError(assert));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Recent allocations shows allocations in the job context', function(assert) {
|
||||||
|
let job;
|
||||||
|
|
||||||
|
this.server.create('node');
|
||||||
|
const mirageJob = makeMirageJob(this.server, { createAllocations: true });
|
||||||
|
this.store.findAll('job');
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
.then(() => {
|
||||||
|
job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
||||||
|
|
||||||
|
this.setProperties(commonProperties(job));
|
||||||
|
this.render(commonTemplate);
|
||||||
|
|
||||||
|
return wait();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
const allocation = this.server.db.allocations.sortBy('modifyIndex').reverse()[0];
|
||||||
|
const allocationRow = Job.allocations.objectAt(0);
|
||||||
|
|
||||||
|
assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'ID');
|
||||||
|
assert.equal(allocationRow.taskGroup, allocation.taskGroup, 'Task Group name');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Recent allocations caps out at five', function(assert) {
|
||||||
|
let job;
|
||||||
|
|
||||||
|
this.server.create('node');
|
||||||
|
const mirageJob = makeMirageJob(this.server);
|
||||||
|
this.server.createList('allocation', 10);
|
||||||
|
|
||||||
|
this.store.findAll('job');
|
||||||
|
|
||||||
|
return wait().then(() => {
|
||||||
|
job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
||||||
|
|
||||||
|
this.setProperties(commonProperties(job));
|
||||||
|
this.render(commonTemplate);
|
||||||
|
|
||||||
|
return wait().then(() => {
|
||||||
|
assert.equal(Job.allocations.length, 5, 'Capped at 5 allocations');
|
||||||
|
assert.ok(
|
||||||
|
Job.viewAllAllocations.includes(job.get('allocations.length') + ''),
|
||||||
|
`View link mentions ${job.get('allocations.length')} allocations`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Recent allocations shows an empty message when the job has no allocations', function(assert) {
|
||||||
|
let job;
|
||||||
|
|
||||||
|
this.server.create('node');
|
||||||
|
const mirageJob = makeMirageJob(this.server);
|
||||||
|
|
||||||
|
this.store.findAll('job');
|
||||||
|
|
||||||
|
return wait()
|
||||||
|
.then(() => {
|
||||||
|
job = this.store.peekAll('job').findBy('plainId', mirageJob.id);
|
||||||
|
|
||||||
|
this.setProperties(commonProperties(job));
|
||||||
|
this.render(commonTemplate);
|
||||||
|
|
||||||
|
return wait();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
assert.ok(
|
||||||
|
Job.recentAllocationsEmptyState.headline.includes('No Allocations'),
|
||||||
|
'No allocations empty message'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -9,6 +9,8 @@ import {
|
||||||
visitable,
|
visitable,
|
||||||
} from 'ember-cli-page-object';
|
} from 'ember-cli-page-object';
|
||||||
|
|
||||||
|
import allocations from 'nomad-ui/tests/pages/components/allocations';
|
||||||
|
|
||||||
export default create({
|
export default create({
|
||||||
visit: visitable('/clients/:id'),
|
visit: visitable('/clients/:id'),
|
||||||
|
|
||||||
|
@ -36,22 +38,7 @@ export default create({
|
||||||
eligibilityDefinition: text('[data-test-eligibility]'),
|
eligibilityDefinition: text('[data-test-eligibility]'),
|
||||||
datacenterDefinition: text('[data-test-datacenter-definition]'),
|
datacenterDefinition: text('[data-test-datacenter-definition]'),
|
||||||
|
|
||||||
allocations: collection('[data-test-allocation]', {
|
...allocations(),
|
||||||
id: text('[data-test-short-id]'),
|
|
||||||
modifyTime: text('[data-test-modify-time]'),
|
|
||||||
name: text('[data-test-name]'),
|
|
||||||
status: text('[data-test-client-status]'),
|
|
||||||
job: text('[data-test-job]'),
|
|
||||||
taskGroup: text('[data-test-task-group]'),
|
|
||||||
jobVersion: text('[data-test-job-version]'),
|
|
||||||
cpu: text('[data-test-cpu]'),
|
|
||||||
cpuTooltip: attribute('aria-label', '[data-test-cpu] .tooltip'),
|
|
||||||
mem: text('[data-test-mem]'),
|
|
||||||
memTooltip: attribute('aria-label', '[data-test-mem] .tooltip'),
|
|
||||||
|
|
||||||
visit: clickable('[data-test-short-id] a'),
|
|
||||||
visitJob: clickable('[data-test-job]'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
attributesTable: isPresent('[data-test-attributes]'),
|
attributesTable: isPresent('[data-test-attributes]'),
|
||||||
metaTable: isPresent('[data-test-meta]'),
|
metaTable: isPresent('[data-test-meta]'),
|
||||||
|
|
30
ui/tests/pages/components/allocations.js
Normal file
30
ui/tests/pages/components/allocations.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { attribute, collection, clickable, isPresent, text } from 'ember-cli-page-object';
|
||||||
|
|
||||||
|
export default function(selector = '[data-test-allocation]') {
|
||||||
|
return {
|
||||||
|
allocations: collection(selector, {
|
||||||
|
id: attribute('data-test-allocation'),
|
||||||
|
shortId: text('[data-test-short-id]'),
|
||||||
|
createTime: text('[data-test-create-time]'),
|
||||||
|
modifyTime: text('[data-test-modify-time]'),
|
||||||
|
status: text('[data-test-client-status]'),
|
||||||
|
job: text('[data-test-job]'),
|
||||||
|
taskGroup: text('[data-test-task-group]'),
|
||||||
|
client: text('[data-test-client]'),
|
||||||
|
jobVersion: text('[data-test-job-version]'),
|
||||||
|
cpu: text('[data-test-cpu]'),
|
||||||
|
cpuTooltip: attribute('aria-label', '[data-test-cpu] .tooltip'),
|
||||||
|
mem: text('[data-test-mem]'),
|
||||||
|
memTooltip: attribute('aria-label', '[data-test-mem] .tooltip'),
|
||||||
|
rescheduled: isPresent('[data-test-indicators] [data-test-icon="reschedule"]'),
|
||||||
|
|
||||||
|
visit: clickable('[data-test-short-id] a'),
|
||||||
|
visitJob: clickable('[data-test-job]'),
|
||||||
|
visitClient: clickable('[data-test-client] a'),
|
||||||
|
}),
|
||||||
|
|
||||||
|
allocationFor(id) {
|
||||||
|
return this.allocations.toArray().find(allocation => allocation.id === id);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -8,6 +8,8 @@ import {
|
||||||
visitable,
|
visitable,
|
||||||
} from 'ember-cli-page-object';
|
} from 'ember-cli-page-object';
|
||||||
|
|
||||||
|
import allocations from 'nomad-ui/tests/pages/components/allocations';
|
||||||
|
|
||||||
export default create({
|
export default create({
|
||||||
visit: visitable('/jobs/:id'),
|
visit: visitable('/jobs/:id'),
|
||||||
|
|
||||||
|
@ -29,10 +31,18 @@ export default create({
|
||||||
return this.stats.toArray().findBy('id', id);
|
return this.stats.toArray().findBy('id', id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
...allocations(),
|
||||||
|
|
||||||
|
viewAllAllocations: text('[data-test-view-all-allocations]'),
|
||||||
|
|
||||||
error: {
|
error: {
|
||||||
isPresent: isPresent('[data-test-error]'),
|
isPresent: isPresent('[data-test-error]'),
|
||||||
title: text('[data-test-error-title]'),
|
title: text('[data-test-error-title]'),
|
||||||
message: text('[data-test-error-message]'),
|
message: text('[data-test-error-message]'),
|
||||||
seekHelp: clickable('[data-test-error-message] a'),
|
seekHelp: clickable('[data-test-error-message] a'),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
recentAllocationsEmptyState: {
|
||||||
|
headline: text('[data-test-empty-recent-allocations-headline]'),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
40
ui/tests/pages/jobs/job/allocations.js
Normal file
40
ui/tests/pages/jobs/job/allocations.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {
|
||||||
|
attribute,
|
||||||
|
clickable,
|
||||||
|
create,
|
||||||
|
collection,
|
||||||
|
fillable,
|
||||||
|
isPresent,
|
||||||
|
text,
|
||||||
|
visitable,
|
||||||
|
} from 'ember-cli-page-object';
|
||||||
|
|
||||||
|
import allocations from 'nomad-ui/tests/pages/components/allocations';
|
||||||
|
|
||||||
|
export default create({
|
||||||
|
visit: visitable('/jobs/:id/allocations'),
|
||||||
|
|
||||||
|
pageSize: 25,
|
||||||
|
|
||||||
|
hasSearchBox: isPresent('[data-test-allocations-search]'),
|
||||||
|
search: fillable('[data-test-allocations-search] input'),
|
||||||
|
|
||||||
|
...allocations(),
|
||||||
|
|
||||||
|
isEmpty: isPresent('[data-test-empty-allocations-list]'),
|
||||||
|
emptyState: {
|
||||||
|
headline: text('[data-test-empty-allocations-list-headline]'),
|
||||||
|
},
|
||||||
|
|
||||||
|
sortOptions: collection('[data-test-sort-by]', {
|
||||||
|
id: attribute('data-test-sort-by'),
|
||||||
|
sort: clickable(),
|
||||||
|
}),
|
||||||
|
|
||||||
|
sortBy(id) {
|
||||||
|
return this.sortOptions
|
||||||
|
.toArray()
|
||||||
|
.findBy('id', id)
|
||||||
|
.sort();
|
||||||
|
},
|
||||||
|
});
|
|
@ -8,6 +8,8 @@ import {
|
||||||
visitable,
|
visitable,
|
||||||
} from 'ember-cli-page-object';
|
} from 'ember-cli-page-object';
|
||||||
|
|
||||||
|
import allocations from 'nomad-ui/tests/pages/components/allocations';
|
||||||
|
|
||||||
export default create({
|
export default create({
|
||||||
visit: visitable('/jobs/:id/deployments'),
|
visit: visitable('/jobs/:id/deployments'),
|
||||||
|
|
||||||
|
@ -46,9 +48,7 @@ export default create({
|
||||||
progress: text('[data-test-deployment-task-group-progress-deadline]'),
|
progress: text('[data-test-deployment-task-group-progress-deadline]'),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
...allocations('[data-test-deployment-allocation]'),
|
||||||
hasAllocations: isPresent('[data-test-deployment-allocations]'),
|
hasAllocations: isPresent('[data-test-deployment-allocations]'),
|
||||||
allocations: collection('[data-test-deployment-allocation]', {
|
|
||||||
id: text('[data-test-short-id]'),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,8 @@ import {
|
||||||
visitable,
|
visitable,
|
||||||
} from 'ember-cli-page-object';
|
} from 'ember-cli-page-object';
|
||||||
|
|
||||||
|
import allocations from 'nomad-ui/tests/pages/components/allocations';
|
||||||
|
|
||||||
export default create({
|
export default create({
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
|
|
||||||
|
@ -31,27 +33,7 @@ export default create({
|
||||||
return this.breadcrumbs.toArray().find(crumb => crumb.id === id);
|
return this.breadcrumbs.toArray().find(crumb => crumb.id === id);
|
||||||
},
|
},
|
||||||
|
|
||||||
allocations: collection('[data-test-allocation]', {
|
...allocations(),
|
||||||
id: attribute('data-test-allocation'),
|
|
||||||
shortId: text('[data-test-short-id]'),
|
|
||||||
modifyTime: text('[data-test-modify-time]'),
|
|
||||||
name: text('[data-test-name]'),
|
|
||||||
status: text('[data-test-client-status]'),
|
|
||||||
jobVersion: text('[data-test-job-version]'),
|
|
||||||
client: text('[data-test-client]'),
|
|
||||||
cpu: text('[data-test-cpu]'),
|
|
||||||
cpuTooltip: attribute('aria-label', '[data-test-cpu] .tooltip'),
|
|
||||||
mem: text('[data-test-mem]'),
|
|
||||||
memTooltip: attribute('aria-label', '[data-test-mem] .tooltip'),
|
|
||||||
rescheduled: isPresent('[data-test-indicators] [data-test-icon="reschedule"]'),
|
|
||||||
|
|
||||||
visit: clickable('[data-test-short-id] a'),
|
|
||||||
visitClient: clickable('[data-test-client] a'),
|
|
||||||
}),
|
|
||||||
|
|
||||||
allocationFor(id) {
|
|
||||||
return this.allocations.toArray().find(allocation => allocation.id === id);
|
|
||||||
},
|
|
||||||
|
|
||||||
isEmpty: isPresent('[data-test-empty-allocations-list]'),
|
isEmpty: isPresent('[data-test-empty-allocations-list]'),
|
||||||
|
|
||||||
|
|
11
ui/yarn.lock
11
ui/yarn.lock
|
@ -854,6 +854,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0:
|
||||||
version "6.13.0"
|
version "6.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
|
||||||
|
|
||||||
|
babel-plugin-syntax-object-rest-spread@^6.8.0:
|
||||||
|
version "6.13.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5"
|
||||||
|
|
||||||
babel-plugin-syntax-trailing-function-commas@^6.22.0:
|
babel-plugin-syntax-trailing-function-commas@^6.22.0:
|
||||||
version "6.22.0"
|
version "6.22.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
|
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
|
||||||
|
@ -1042,6 +1046,13 @@ babel-plugin-transform-exponentiation-operator@^6.22.0:
|
||||||
babel-plugin-syntax-exponentiation-operator "^6.8.0"
|
babel-plugin-syntax-exponentiation-operator "^6.8.0"
|
||||||
babel-runtime "^6.22.0"
|
babel-runtime "^6.22.0"
|
||||||
|
|
||||||
|
babel-plugin-transform-object-rest-spread@^6.26.0:
|
||||||
|
version "6.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06"
|
||||||
|
dependencies:
|
||||||
|
babel-plugin-syntax-object-rest-spread "^6.8.0"
|
||||||
|
babel-runtime "^6.26.0"
|
||||||
|
|
||||||
babel-plugin-transform-regenerator@^6.22.0:
|
babel-plugin-transform-regenerator@^6.22.0:
|
||||||
version "6.26.0"
|
version "6.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
|
resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
|
||||||
|
|
Loading…
Reference in a new issue