github.com/anuvu/nomad@v0.8.7-atom1/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 // A job model created from the jobs list response will be lacking 88 // task groups. This is an indicator that it needs to be reloaded 89 // if task group information is important. 90 isPartial: equal('taskGroups.length', 0), 91 92 // If a job has only been loaded through the list request, the task groups 93 // are still unknown. However, the count of task groups is available through 94 // the job-summary model which is embedded in the jobs list response. 95 taskGroupCount: or('taskGroups.length', 'taskGroupSummaries.length'), 96 97 // Alias through to the summary, as if there was no relationship 98 taskGroupSummaries: alias('summary.taskGroupSummaries'), 99 queuedAllocs: alias('summary.queuedAllocs'), 100 startingAllocs: alias('summary.startingAllocs'), 101 runningAllocs: alias('summary.runningAllocs'), 102 completeAllocs: alias('summary.completeAllocs'), 103 failedAllocs: alias('summary.failedAllocs'), 104 lostAllocs: alias('summary.lostAllocs'), 105 totalAllocs: alias('summary.totalAllocs'), 106 pendingChildren: alias('summary.pendingChildren'), 107 runningChildren: alias('summary.runningChildren'), 108 deadChildren: alias('summary.deadChildren'), 109 totalChildren: alias('summary.totalChildren'), 110 111 version: attr('number'), 112 113 versions: hasMany('job-versions'), 114 allocations: hasMany('allocations'), 115 deployments: hasMany('deployments'), 116 evaluations: hasMany('evaluations'), 117 namespace: belongsTo('namespace'), 118 119 drivers: computed('taskGroups.@each.drivers', function() { 120 return this.get('taskGroups') 121 .mapBy('drivers') 122 .reduce((all, drivers) => { 123 all.push(...drivers); 124 return all; 125 }, []) 126 .uniq(); 127 }), 128 129 // Getting all unhealthy drivers for a job can be incredibly expensive if the job 130 // has many allocations. This can lead to making an API request for many nodes. 131 unhealthyDrivers: computed('allocations.@each.unhealthyDrivers.[]', function() { 132 return this.get('allocations') 133 .mapBy('unhealthyDrivers') 134 .reduce((all, drivers) => { 135 all.push(...drivers); 136 return all; 137 }, []) 138 .uniq(); 139 }), 140 141 hasBlockedEvaluation: computed('evaluations.@each.isBlocked', function() { 142 return this.get('evaluations') 143 .toArray() 144 .some(evaluation => evaluation.get('isBlocked')); 145 }), 146 147 hasPlacementFailures: and('latestFailureEvaluation', 'hasBlockedEvaluation'), 148 149 latestEvaluation: computed('evaluations.@each.modifyIndex', 'evaluations.isPending', function() { 150 const evaluations = this.get('evaluations'); 151 if (!evaluations || evaluations.get('isPending')) { 152 return null; 153 } 154 return evaluations.sortBy('modifyIndex').get('lastObject'); 155 }), 156 157 latestFailureEvaluation: computed( 158 'evaluations.@each.modifyIndex', 159 'evaluations.isPending', 160 function() { 161 const evaluations = this.get('evaluations'); 162 if (!evaluations || evaluations.get('isPending')) { 163 return null; 164 } 165 166 const failureEvaluations = evaluations.filterBy('hasPlacementFailures'); 167 if (failureEvaluations) { 168 return failureEvaluations.sortBy('modifyIndex').get('lastObject'); 169 } 170 } 171 ), 172 173 supportsDeployments: equal('type', 'service'), 174 175 runningDeployment: computed('deployments.@each.status', function() { 176 return this.get('deployments').findBy('status', 'running'); 177 }), 178 179 fetchRawDefinition() { 180 return this.store.adapterFor('job').fetchRawDefinition(this); 181 }, 182 183 forcePeriodic() { 184 return this.store.adapterFor('job').forcePeriodic(this); 185 }, 186 187 stop() { 188 return this.store.adapterFor('job').stop(this); 189 }, 190 191 statusClass: computed('status', function() { 192 const classMap = { 193 pending: 'is-pending', 194 running: 'is-primary', 195 dead: 'is-light', 196 }; 197 198 return classMap[this.get('status')] || 'is-dark'; 199 }), 200 201 payload: attr('string'), 202 decodedPayload: computed('payload', function() { 203 // Lazily decode the base64 encoded payload 204 return window.atob(this.get('payload') || ''); 205 }), 206 });