From 81255bee1685638fa815d8493e0f50239b7af6b4 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 13:31:39 -0700 Subject: [PATCH 01/17] Add createTime to the allocation model --- ui/app/models/allocation.js | 6 +++++- ui/app/serializers/allocation.js | 3 +++ ui/mirage/factories/allocation.js | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ui/app/models/allocation.js b/ui/app/models/allocation.js index b65232ce2338..f98e3fc22fae 100644 --- a/ui/app/models/allocation.js +++ b/ui/app/models/allocation.js @@ -25,9 +25,13 @@ export default Model.extend({ name: attr('string'), taskGroupName: attr('string'), resources: fragment('resources'), + jobVersion: attr('number'), + modifyIndex: attr('number'), modifyTime: attr('date'), - jobVersion: attr('number'), + + createIndex: attr('number'), + createTime: attr('date'), clientStatus: attr('string'), desiredStatus: attr('string'), diff --git a/ui/app/serializers/allocation.js b/ui/app/serializers/allocation.js index 7cbd76a18864..2aa91d39f073 100644 --- a/ui/app/serializers/allocation.js +++ b/ui/app/serializers/allocation.js @@ -34,6 +34,9 @@ export default ApplicationSerializer.extend({ hash.ModifyTimeNanos = 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; // API returns empty strings instead of null diff --git a/ui/mirage/factories/allocation.js b/ui/mirage/factories/allocation.js index 3336a9c1588a..3eeac22ca53a 100644 --- a/ui/mirage/factories/allocation.js +++ b/ui/mirage/factories/allocation.js @@ -11,11 +11,16 @@ const REF_TIME = new Date(); export default Factory.extend({ id: i => (i >= 100 ? `${UUIDS[i % 100]}-${i}` : UUIDS[i]), - modifyIndex: () => faker.random.number({ min: 10, max: 2000 }), jobVersion: () => faker.random.number(10), + modifyIndex: () => faker.random.number({ min: 10, max: 2000 }), 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, clientStatus: faker.list.random(...CLIENT_STATUSES), From 137fbccb3de6493522154372eae70e9cab71455c Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 13:30:08 -0700 Subject: [PATCH 02/17] Remove the name column and add a created column to allocation rows --- ui/app/templates/clients/client.hbs | 2 +- ui/app/templates/components/allocation-row.hbs | 4 ++-- .../components/job-deployment/deployment-allocations.hbs | 2 +- ui/app/templates/jobs/job/task-group.hbs | 2 +- ui/tests/acceptance/client-detail-test.js | 8 ++++++-- ui/tests/acceptance/task-group-detail-test.js | 8 ++++++-- ui/tests/pages/clients/detail.js | 2 +- ui/tests/pages/jobs/job/task-group.js | 2 +- 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/ui/app/templates/clients/client.hbs b/ui/app/templates/clients/client.hbs index b6e2bcf9ffef..3b84f643e044 100644 --- a/ui/app/templates/clients/client.hbs +++ b/ui/app/templates/clients/client.hbs @@ -100,7 +100,7 @@ {{#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="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="job.name"}}Job{{/t.sort-by}} {{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}} diff --git a/ui/app/templates/components/allocation-row.hbs b/ui/app/templates/components/allocation-row.hbs index 1da565ff5fb9..1fe00199000e 100644 --- a/ui/app/templates/components/allocation-row.hbs +++ b/ui/app/templates/components/allocation-row.hbs @@ -15,8 +15,8 @@ {{allocation.shortId}} {{/link-to}} -{{moment-format allocation.modifyTime "MM/DD HH:mm:ss"}} -{{allocation.name}} +{{moment-format allocation.createTime "MM/DD HH:mm:ss"}} +{{moment-from-now allocation.modifyTime}} {{allocation.clientStatus}} diff --git a/ui/app/templates/components/job-deployment/deployment-allocations.hbs b/ui/app/templates/components/job-deployment/deployment-allocations.hbs index 24242723879b..cda6afb0e763 100644 --- a/ui/app/templates/components/job-deployment/deployment-allocations.hbs +++ b/ui/app/templates/components/job-deployment/deployment-allocations.hbs @@ -9,8 +9,8 @@ {{#t.head}} ID + Created Modified - Name Status Version Node diff --git a/ui/app/templates/jobs/job/task-group.hbs b/ui/app/templates/jobs/job/task-group.hbs index 0bf01cb84ca9..1ae35542ca17 100644 --- a/ui/app/templates/jobs/job/task-group.hbs +++ b/ui/app/templates/jobs/job/task-group.hbs @@ -63,8 +63,8 @@ {{#t.head}} {{#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="name"}}Name{{/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}} diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index cf260833d093..df58c32ea884 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -129,12 +129,16 @@ test('each allocation should have high-level details for the allocation', functi const allocationRow = ClientDetail.allocations.objectAt(0); assert.equal(allocationRow.id, 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( allocationRow.modifyTime, - moment(allocation.modifyTime / 1000000).format('MM/DD HH:mm:ss'), + moment(allocation.modifyTime / 1000000).fromNow(), 'Allocation modify time' ); - assert.equal(allocationRow.name, allocation.name, 'Allocation name'); assert.equal(allocationRow.status, allocation.clientStatus, 'Client status'); assert.equal(allocationRow.job, server.db.jobs.find(allocation.jobId).name, 'Job name'); assert.ok(allocationRow.taskGroup, 'Task group name'); diff --git a/ui/tests/acceptance/task-group-detail-test.js b/ui/tests/acceptance/task-group-detail-test.js index 2466e6da952c..cccdd67d71e8 100644 --- a/ui/tests/acceptance/task-group-detail-test.js +++ b/ui/tests/acceptance/task-group-detail-test.js @@ -143,12 +143,16 @@ test('each allocation should show basic information about the allocation', funct andThen(() => { 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( allocationRow.modifyTime, - moment(allocation.modifyTime / 1000000).format('MM/DD HH:mm:ss'), + moment(allocation.modifyTime / 1000000).fromNow(), 'Allocation modify time' ); - assert.equal(allocationRow.name, allocation.name, 'Allocation name'); assert.equal(allocationRow.status, allocation.clientStatus, 'Client status'); assert.equal(allocationRow.jobVersion, allocation.jobVersion, 'Job Version'); assert.equal( diff --git a/ui/tests/pages/clients/detail.js b/ui/tests/pages/clients/detail.js index d67e42c53d00..a46fd85bc376 100644 --- a/ui/tests/pages/clients/detail.js +++ b/ui/tests/pages/clients/detail.js @@ -38,8 +38,8 @@ export default create({ allocations: collection('[data-test-allocation]', { id: text('[data-test-short-id]'), + createTime: text('[data-test-create-time]'), 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]'), diff --git a/ui/tests/pages/jobs/job/task-group.js b/ui/tests/pages/jobs/job/task-group.js index b1203e89323c..9a7db9056b12 100644 --- a/ui/tests/pages/jobs/job/task-group.js +++ b/ui/tests/pages/jobs/job/task-group.js @@ -34,8 +34,8 @@ export default create({ allocations: collection('[data-test-allocation]', { id: attribute('data-test-allocation'), shortId: text('[data-test-short-id]'), + createTime: text('[data-test-create-time]'), 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]'), From bc41b23a13d228621a6008fbe790ef7dc835ced1 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 13:31:02 -0700 Subject: [PATCH 03/17] Make sure memory and cpu bars have a consistent size --- ui/app/styles/charts.scss | 10 +++++++--- ui/app/templates/components/allocation-row.hbs | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ui/app/styles/charts.scss b/ui/app/styles/charts.scss index e7073865079e..3db77a93b90f 100644 --- a/ui/app/styles/charts.scss +++ b/ui/app/styles/charts.scss @@ -1,11 +1,15 @@ -@import "./charts/distribution-bar"; -@import "./charts/tooltip"; -@import "./charts/colors"; +@import './charts/distribution-bar'; +@import './charts/tooltip'; +@import './charts/colors'; .inline-chart { height: 1.5rem; display: flex; align-items: center; + + &.is-small { + width: 50px; + } } // Patterns are templates referenced by other SVG fill properties. diff --git a/ui/app/templates/components/allocation-row.hbs b/ui/app/templates/components/allocation-row.hbs index 1fe00199000e..6dc6beaa8196 100644 --- a/ui/app/templates/components/allocation-row.hbs +++ b/ui/app/templates/components/allocation-row.hbs @@ -32,9 +32,9 @@ / {{allocation.taskGroup.name}} {{/if}} - {{allocation.jobVersion}} + {{allocation.jobVersion}} {{/if}} - + {{#if (and (not stats) fetchStats.isRunning)}} ... {{else if (not allocation)}} @@ -44,7 +44,7 @@ {{x-icon "warning" class="is-warning"}} {{else}} -
+
{{/if}} - + {{#if (and (not stats) fetchStats.isRunning)}} ... {{else if (not allocation)}} {{! nothing when there's no allocation}} {{else if statsError}} - + {{x-icon "warning" class="is-warning"}} {{else}} From 221881e502078722bddd14974bb1b1d9a06222e4 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 16:08:51 -0700 Subject: [PATCH 04/17] Add recent allocations to relevant job overview pages --- .../job-page/parts/recent-allocations.js | 19 +++++++++ .../templates/components/job-page/batch.hbs | 2 + .../job-page/parameterized-child.hbs | 2 + .../job-page/parts/recent-allocations.hbs | 40 +++++++++++++++++++ .../components/job-page/periodic-child.hbs | 2 + .../templates/components/job-page/service.hbs | 2 + .../templates/components/job-page/system.hbs | 2 + 7 files changed, 69 insertions(+) create mode 100644 ui/app/components/job-page/parts/recent-allocations.js create mode 100644 ui/app/templates/components/job-page/parts/recent-allocations.hbs diff --git a/ui/app/components/job-page/parts/recent-allocations.js b/ui/app/components/job-page/parts/recent-allocations.js new file mode 100644 index 000000000000..d7d2ce0a2e41 --- /dev/null +++ b/ui/app/components/job-page/parts/recent-allocations.js @@ -0,0 +1,19 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +export default Component.extend({ + sortProperty: 'modifyIndex', + sortDescending: true, + sortedAllocations: computed('job.allocations.@each.modifyIndex', function() { + return this.get('job.allocations') + .sortBy('modifyIndex') + .reverse() + .slice(0, 5); + }), + + actions: { + gotoAllocation(allocation) { + this.transitionToRoute('allocations.allocation', allocation); + }, + }, +}); diff --git a/ui/app/templates/components/job-page/batch.hbs b/ui/app/templates/components/job-page/batch.hbs index f11a778496c2..dcd1ed7bef98 100644 --- a/ui/app/templates/components/job-page/batch.hbs +++ b/ui/app/templates/components/job-page/batch.hbs @@ -22,4 +22,6 @@ sortProperty=sortProperty sortDescending=sortDescending gotoTaskGroup=gotoTaskGroup}} + + {{job-page/parts/recent-allocations job=job}} {{/job-page/parts/body}} diff --git a/ui/app/templates/components/job-page/parameterized-child.hbs b/ui/app/templates/components/job-page/parameterized-child.hbs index a5cbfb84646b..b20362263e93 100644 --- a/ui/app/templates/components/job-page/parameterized-child.hbs +++ b/ui/app/templates/components/job-page/parameterized-child.hbs @@ -29,6 +29,8 @@ sortDescending=sortDescending gotoTaskGroup=gotoTaskGroup}} + {{job-page/parts/recent-allocations job=job}} +
Payload
diff --git a/ui/app/templates/components/job-page/parts/recent-allocations.hbs b/ui/app/templates/components/job-page/parts/recent-allocations.hbs new file mode 100644 index 000000000000..49e8da98bbbb --- /dev/null +++ b/ui/app/templates/components/job-page/parts/recent-allocations.hbs @@ -0,0 +1,40 @@ +
+
+ Recent Allocations +
+
+ {{#if job.allocations.length}} + {{#list-table + source=sortedAllocations + sortProperty=sortProperty + sortDescending=sortDescending + class="with-foot" as |t|}} + {{#t.head}} + + ID + Created + Modified + Status + Version + Client + CPU + Memory + {{/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}} +
+

No Allocations

+

No allocations have been placed.

+
+ {{/if}} +
+
diff --git a/ui/app/templates/components/job-page/periodic-child.hbs b/ui/app/templates/components/job-page/periodic-child.hbs index 2accceb84c1e..69839750331c 100644 --- a/ui/app/templates/components/job-page/periodic-child.hbs +++ b/ui/app/templates/components/job-page/periodic-child.hbs @@ -28,4 +28,6 @@ sortProperty=sortProperty sortDescending=sortDescending gotoTaskGroup=gotoTaskGroup}} + + {{job-page/parts/recent-allocations job=job}} {{/job-page/parts/body}} diff --git a/ui/app/templates/components/job-page/service.hbs b/ui/app/templates/components/job-page/service.hbs index da7af5d5d48a..6a921beb8bb6 100644 --- a/ui/app/templates/components/job-page/service.hbs +++ b/ui/app/templates/components/job-page/service.hbs @@ -24,4 +24,6 @@ sortProperty=sortProperty sortDescending=sortDescending gotoTaskGroup=gotoTaskGroup}} + + {{job-page/parts/recent-allocations job=job}} {{/job-page/parts/body}} diff --git a/ui/app/templates/components/job-page/system.hbs b/ui/app/templates/components/job-page/system.hbs index f11a778496c2..dcd1ed7bef98 100644 --- a/ui/app/templates/components/job-page/system.hbs +++ b/ui/app/templates/components/job-page/system.hbs @@ -22,4 +22,6 @@ sortProperty=sortProperty sortDescending=sortDescending gotoTaskGroup=gotoTaskGroup}} + + {{job-page/parts/recent-allocations job=job}} {{/job-page/parts/body}} From d250b2a54aaa28d13bbdc3e148d089a996a78d18 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 16:57:44 -0700 Subject: [PATCH 05/17] New dedicated allocations page for jobs --- ui/app/controllers/jobs/job/allocations.js | 39 +++++++++++++ ui/app/router.js | 1 + ui/app/routes/jobs/job/allocations.js | 19 +++++++ ui/app/templates/jobs/job/allocations.hbs | 65 ++++++++++++++++++++++ ui/app/templates/jobs/job/subnav.hbs | 1 + 5 files changed, 125 insertions(+) create mode 100644 ui/app/controllers/jobs/job/allocations.js create mode 100644 ui/app/routes/jobs/job/allocations.js create mode 100644 ui/app/templates/jobs/job/allocations.hbs diff --git a/ui/app/controllers/jobs/job/allocations.js b/ui/app/controllers/jobs/job/allocations.js new file mode 100644 index 000000000000..efed1ef2b44f --- /dev/null +++ b/ui/app/controllers/jobs/job/allocations.js @@ -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']), + + 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); + }, + }, +}); diff --git a/ui/app/router.js b/ui/app/router.js index ce0a1bc744d6..39446ffa9583 100644 --- a/ui/app/router.js +++ b/ui/app/router.js @@ -14,6 +14,7 @@ Router.map(function() { this.route('versions'); this.route('deployments'); this.route('evaluations'); + this.route('allocations'); }); }); diff --git a/ui/app/routes/jobs/job/allocations.js b/ui/app/routes/jobs/job/allocations.js new file mode 100644 index 000000000000..da59c95a4e04 --- /dev/null +++ b/ui/app/routes/jobs/job/allocations.js @@ -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'), +}); diff --git a/ui/app/templates/jobs/job/allocations.hbs b/ui/app/templates/jobs/job/allocations.hbs new file mode 100644 index 000000000000..ad53ba6d2bb6 --- /dev/null +++ b/ui/app/templates/jobs/job/allocations.hbs @@ -0,0 +1,65 @@ +{{#gutter-menu class="page-body" onNamespaceChange=(action "gotoJobs")}} + {{partial "jobs/job/subnav"}} +
+
+
+ Allocations +
+
+ {{#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}} + + {{#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="statusIndex"}}Status{{/t.sort-by}} + {{#t.sort-by prop="jobVersion"}}Version{{/t.sort-by}} + {{#t.sort-by prop="node.shortId"}}Client{{/t.sort-by}} + CPU + Memory + {{/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}} + {{#if allocations.length}} +
+
+

No Matches

+

No allocations match the term {{searchTerm}}

+
+
+ {{else}} +
+
+

No Allocations

+

No allocations have been placed.

+
+
+ {{/if}} + {{/list-pagination}} +
+
+
+{{/gutter-menu}} + diff --git a/ui/app/templates/jobs/job/subnav.hbs b/ui/app/templates/jobs/job/subnav.hbs index 257072649f0a..fa37382051f3 100644 --- a/ui/app/templates/jobs/job/subnav.hbs +++ b/ui/app/templates/jobs/job/subnav.hbs @@ -6,6 +6,7 @@ {{#if job.supportsDeployments}}
  • {{#link-to "jobs.job.deployments" job activeClass="is-active"}}Deployments{{/link-to}}
  • {{/if}} +
  • {{#link-to "jobs.job.allocations" job activeClass="is-active"}}Allocations{{/link-to}}
  • {{#link-to "jobs.job.evaluations" job activeClass="is-active"}}Evaluations{{/link-to}}
  • From 77e7d8edab3a511817a45426cc5abde774e514aa Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 17:24:36 -0700 Subject: [PATCH 06/17] Use the new taskGroup context for allocation row --- ui/app/templates/components/allocation-row.hbs | 9 ++++++++- .../components/job-page/parts/recent-allocations.hbs | 3 ++- ui/app/templates/jobs/job/allocations.hbs | 7 ++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ui/app/templates/components/allocation-row.hbs b/ui/app/templates/components/allocation-row.hbs index 6dc6beaa8196..734459981e1a 100644 --- a/ui/app/templates/components/allocation-row.hbs +++ b/ui/app/templates/components/allocation-row.hbs @@ -15,12 +15,19 @@ {{allocation.shortId}} {{/link-to}} +{{#if (eq context "taskGroup")}} + + {{#link-to "jobs.job.task-group" allocation.job allocation.taskGroupName (query-params jobNamespace=allocation.job.namespace.id)}} + {{allocation.taskGroupName}} + {{/link-to}} + +{{/if}} {{moment-format allocation.createTime "MM/DD HH:mm:ss"}} {{moment-from-now allocation.modifyTime}} {{allocation.clientStatus}} -{{#if (eq context "job")}} +{{#if (or (eq context "job") (eq context "taskGroup"))}} {{allocation.jobVersion}} {{#link-to "clients.client" allocation.node}}{{allocation.node.shortId}}{{/link-to}} {{else if (eq context "node")}} diff --git a/ui/app/templates/components/job-page/parts/recent-allocations.hbs b/ui/app/templates/components/job-page/parts/recent-allocations.hbs index 49e8da98bbbb..7eb9bc8d7cbf 100644 --- a/ui/app/templates/components/job-page/parts/recent-allocations.hbs +++ b/ui/app/templates/components/job-page/parts/recent-allocations.hbs @@ -12,6 +12,7 @@ {{#t.head}} ID + Task Group Created Modified Status @@ -24,7 +25,7 @@ {{allocation-row data-test-allocation=row.model.id allocation=row.model - context="job" + context="taskGroup" onClick=(action "gotoAllocation" row.model)}} {{/t.body}} {{/list-table}} diff --git a/ui/app/templates/jobs/job/allocations.hbs b/ui/app/templates/jobs/job/allocations.hbs index ad53ba6d2bb6..b4b1cd6b3134 100644 --- a/ui/app/templates/jobs/job/allocations.hbs +++ b/ui/app/templates/jobs/job/allocations.hbs @@ -19,6 +19,7 @@ {{#t.head}} {{#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}} @@ -28,7 +29,11 @@ Memory {{/t.head}} {{#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}} {{/list-table}}
    From 87c3aa2c898731d4a3dbdb7f8104d2add44a0e8c Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 17:36:25 -0700 Subject: [PATCH 07/17] Use the correct allocation row context in various places --- ui/app/templates/components/allocation-row.hbs | 4 ++-- .../components/job-deployment/deployment-allocations.hbs | 1 + .../components/job-page/parts/recent-allocations.hbs | 2 +- ui/app/templates/jobs/job/allocations.hbs | 2 +- ui/app/templates/jobs/job/task-group.hbs | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/app/templates/components/allocation-row.hbs b/ui/app/templates/components/allocation-row.hbs index 734459981e1a..fd16e5bc9064 100644 --- a/ui/app/templates/components/allocation-row.hbs +++ b/ui/app/templates/components/allocation-row.hbs @@ -15,7 +15,7 @@ {{allocation.shortId}} {{/link-to}} -{{#if (eq context "taskGroup")}} +{{#if (eq context "job")}} {{#link-to "jobs.job.task-group" allocation.job allocation.taskGroupName (query-params jobNamespace=allocation.job.namespace.id)}} {{allocation.taskGroupName}} @@ -27,7 +27,7 @@ {{allocation.clientStatus}} -{{#if (or (eq context "job") (eq context "taskGroup"))}} +{{#if (or (eq context "taskGroup") (eq context "job"))}} {{allocation.jobVersion}} {{#link-to "clients.client" allocation.node}}{{allocation.node.shortId}}{{/link-to}} {{else if (eq context "node")}} diff --git a/ui/app/templates/components/job-deployment/deployment-allocations.hbs b/ui/app/templates/components/job-deployment/deployment-allocations.hbs index cda6afb0e763..3f7dbf89502b 100644 --- a/ui/app/templates/components/job-deployment/deployment-allocations.hbs +++ b/ui/app/templates/components/job-deployment/deployment-allocations.hbs @@ -9,6 +9,7 @@ {{#t.head}} ID + Task Group Created Modified Status diff --git a/ui/app/templates/components/job-page/parts/recent-allocations.hbs b/ui/app/templates/components/job-page/parts/recent-allocations.hbs index 7eb9bc8d7cbf..f65a62c7029f 100644 --- a/ui/app/templates/components/job-page/parts/recent-allocations.hbs +++ b/ui/app/templates/components/job-page/parts/recent-allocations.hbs @@ -25,7 +25,7 @@ {{allocation-row data-test-allocation=row.model.id allocation=row.model - context="taskGroup" + context="job" onClick=(action "gotoAllocation" row.model)}} {{/t.body}} {{/list-table}} diff --git a/ui/app/templates/jobs/job/allocations.hbs b/ui/app/templates/jobs/job/allocations.hbs index b4b1cd6b3134..94744f146d53 100644 --- a/ui/app/templates/jobs/job/allocations.hbs +++ b/ui/app/templates/jobs/job/allocations.hbs @@ -32,7 +32,7 @@ {{allocation-row data-test-allocation=row.model.id allocation=row.model - context="taskGroup" + context="job" onClick=(action "gotoAllocation" row.model)}} {{/t.body}} {{/list-table}} diff --git a/ui/app/templates/jobs/job/task-group.hbs b/ui/app/templates/jobs/job/task-group.hbs index 1ae35542ca17..1c20a03e779b 100644 --- a/ui/app/templates/jobs/job/task-group.hbs +++ b/ui/app/templates/jobs/job/task-group.hbs @@ -72,7 +72,7 @@ Memory {{/t.head}} {{#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}} {{/list-table}}
    From ac972a60e60d5d53ba977151963a10a94b8b9920 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Thu, 19 Jul 2018 17:49:07 -0700 Subject: [PATCH 08/17] Link to the allocations page from the recent allocations table --- .../components/job-page/parts/recent-allocations.hbs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/app/templates/components/job-page/parts/recent-allocations.hbs b/ui/app/templates/components/job-page/parts/recent-allocations.hbs index f65a62c7029f..b9c9edb90d6a 100644 --- a/ui/app/templates/components/job-page/parts/recent-allocations.hbs +++ b/ui/app/templates/components/job-page/parts/recent-allocations.hbs @@ -29,8 +29,6 @@ onClick=(action "gotoAllocation" row.model)}} {{/t.body}} {{/list-table}} -
    -
    {{else}}

    No Allocations

    @@ -38,4 +36,11 @@
    {{/if}}
    + {{#if job.allocations.length}} +
    +

    {{#link-to "jobs.job.allocations" job}} + View all {{job.allocations.length}} {{pluralize "allocation" job.allocations.length}} + {{/link-to}}

    +
    + {{/if}}
    From b128c05dacbdd51152ce09e38bf9e6f11db626b0 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Fri, 20 Jul 2018 10:29:44 -0700 Subject: [PATCH 09/17] Add search to the allocations page --- ui/app/controllers/jobs/job/allocations.js | 2 +- ui/app/templates/jobs/job/allocations.hbs | 123 +++++++++++---------- 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/ui/app/controllers/jobs/job/allocations.js b/ui/app/controllers/jobs/job/allocations.js index efed1ef2b44f..1ec7a40a240e 100644 --- a/ui/app/controllers/jobs/job/allocations.js +++ b/ui/app/controllers/jobs/job/allocations.js @@ -21,7 +21,7 @@ export default Controller.extend(Sortable, Searchable, WithNamespaceResetting, { job: alias('model'), - searchProps: computed(() => ['shortId', 'name']), + searchProps: computed(() => ['shortId', 'name', 'taskGroupName']), allocations: computed('model.allocations.[]', function() { return this.get('model.allocations') || []; diff --git a/ui/app/templates/jobs/job/allocations.hbs b/ui/app/templates/jobs/job/allocations.hbs index 94744f146d53..6faf0e923f08 100644 --- a/ui/app/templates/jobs/job/allocations.hbs +++ b/ui/app/templates/jobs/job/allocations.hbs @@ -1,70 +1,71 @@ {{#gutter-menu class="page-body" onNamespaceChange=(action "gotoJobs")}} {{partial "jobs/job/subnav"}}
    -
    -
    - Allocations + {{#if sortedAllocations.length}} +
    +
    + {{search-box + data-test-allocations-search + searchTerm=(mut searchTerm) + placeholder="Search allocations..."}} +
    -
    - {{#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}} - - {{#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}} - CPU - Memory - {{/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}} - {{#if allocations.length}} -
    -
    -

    No Matches

    -

    No allocations match the term {{searchTerm}}

    -
    -
    - {{else}} -
    -
    -

    No Allocations

    -

    No allocations have been placed.

    -
    + {{#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}} + + {{#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}} + CPU + Memory + {{/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}} +
    +
    +

    No Matches

    +

    No allocations match the term {{searchTerm}}

    +
    +
    + {{/list-pagination}} + {{else}} +
    +
    +

    No Allocations

    +

    No allocations have been placed.

    +
    -
    + {{/if}}
    {{/gutter-menu}} From e8a7d9db8346096254c6dcbad4a2cf6ecd1f2699 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Fri, 20 Jul 2018 14:28:23 -0700 Subject: [PATCH 10/17] Don't use the boxed-section pattern on pages with one section --- ui/app/templates/jobs/job/evaluations.hbs | 77 +++++++++++------------ 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/ui/app/templates/jobs/job/evaluations.hbs b/ui/app/templates/jobs/job/evaluations.hbs index 212b6ea426d2..0c6e18290ed8 100644 --- a/ui/app/templates/jobs/job/evaluations.hbs +++ b/ui/app/templates/jobs/job/evaluations.hbs @@ -1,46 +1,39 @@ {{partial "jobs/job/subnav"}}
    -
    -
    - Evaluations + {{#if sortedEvaluations.length}} + {{#list-table + source=sortedEvaluations + sortProperty=sortProperty + sortDescending=sortDescending as |t|}} + {{#t.head}} + ID + {{#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|}} + + {{row.model.shortId}} + {{row.model.priority}} + {{row.model.triggeredBy}} + {{row.model.status}} + + {{#if (eq row.model.status "blocked")}} + N/A - In Progress + {{else if row.model.hasPlacementFailures}} + True + {{else}} + False + {{/if}} + + + {{/t.body}} + {{/list-table}} + {{else}} +
    +

    No Evaluations

    +

    This is most likely due to garbage collection.

    -
    - {{#if sortedEvaluations.length}} - {{#list-table - source=sortedEvaluations - sortProperty=sortProperty - sortDescending=sortDescending as |t|}} - {{#t.head}} - ID - {{#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|}} - - {{row.model.shortId}} - {{row.model.priority}} - {{row.model.triggeredBy}} - {{row.model.status}} - - {{#if (eq row.model.status "blocked")}} - N/A - In Progress - {{else if row.model.hasPlacementFailures}} - True - {{else}} - False - {{/if}} - - - {{/t.body}} - {{/list-table}} - {{else}} -
    -

    No Evaluations

    -

    This is most likely due to garbage collection.

    -
    - {{/if}} -
    -
    + {{/if}}
    From 5fff8e1c9abc4436dff47a4a00ed96762df81506 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Fri, 20 Jul 2018 14:28:57 -0700 Subject: [PATCH 11/17] Watch allocations on the job index page Since there are no recent allocations, those alloc need to be watched --- ui/app/routes/jobs/job/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/app/routes/jobs/job/index.js b/ui/app/routes/jobs/job/index.js index 58bf30beb6c3..30423ce9db51 100644 --- a/ui/app/routes/jobs/job/index.js +++ b/ui/app/routes/jobs/job/index.js @@ -11,6 +11,7 @@ export default Route.extend(WithWatchers, { controller.set('watchers', { model: this.get('watch').perform(model), summary: this.get('watchSummary').perform(model.get('summary')), + allocations: this.get('watchAllocations').perform(model), evaluations: this.get('watchEvaluations').perform(model), latestDeployment: model.get('supportsDeployments') && this.get('watchLatestDeployment').perform(model), @@ -21,6 +22,7 @@ export default Route.extend(WithWatchers, { watch: watchRecord('job'), watchAll: watchAll('job'), watchSummary: watchRecord('job-summary'), + watchAllocations: watchRelationship('allocations'), watchEvaluations: watchRelationship('evaluations'), watchLatestDeployment: watchRelationship('latestDeployment'), @@ -28,6 +30,7 @@ export default Route.extend(WithWatchers, { 'watch', 'watchAll', 'watchSummary', + 'watchAllocations', 'watchEvaluations', 'watchLatestDeployment' ), From f50aff562cf2a566273dcbe9c18b47165f00c0ef Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Fri, 20 Jul 2018 14:29:51 -0700 Subject: [PATCH 12/17] Treat filtering on an async relationship as async --- .../components/job-page/parts/recent-allocations.js | 13 +++++++++---- ui/app/utils/classes/promise-array.js | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 ui/app/utils/classes/promise-array.js diff --git a/ui/app/components/job-page/parts/recent-allocations.js b/ui/app/components/job-page/parts/recent-allocations.js index d7d2ce0a2e41..c50ff7f02517 100644 --- a/ui/app/components/job-page/parts/recent-allocations.js +++ b/ui/app/components/job-page/parts/recent-allocations.js @@ -1,14 +1,19 @@ 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 this.get('job.allocations') - .sortBy('modifyIndex') - .reverse() - .slice(0, 5); + return new PromiseArray({ + promise: this.get('job.allocations').then(allocations => + allocations + .sortBy('modifyIndex') + .reverse() + .slice(0, 5) + ), + }); }), actions: { diff --git a/ui/app/utils/classes/promise-array.js b/ui/app/utils/classes/promise-array.js new file mode 100644 index 000000000000..0fd302aec7ae --- /dev/null +++ b/ui/app/utils/classes/promise-array.js @@ -0,0 +1,4 @@ +import ArrayProxy from '@ember/array/proxy'; +import PromiseProxyMixin from '@ember/object/promise-proxy-mixin'; + +export default ArrayProxy.extend(PromiseProxyMixin); From b14693f168e6db00f0ac40129cdfd056fbdeb517 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Mon, 23 Jul 2018 19:06:15 -0700 Subject: [PATCH 13/17] Add object spread --- ui/.eslintrc.js | 3 +++ ui/ember-cli-build.js | 3 +++ ui/package.json | 1 + ui/yarn.lock | 11 +++++++++++ 4 files changed, 18 insertions(+) diff --git a/ui/.eslintrc.js b/ui/.eslintrc.js index 5d19a7da5399..d4f4d42a0682 100644 --- a/ui/.eslintrc.js +++ b/ui/.eslintrc.js @@ -10,6 +10,9 @@ module.exports = { parserOptions: { ecmaVersion: 2017, sourceType: 'module', + ecmaFeatures: { + experimentalObjectRestSpread: true, + }, }, rules: { indent: ['error', 2, { SwitchCase: 1 }], diff --git a/ui/ember-cli-build.js b/ui/ember-cli-build.js index 8e879ba07abe..121823950fa4 100644 --- a/ui/ember-cli-build.js +++ b/ui/ember-cli-build.js @@ -19,6 +19,9 @@ module.exports = function(defaults) { `${defaults.project.pkg.name}/templates/components/freestyle/**/*`, ], }, + babel: { + plugins: ['transform-object-rest-spread'], + }, }); // Use `app.import` to add additional libraries to the generated diff --git a/ui/package.json b/ui/package.json index 6a33c98c4f38..b8c4f58392f9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -24,6 +24,7 @@ ] }, "devDependencies": { + "babel-plugin-transform-object-rest-spread": "^6.26.0", "broccoli-asset-rev": "^2.4.5", "bulma": "0.6.1", "core-js": "^2.4.1", diff --git a/ui/yarn.lock b/ui/yarn.lock index 2a1e4205b2a0..ff3b1fc8f67d 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -854,6 +854,10 @@ babel-plugin-syntax-exponentiation-operator@^6.8.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" +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: 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" @@ -1042,6 +1046,13 @@ babel-plugin-transform-exponentiation-operator@^6.22.0: babel-plugin-syntax-exponentiation-operator "^6.8.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: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" From a0148600a6b846b200f9f52a0c21e74a5a41f013 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Mon, 23 Jul 2018 19:06:41 -0700 Subject: [PATCH 14/17] Refactor allocations page as a component --- ui/tests/acceptance/client-detail-test.js | 2 +- ui/tests/acceptance/job-deployments-test.js | 2 +- ui/tests/pages/clients/detail.js | 19 +++---------- ui/tests/pages/components/allocations.js | 30 +++++++++++++++++++++ ui/tests/pages/jobs/job/deployments.js | 6 ++--- ui/tests/pages/jobs/job/task-group.js | 24 +++-------------- 6 files changed, 41 insertions(+), 42 deletions(-) create mode 100644 ui/tests/pages/components/allocations.js diff --git a/ui/tests/acceptance/client-detail-test.js b/ui/tests/acceptance/client-detail-test.js index df58c32ea884..1c766d429c39 100644 --- a/ui/tests/acceptance/client-detail-test.js +++ b/ui/tests/acceptance/client-detail-test.js @@ -128,7 +128,7 @@ test('each allocation should have high-level details for the allocation', functi andThen(() => { 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'), diff --git a/ui/tests/acceptance/job-deployments-test.js b/ui/tests/acceptance/job-deployments-test.js index 6408df55f948..407317b37a83 100644 --- a/ui/tests/acceptance/job-deployments-test.js +++ b/ui/tests/acceptance/job-deployments-test.js @@ -232,7 +232,7 @@ test('when open, a deployment shows a list of all allocations for the deployment const allocation = allocations[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'); }); }); }); diff --git a/ui/tests/pages/clients/detail.js b/ui/tests/pages/clients/detail.js index a46fd85bc376..309a3f176d0a 100644 --- a/ui/tests/pages/clients/detail.js +++ b/ui/tests/pages/clients/detail.js @@ -9,6 +9,8 @@ import { visitable, } from 'ember-cli-page-object'; +import allocations from 'nomad-ui/tests/pages/components/allocations'; + export default create({ visit: visitable('/clients/:id'), @@ -36,22 +38,7 @@ export default create({ eligibilityDefinition: text('[data-test-eligibility]'), datacenterDefinition: text('[data-test-datacenter-definition]'), - allocations: collection('[data-test-allocation]', { - id: 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]'), - 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]'), - }), + ...allocations(), attributesTable: isPresent('[data-test-attributes]'), metaTable: isPresent('[data-test-meta]'), diff --git a/ui/tests/pages/components/allocations.js b/ui/tests/pages/components/allocations.js new file mode 100644 index 000000000000..29cfb3ffca05 --- /dev/null +++ b/ui/tests/pages/components/allocations.js @@ -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); + }, + }; +} diff --git a/ui/tests/pages/jobs/job/deployments.js b/ui/tests/pages/jobs/job/deployments.js index 5f0d633da86f..fe7d2ed4ced0 100644 --- a/ui/tests/pages/jobs/job/deployments.js +++ b/ui/tests/pages/jobs/job/deployments.js @@ -8,6 +8,8 @@ import { visitable, } from 'ember-cli-page-object'; +import allocations from 'nomad-ui/tests/pages/components/allocations'; + export default create({ visit: visitable('/jobs/:id/deployments'), @@ -46,9 +48,7 @@ export default create({ progress: text('[data-test-deployment-task-group-progress-deadline]'), }), + ...allocations('[data-test-deployment-allocation]'), hasAllocations: isPresent('[data-test-deployment-allocations]'), - allocations: collection('[data-test-deployment-allocation]', { - id: text('[data-test-short-id]'), - }), }), }); diff --git a/ui/tests/pages/jobs/job/task-group.js b/ui/tests/pages/jobs/job/task-group.js index 9a7db9056b12..d20537e5a242 100644 --- a/ui/tests/pages/jobs/job/task-group.js +++ b/ui/tests/pages/jobs/job/task-group.js @@ -9,6 +9,8 @@ import { visitable, } from 'ember-cli-page-object'; +import allocations from 'nomad-ui/tests/pages/components/allocations'; + export default create({ pageSize: 10, @@ -31,27 +33,7 @@ export default create({ return this.breadcrumbs.toArray().find(crumb => crumb.id === id); }, - allocations: collection('[data-test-allocation]', { - 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]'), - 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); - }, + ...allocations(), isEmpty: isPresent('[data-test-empty-allocations-list]'), From f8504cb56f6cb2d57495c059048b085aefd01952 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Mon, 23 Jul 2018 20:13:16 -0700 Subject: [PATCH 15/17] Tests for the recent allocations table --- .../job-page/parts/recent-allocations.hbs | 8 +- ui/tests/integration/job-page/service-test.js | 97 +++++++++++++++++-- ui/tests/pages/jobs/detail.js | 10 ++ 3 files changed, 105 insertions(+), 10 deletions(-) diff --git a/ui/app/templates/components/job-page/parts/recent-allocations.hbs b/ui/app/templates/components/job-page/parts/recent-allocations.hbs index b9c9edb90d6a..4a0c22965b05 100644 --- a/ui/app/templates/components/job-page/parts/recent-allocations.hbs +++ b/ui/app/templates/components/job-page/parts/recent-allocations.hbs @@ -30,15 +30,15 @@ {{/t.body}} {{/list-table}} {{else}} -
    -

    No Allocations

    -

    No allocations have been placed.

    +
    +

    No Allocations

    +

    No allocations have been placed.

    {{/if}}
    {{#if job.allocations.length}}
    -

    {{#link-to "jobs.job.allocations" job}} +

    {{#link-to "jobs.job.allocations" job}} View all {{job.allocations.length}} {{pluralize "allocation" job.allocations.length}} {{/link-to}}

    diff --git a/ui/tests/integration/job-page/service-test.js b/ui/tests/integration/job-page/service-test.js index 5b9bf69f345d..aef698a149cf 100644 --- a/ui/tests/integration/job-page/service-test.js +++ b/ui/tests/integration/job-page/service-test.js @@ -1,19 +1,23 @@ import { getOwner } from '@ember/application'; +import { assign } from '@ember/polyfills'; import { test, moduleForComponent } from 'ember-qunit'; import wait from 'ember-test-helpers/wait'; import hbs from 'htmlbars-inline-precompile'; import { startMirage } from 'nomad-ui/initializers/ember-cli-mirage'; import { stopJob, expectStopError, expectDeleteRequest } from './helpers'; +import Job from 'nomad-ui/tests/pages/jobs/detail'; moduleForComponent('job-page/service', 'Integration | Component | job-page/service', { integration: true, beforeEach() { + Job.setContext(this); window.localStorage.clear(); this.store = getOwner(this).lookup('service:store'); this.server = startMirage(); this.server.create('namespace'); }, afterEach() { + Job.removeContext(); this.server.shutdown(); window.localStorage.clear(); }, @@ -36,12 +40,18 @@ const commonProperties = job => ({ gotoJob() {}, }); -const makeMirageJob = server => - server.create('job', { - type: 'service', - createAllocations: false, - status: 'running', - }); +const makeMirageJob = (server, props = {}) => + server.create( + 'job', + assign( + { + type: 'service', + createAllocations: false, + status: 'running', + }, + props + ) + ); test('Stopping a job sends a delete request for the job', function(assert) { let job; @@ -80,3 +90,78 @@ test('Stopping a job without proper permissions shows an error message', functio .then(stopJob) .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' + ); + }); +}); diff --git a/ui/tests/pages/jobs/detail.js b/ui/tests/pages/jobs/detail.js index 2208a800318a..0f5d21bdcc07 100644 --- a/ui/tests/pages/jobs/detail.js +++ b/ui/tests/pages/jobs/detail.js @@ -8,6 +8,8 @@ import { visitable, } from 'ember-cli-page-object'; +import allocations from 'nomad-ui/tests/pages/components/allocations'; + export default create({ visit: visitable('/jobs/:id'), @@ -29,10 +31,18 @@ export default create({ return this.stats.toArray().findBy('id', id); }, + ...allocations(), + + viewAllAllocations: text('[data-test-view-all-allocations]'), + error: { isPresent: isPresent('[data-test-error]'), title: text('[data-test-error-title]'), message: text('[data-test-error-message]'), seekHelp: clickable('[data-test-error-message] a'), }, + + recentAllocationsEmptyState: { + headline: text('[data-test-empty-recent-allocations-headline]'), + }, }); From 29fcece8e732e7423d1250ecca10117e8ba59240 Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Mon, 23 Jul 2018 21:54:23 -0700 Subject: [PATCH 16/17] Test coverage for the jobs/:job/allocations page --- ui/tests/acceptance/job-allocations-test.js | 85 +++++++++++++++++++++ ui/tests/pages/jobs/job/allocations.js | 32 ++++++++ 2 files changed, 117 insertions(+) create mode 100644 ui/tests/acceptance/job-allocations-test.js create mode 100644 ui/tests/pages/jobs/job/allocations.js diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js new file mode 100644 index 000000000000..ee46f6624990 --- /dev/null +++ b/ui/tests/acceptance/job-allocations-test.js @@ -0,0 +1,85 @@ +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; + +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) { + Array(10) + .fill(null) + .map((_, index) => { + server.create('allocation', { + id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`, + }); + }); + + 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'); + }); +}); diff --git a/ui/tests/pages/jobs/job/allocations.js b/ui/tests/pages/jobs/job/allocations.js new file mode 100644 index 000000000000..1c15aa9fd534 --- /dev/null +++ b/ui/tests/pages/jobs/job/allocations.js @@ -0,0 +1,32 @@ +import { + attribute, + clickable, + create, + collection, + fillable, + 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, + + search: fillable('[data-test-allocations-search] input'), + + ...allocations(), + + sortOptions: collection('[data-test-sort-by]', { + id: attribute('data-test-sort-by'), + sort: clickable(), + }), + + sortBy(id) { + return this.sortOptions + .toArray() + .findBy('id', id) + .sort(); + }, +}); From 32c321abbfbce1a69150206469d1cd6b3bbb4dbc Mon Sep 17 00:00:00 2001 From: Michael Lange Date: Mon, 30 Jul 2018 12:56:42 -0700 Subject: [PATCH 17/17] Keep the search box around when a search yields no results --- ui/app/templates/jobs/job/allocations.hbs | 2 +- ui/tests/acceptance/job-allocations-test.js | 39 +++++++++++++++++---- ui/tests/pages/jobs/job/allocations.js | 8 +++++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/ui/app/templates/jobs/job/allocations.hbs b/ui/app/templates/jobs/job/allocations.hbs index 6faf0e923f08..080919b6a655 100644 --- a/ui/app/templates/jobs/job/allocations.hbs +++ b/ui/app/templates/jobs/job/allocations.hbs @@ -1,7 +1,7 @@ {{#gutter-menu class="page-body" onNamespaceChange=(action "gotoJobs")}} {{partial "jobs/job/subnav"}}
    - {{#if sortedAllocations.length}} + {{#if allocations.length}}
    {{search-box diff --git a/ui/tests/acceptance/job-allocations-test.js b/ui/tests/acceptance/job-allocations-test.js index ee46f6624990..202104599064 100644 --- a/ui/tests/acceptance/job-allocations-test.js +++ b/ui/tests/acceptance/job-allocations-test.js @@ -5,6 +5,16 @@ 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'); @@ -64,13 +74,7 @@ test('allocations table is sortable', function(assert) { }); test('allocations table is searchable', function(assert) { - Array(10) - .fill(null) - .map((_, index) => { - server.create('allocation', { - id: index < 5 ? `ffffff-dddddd-${index}` : `111111-222222-${index}`, - }); - }); + makeSearchAllocations(server); allocations = server.schema.allocations.where({ jobId: job.id }).models; Allocations.visit({ id: job.id }); @@ -83,3 +87,24 @@ test('allocations table is searchable', function(assert) { 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'); + }); +}); diff --git a/ui/tests/pages/jobs/job/allocations.js b/ui/tests/pages/jobs/job/allocations.js index 1c15aa9fd534..65ca46f5b923 100644 --- a/ui/tests/pages/jobs/job/allocations.js +++ b/ui/tests/pages/jobs/job/allocations.js @@ -4,6 +4,8 @@ import { create, collection, fillable, + isPresent, + text, visitable, } from 'ember-cli-page-object'; @@ -14,10 +16,16 @@ export default create({ 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(),