github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/ui/app/models/job.js (about) 1 import { alias, equal, or, and } from '@ember/object/computed'; 2 import { computed } from '@ember/object'; 3 import Model from 'ember-data/model'; 4 import attr from 'ember-data/attr'; 5 import { belongsTo, hasMany } from 'ember-data/relationships'; 6 import { fragmentArray } from 'ember-data-model-fragments/attributes'; 7 8 const JOB_TYPES = ['service', 'batch', 'system']; 9 10 export default Model.extend({ 11 region: attr('string'), 12 name: attr('string'), 13 plainId: attr('string'), 14 type: attr('string'), 15 priority: attr('number'), 16 allAtOnce: attr('boolean'), 17 18 status: attr('string'), 19 statusDescription: attr('string'), 20 createIndex: attr('number'), 21 modifyIndex: attr('number'), 22 23 // True when the job is the parent periodic or parameterized jobs 24 // Instances of periodic or parameterized jobs are false for both properties 25 periodic: attr('boolean'), 26 parameterized: attr('boolean'), 27 28 periodicDetails: attr(), 29 parameterizedDetails: attr(), 30 31 hasChildren: or('periodic', 'parameterized'), 32 33 parent: belongsTo('job', { inverse: 'children' }), 34 children: hasMany('job', { inverse: 'parent' }), 35 36 // The parent job name is prepended to child launch job names 37 trimmedName: computed('name', 'parent', function() { 38 return this.get('parent.content') ? this.get('name').replace(/.+?\//, '') : this.get('name'); 39 }), 40 41 // A composite of type and other job attributes to determine 42 // a better type descriptor for human interpretation rather 43 // than for scheduling. 44 displayType: computed('type', 'periodic', 'parameterized', function() { 45 if (this.get('periodic')) { 46 return 'periodic'; 47 } else if (this.get('parameterized')) { 48 return 'parameterized'; 49 } 50 return this.get('type'); 51 }), 52 53 // A composite of type and other job attributes to determine 54 // type for templating rather than scheduling 55 templateType: computed( 56 'type', 57 'periodic', 58 'parameterized', 59 'parent.periodic', 60 'parent.parameterized', 61 function() { 62 const type = this.get('type'); 63 64 if (this.get('periodic')) { 65 return 'periodic'; 66 } else if (this.get('parameterized')) { 67 return 'parameterized'; 68 } else if (this.get('parent.periodic')) { 69 return 'periodic-child'; 70 } else if (this.get('parent.parameterized')) { 71 return 'parameterized-child'; 72 } else if (JOB_TYPES.includes(type)) { 73 // Guard against the API introducing a new type before the UI 74 // is prepared to handle it. 75 return this.get('type'); 76 } 77 78 // A fail-safe in the event the API introduces a new type. 79 return 'service'; 80 } 81 ), 82 83 datacenters: attr(), 84 taskGroups: fragmentArray('task-group', { defaultValue: () => [] }), 85 summary: belongsTo('job-summary'), 86 87 // Alias through to the summary, as if there was no relationship 88 taskGroupSummaries: alias('summary.taskGroupSummaries'), 89 queuedAllocs: alias('summary.queuedAllocs'), 90 startingAllocs: alias('summary.startingAllocs'), 91 runningAllocs: alias('summary.runningAllocs'), 92 completeAllocs: alias('summary.completeAllocs'), 93 failedAllocs: alias('summary.failedAllocs'), 94 lostAllocs: alias('summary.lostAllocs'), 95 totalAllocs: alias('summary.totalAllocs'), 96 pendingChildren: alias('summary.pendingChildren'), 97 runningChildren: alias('summary.runningChildren'), 98 deadChildren: alias('summary.deadChildren'), 99 totalChildren: alias('summary.childrenList'), 100 101 version: attr('number'), 102 103 versions: hasMany('job-versions'), 104 allocations: hasMany('allocations'), 105 deployments: hasMany('deployments'), 106 evaluations: hasMany('evaluations'), 107 namespace: belongsTo('namespace'), 108 109 hasBlockedEvaluation: computed('evaluations.@each.isBlocked', function() { 110 return this.get('evaluations') 111 .toArray() 112 .some(evaluation => evaluation.get('isBlocked')); 113 }), 114 115 hasPlacementFailures: and('latestFailureEvaluation', 'hasBlockedEvaluation'), 116 117 latestEvaluation: computed('evaluations.@each.modifyIndex', 'evaluations.isPending', function() { 118 const evaluations = this.get('evaluations'); 119 if (!evaluations || evaluations.get('isPending')) { 120 return null; 121 } 122 return evaluations.sortBy('modifyIndex').get('lastObject'); 123 }), 124 125 latestFailureEvaluation: computed( 126 'evaluations.@each.modifyIndex', 127 'evaluations.isPending', 128 function() { 129 const evaluations = this.get('evaluations'); 130 if (!evaluations || evaluations.get('isPending')) { 131 return null; 132 } 133 134 const failureEvaluations = evaluations.filterBy('hasPlacementFailures'); 135 if (failureEvaluations) { 136 return failureEvaluations.sortBy('modifyIndex').get('lastObject'); 137 } 138 } 139 ), 140 141 supportsDeployments: equal('type', 'service'), 142 143 runningDeployment: computed('deployments.@each.status', function() { 144 return this.get('deployments').findBy('status', 'running'); 145 }), 146 147 fetchRawDefinition() { 148 return this.store.adapterFor('job').fetchRawDefinition(this); 149 }, 150 151 forcePeriodic() { 152 return this.store.adapterFor('job').forcePeriodic(this); 153 }, 154 155 statusClass: computed('status', function() { 156 const classMap = { 157 pending: 'is-pending', 158 running: 'is-primary', 159 dead: 'is-light', 160 }; 161 162 return classMap[this.get('status')] || 'is-dark'; 163 }), 164 165 payload: attr('string'), 166 decodedPayload: computed('payload', function() { 167 // Lazily decode the base64 encoded payload 168 return window.atob(this.get('payload') || ''); 169 }), 170 });