Refactor job summary to a relationship

Now that blocking queries are going to be in play, We can no longer
pretend the two requests are one, since they have independent nomad
indices.
This commit is contained in:
Michael Lange 2018-02-08 15:02:48 -08:00
parent 2228e1fd0c
commit e7796ccaee
6 changed files with 103 additions and 72 deletions

View file

@ -1,5 +1,4 @@
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import RSVP from 'rsvp';
import { assign } from '@ember/polyfills'; import { assign } from '@ember/polyfills';
import ApplicationAdapter from './application'; import ApplicationAdapter from './application';
@ -26,28 +25,17 @@ export default ApplicationAdapter.extend({
}); });
}, },
findRecord(store, { modelName }, id, snapshot) { findRecordSummary(modelName, name, snapshot, namespaceQuery) {
// To make a findRecord response reflect the findMany response, the JobSummary return this.ajax(`${this.buildURL(modelName, name, snapshot, 'findRecord')}/summary`, 'GET', {
// from /summary needs to be stitched into the response. data: assign(this.buildQuery() || {}, namespaceQuery),
});
},
// URL is the form of /job/:name?namespace=:namespace with arbitrary additional query params findRecord(store, type, id, snapshot) {
const [name, namespace] = JSON.parse(id); const [name, namespace] = JSON.parse(id);
const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {}; const namespaceQuery = namespace && namespace !== 'default' ? { namespace } : {};
return RSVP.hash({
job: this.ajax(this.buildURL(modelName, name, snapshot, 'findRecord'), 'GET', { return this._super(store, type, name, snapshot, namespaceQuery);
data: assign(this.buildQuery() || {}, namespaceQuery),
}),
summary: this.ajax(
`${this.buildURL(modelName, name, snapshot, 'findRecord')}/summary`,
'GET',
{
data: assign(this.buildQuery() || {}, namespaceQuery),
}
),
}).then(({ job, summary }) => {
job.JobSummary = summary;
return job;
});
}, },
findAllocations(job) { findAllocations(job) {

View file

@ -0,0 +1,39 @@
import { collect, sum } from '@ember/object/computed';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
import { belongsTo } from 'ember-data/relationships';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
import sumAggregation from '../utils/properties/sum-aggregation';
export default Model.extend({
job: belongsTo('job'),
taskGroupSummaries: fragmentArray('task-group-summary'),
// Aggregate allocation counts across all summaries
queuedAllocs: sumAggregation('taskGroupSummaries', 'queuedAllocs'),
startingAllocs: sumAggregation('taskGroupSummaries', 'startingAllocs'),
runningAllocs: sumAggregation('taskGroupSummaries', 'runningAllocs'),
completeAllocs: sumAggregation('taskGroupSummaries', 'completeAllocs'),
failedAllocs: sumAggregation('taskGroupSummaries', 'failedAllocs'),
lostAllocs: sumAggregation('taskGroupSummaries', 'lostAllocs'),
allocsList: collect(
'queuedAllocs',
'startingAllocs',
'runningAllocs',
'completeAllocs',
'failedAllocs',
'lostAllocs'
),
totalAllocs: sum('allocsList'),
pendingChildren: attr('number'),
runningChildren: attr('number'),
deadChildren: attr('number'),
childrenList: collect('pendingChildren', 'runningChildren', 'deadChildren'),
totalChildren: sum('childrenList'),
});

View file

@ -1,10 +1,9 @@
import { collect, sum, bool, equal, or } from '@ember/object/computed'; import { alias, bool, equal, or } from '@ember/object/computed';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import Model from 'ember-data/model'; import Model from 'ember-data/model';
import attr from 'ember-data/attr'; import attr from 'ember-data/attr';
import { belongsTo, hasMany } from 'ember-data/relationships'; import { belongsTo, hasMany } from 'ember-data/relationships';
import { fragmentArray } from 'ember-data-model-fragments/attributes'; import { fragmentArray } from 'ember-data-model-fragments/attributes';
import sumAggregation from '../utils/properties/sum-aggregation';
const JOB_TYPES = ['service', 'batch', 'system']; const JOB_TYPES = ['service', 'batch', 'system'];
@ -83,34 +82,21 @@ export default Model.extend({
datacenters: attr(), datacenters: attr(),
taskGroups: fragmentArray('task-group', { defaultValue: () => [] }), taskGroups: fragmentArray('task-group', { defaultValue: () => [] }),
taskGroupSummaries: fragmentArray('task-group-summary'), summary: belongsTo('job-summary'),
// Aggregate allocation counts across all summaries // Alias through to the summary, as if there was no relationship
queuedAllocs: sumAggregation('taskGroupSummaries', 'queuedAllocs'), taskGroupSummaries: alias('summary.taskGroupSummaries'),
startingAllocs: sumAggregation('taskGroupSummaries', 'startingAllocs'), queuedAllocs: alias('summary.queuedAllocs'),
runningAllocs: sumAggregation('taskGroupSummaries', 'runningAllocs'), startingAllocs: alias('summary.startingAllocs'),
completeAllocs: sumAggregation('taskGroupSummaries', 'completeAllocs'), runningAllocs: alias('summary.runningAllocs'),
failedAllocs: sumAggregation('taskGroupSummaries', 'failedAllocs'), completeAllocs: alias('summary.completeAllocs'),
lostAllocs: sumAggregation('taskGroupSummaries', 'lostAllocs'), failedAllocs: alias('summary.failedAllocs'),
lostAllocs: alias('summary.lostAllocs'),
allocsList: collect( totalAllocs: alias('summary.totalAllocs'),
'queuedAllocs', pendingChildren: alias('summary.pendingChildren'),
'startingAllocs', runningChildren: alias('summary.runningChildren'),
'runningAllocs', deadChildren: alias('summary.deadChildren'),
'completeAllocs', totalChildren: alias('summary.childrenList'),
'failedAllocs',
'lostAllocs'
),
totalAllocs: sum('allocsList'),
pendingChildren: attr('number'),
runningChildren: attr('number'),
deadChildren: attr('number'),
childrenList: collect('pendingChildren', 'runningChildren', 'deadChildren'),
totalChildren: sum('childrenList'),
version: attr('number'), version: attr('number'),

View file

@ -4,6 +4,8 @@ import attr from 'ember-data/attr';
import { fragmentOwner, fragmentArray } from 'ember-data-model-fragments/attributes'; import { fragmentOwner, fragmentArray } from 'ember-data-model-fragments/attributes';
import sumAggregation from '../utils/properties/sum-aggregation'; import sumAggregation from '../utils/properties/sum-aggregation';
const maybe = arr => arr || [];
export default Fragment.extend({ export default Fragment.extend({
job: fragmentOwner(), job: fragmentOwner(),
@ -13,7 +15,7 @@ export default Fragment.extend({
tasks: fragmentArray('task'), tasks: fragmentArray('task'),
allocations: computed('job.allocations.@each.taskGroup', function() { allocations: computed('job.allocations.@each.taskGroup', function() {
return this.get('job.allocations').filterBy('taskGroupName', this.get('name')); return maybe(this.get('job.allocations')).filterBy('taskGroupName', this.get('name'));
}), }),
reservedCPU: sumAggregation('tasks', 'reservedCPU'), reservedCPU: sumAggregation('tasks', 'reservedCPU'),
@ -32,6 +34,6 @@ export default Fragment.extend({
}), }),
summary: computed('job.taskGroupSummaries.[]', function() { summary: computed('job.taskGroupSummaries.[]', function() {
return this.get('job.taskGroupSummaries').findBy('name', this.get('name')); return maybe(this.get('job.taskGroupSummaries')).findBy('name', this.get('name'));
}), }),
}); });

View file

@ -0,0 +1,32 @@
import { get } from '@ember/object';
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
normalize(modelClass, hash) {
// Transform the map-based Summary object into an array-based
// TaskGroupSummary fragment list
hash.PlainJobId = hash.JobID;
hash.ID = JSON.stringify([hash.JobID, hash.Namespace || 'default']);
hash.TaskGroupSummaries = Object.keys(get(hash, 'Summary') || {}).map(key => {
const allocStats = get(hash, `Summary.${key}`) || {};
const summary = { Name: key };
Object.keys(allocStats).forEach(
allocKey => (summary[`${allocKey}Allocs`] = allocStats[allocKey])
);
return summary;
});
// Lift the children stats out of the Children object
const childrenStats = get(hash, 'Children');
if (childrenStats) {
Object.keys(childrenStats).forEach(
childrenKey => (hash[`${childrenKey}Children`] = childrenStats[childrenKey])
);
}
return this._super(modelClass, hash);
},
});

View file

@ -34,27 +34,6 @@ export default ApplicationSerializer.extend({
hash.ParameterizedJob = true; hash.ParameterizedJob = true;
} }
// Transform the map-based JobSummary object into an array-based
// JobSummary fragment list
hash.TaskGroupSummaries = Object.keys(get(hash, 'JobSummary.Summary') || {}).map(key => {
const allocStats = get(hash, `JobSummary.Summary.${key}`) || {};
const summary = { Name: key };
Object.keys(allocStats).forEach(
allocKey => (summary[`${allocKey}Allocs`] = allocStats[allocKey])
);
return summary;
});
// Lift the children stats out of the JobSummary object
const childrenStats = get(hash, 'JobSummary.Children');
if (childrenStats) {
Object.keys(childrenStats).forEach(
childrenKey => (hash[`${childrenKey}Children`] = childrenStats[childrenKey])
);
}
return this._super(typeHash, hash); return this._super(typeHash, hash);
}, },
@ -68,6 +47,11 @@ export default ApplicationSerializer.extend({
.buildURL(modelName, hash.PlainId, hash, 'findRecord'); .buildURL(modelName, hash.PlainId, hash, 'findRecord');
return assign(this._super(...arguments), { return assign(this._super(...arguments), {
summary: {
links: {
related: buildURL(`${jobURL}/summary`, { namespace: namespace }),
},
},
allocations: { allocations: {
links: { links: {
related: buildURL(`${jobURL}/allocations`, { namespace: namespace }), related: buildURL(`${jobURL}/allocations`, { namespace: namespace }),