diff --git a/ui/app/adapters/job.js b/ui/app/adapters/job.js index 7f715c25e..eec5483f8 100644 --- a/ui/app/adapters/job.js +++ b/ui/app/adapters/job.js @@ -1,11 +1,11 @@ import { inject as service } from '@ember/service'; import { assign } from '@ember/polyfills'; -import ApplicationAdapter from './application'; +import Watchable from './watchable'; -export default ApplicationAdapter.extend({ +export default Watchable.extend({ system: service(), - shouldReloadAll: () => true, + // shouldReloadAll: () => true, buildQuery() { const namespace = this.get('system.activeNamespace.id'); diff --git a/ui/app/adapters/watchable.js b/ui/app/adapters/watchable.js new file mode 100644 index 000000000..fb32c0825 --- /dev/null +++ b/ui/app/adapters/watchable.js @@ -0,0 +1,60 @@ +import { get } from '@ember/object'; +import { assign } from '@ember/polyfills'; +import { copy } from '@ember/object/internals'; +import { inject as service } from '@ember/service'; +import queryString from 'npm:query-string'; +import ApplicationAdapter from './application'; + +export default ApplicationAdapter.extend({ + watchList: service(), + store: service(), + + findRecord(store, type, id, snapshot, additionalParams = {}) { + const params = copy(additionalParams, true); + const url = this.buildURL(type.modelName, id, snapshot, 'findRecord'); + + if (get(snapshot, 'adapterOptions.watch')) { + params.index = this.get('watchList').getIndexFor(url); + } + + return this.ajax(url, 'GET', { + data: params, + }); + }, + + reloadRelationship(model, relationshipName, watch = false) { + const relationship = model.relationshipFor(relationshipName); + if (relationship.kind !== 'belongsTo' && relationship.kind !== 'hasMany') { + throw new Error( + `${relationship.key} must be a belongsTo or hasMany, instead it was ${relationship.kind}` + ); + } else { + const url = model[relationship.kind](relationship.key).link(); + let params = {}; + + if (watch) { + params.index = this.get('watchList').getIndexFor(url); + } + + if (url.includes('?')) { + params = assign(queryString.parse(url.split('?')[1]), params); + } + + this.ajax(url, 'GET', { + data: params, + }).then(json => { + this.get('store').pushPayload(relationship.type, { + [relationship.type]: relationship.kind === 'hasMany' ? json : [json], + }); + }); + } + }, + + handleResponse(status, headers, payload, requestData) { + const newIndex = headers['x-nomad-index']; + if (newIndex) { + this.get('watchList').setIndexFor(requestData.url, newIndex); + } + return this._super(...arguments); + }, +}); diff --git a/ui/app/routes/jobs/job.js b/ui/app/routes/jobs/job.js index 558ea55e4..8062d01e5 100644 --- a/ui/app/routes/jobs/job.js +++ b/ui/app/routes/jobs/job.js @@ -2,9 +2,13 @@ import { inject as service } from '@ember/service'; import Route from '@ember/routing/route'; import RSVP from 'rsvp'; import notifyError from 'nomad-ui/utils/notify-error'; +import { task } from 'ember-concurrency'; +import wait from 'nomad-ui/utils/wait'; export default Route.extend({ store: service(), + token: service(), + watchList: service(), serialize(model) { return { job_name: model.get('plainId') }; @@ -21,4 +25,41 @@ export default Route.extend({ }) .catch(notifyError(this)); }, + + setupController(controller, model) { + controller.set('modelTask', this.get('watch').perform(model.get('id'))); + controller.set('summaryTask', this.get('watchRelationship').perform(model, 'summary')); + controller.set('evaluationsTask', this.get('watchRelationship').perform(model, 'evaluations')); + controller.set('deploymentsTask', this.get('watchRelationship').perform(model, 'deployments')); + }, + + watch: task(function*(jobId) { + while (true) { + try { + yield RSVP.all([ + this.store.findRecord('job', jobId, { reload: true, adapterOptions: { watch: true } }), + wait(2000), + ]); + } catch (e) { + yield e; + break; + } + } + }), + + watchRelationship: task(function*(job, relationshipName) { + while (true) { + try { + yield RSVP.all([ + this.store + .adapterFor(job.get('modelName')) + .reloadRelationship(job, relationshipName, true), + wait(2000), + ]); + } catch (e) { + yield e; + break; + } + } + }), }); diff --git a/ui/app/services/watch-list.js b/ui/app/services/watch-list.js new file mode 100644 index 000000000..f22ad34d9 --- /dev/null +++ b/ui/app/services/watch-list.js @@ -0,0 +1,19 @@ +import { readOnly } from '@ember/object/computed'; +import { copy } from '@ember/object/internals'; +import Service from '@ember/service'; + +const list = {}; + +export default Service.extend({ + list: readOnly(function() { + return copy(list, true); + }), + + getIndexFor(url) { + return list[url] || 0; + }, + + setIndexFor(url, value) { + list[url] = value; + }, +}); diff --git a/ui/app/utils/wait.js b/ui/app/utils/wait.js new file mode 100644 index 000000000..3949cf23a --- /dev/null +++ b/ui/app/utils/wait.js @@ -0,0 +1,10 @@ +import RSVP from 'rsvp'; + +// An always passing promise used to throttle other promises +export default function wait(duration) { + return new RSVP.Promise(resolve => { + setTimeout(() => { + resolve(`Waited ${duration}ms`); + }, duration); + }); +}