github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/components/das/recommendation-card.js (about) 1 import Component from '@glimmer/component'; 2 import { inject as service } from '@ember/service'; 3 import { tracked } from '@glimmer/tracking'; 4 import { action } from '@ember/object'; 5 import ResourcesDiffs from 'nomad-ui/utils/resources-diffs'; 6 import { htmlSafe } from '@ember/template'; 7 import { didCancel, task, timeout } from 'ember-concurrency'; 8 import Ember from 'ember'; 9 10 export default class DasRecommendationCardComponent extends Component { 11 @service router; 12 13 @tracked allCpuToggleActive = true; 14 @tracked allMemoryToggleActive = true; 15 16 @tracked activeTaskToggleRowIndex = 0; 17 18 element = null; 19 20 @tracked cardHeight; 21 @tracked interstitialComponent; 22 @tracked error; 23 24 @tracked proceedPromiseResolve; 25 26 get activeTaskToggleRow() { 27 return this.taskToggleRows[this.activeTaskToggleRowIndex]; 28 } 29 30 get activeTask() { 31 return this.activeTaskToggleRow.task; 32 } 33 34 get narrative() { 35 const summary = this.args.summary; 36 const taskGroup = summary.taskGroup; 37 38 const diffs = new ResourcesDiffs( 39 taskGroup, 40 taskGroup.count, 41 this.args.summary.recommendations, 42 this.args.summary.excludedRecommendations 43 ); 44 45 const cpuDelta = diffs.cpu.delta; 46 const memoryDelta = diffs.memory.delta; 47 48 const aggregate = taskGroup.count > 1; 49 const aggregateString = aggregate ? ' an aggregate' : ''; 50 51 if (cpuDelta || memoryDelta) { 52 const deltasSameDirection = 53 (cpuDelta < 0 && memoryDelta < 0) || (cpuDelta > 0 && memoryDelta > 0); 54 55 let narrative = 'Applying the selected recommendations will'; 56 57 if (deltasSameDirection) { 58 narrative += ` ${verbForDelta(cpuDelta)} ${aggregateString}`; 59 } 60 61 if (cpuDelta) { 62 if (!deltasSameDirection) { 63 narrative += ` ${verbForDelta(cpuDelta)} ${aggregateString}`; 64 } 65 66 narrative += ` <strong>${diffs.cpu.absoluteAggregateDiff} of CPU</strong>`; 67 } 68 69 if (cpuDelta && memoryDelta) { 70 narrative += ' and'; 71 } 72 73 if (memoryDelta) { 74 if (!deltasSameDirection) { 75 narrative += ` ${verbForDelta(memoryDelta)} ${aggregateString}`; 76 } 77 78 narrative += ` <strong>${diffs.memory.absoluteAggregateDiff} of memory</strong>`; 79 } 80 81 if (taskGroup.count === 1) { 82 narrative += '.'; 83 } else { 84 narrative += ` across <strong>${taskGroup.count} allocations</strong>.`; 85 } 86 87 return htmlSafe(narrative); 88 } else { 89 return ''; 90 } 91 } 92 93 get taskToggleRows() { 94 const taskNameToTaskToggles = {}; 95 96 return this.args.summary.recommendations.reduce((taskToggleRows, recommendation) => { 97 let taskToggleRow = taskNameToTaskToggles[recommendation.task.name]; 98 99 if (!taskToggleRow) { 100 taskToggleRow = { 101 recommendations: [], 102 task: recommendation.task, 103 }; 104 105 taskNameToTaskToggles[recommendation.task.name] = taskToggleRow; 106 taskToggleRows.push(taskToggleRow); 107 } 108 109 const isCpu = recommendation.resource === 'CPU'; 110 const rowResourceProperty = isCpu ? 'cpu' : 'memory'; 111 112 taskToggleRow[rowResourceProperty] = { 113 recommendation, 114 isActive: !this.args.summary.excludedRecommendations.includes(recommendation), 115 }; 116 117 if (isCpu) { 118 taskToggleRow.recommendations.unshift(recommendation); 119 } else { 120 taskToggleRow.recommendations.push(recommendation); 121 } 122 123 return taskToggleRows; 124 }, []); 125 } 126 127 get showToggleAllToggles() { 128 return this.taskToggleRows.length > 1; 129 } 130 131 get allCpuToggleDisabled() { 132 return !this.args.summary.recommendations.filterBy('resource', 'CPU').length; 133 } 134 135 get allMemoryToggleDisabled() { 136 return !this.args.summary.recommendations.filterBy('resource', 'MemoryMB').length; 137 } 138 139 get cannotAccept() { 140 return ( 141 this.args.summary.excludedRecommendations.length == this.args.summary.recommendations.length 142 ); 143 } 144 145 get copyButtonLink() { 146 const path = this.router.urlFor('optimize.summary', this.args.summary.slug, { 147 queryParams: { namespace: this.args.summary.jobNamespace }, 148 }); 149 const { origin } = window.location; 150 151 return `${origin}${path}`; 152 } 153 154 @action 155 toggleAllRecommendationsForResource(resource) { 156 let enabled; 157 158 if (resource === 'CPU') { 159 this.allCpuToggleActive = !this.allCpuToggleActive; 160 enabled = this.allCpuToggleActive; 161 } else { 162 this.allMemoryToggleActive = !this.allMemoryToggleActive; 163 enabled = this.allMemoryToggleActive; 164 } 165 166 this.args.summary.toggleAllRecommendationsForResource(resource, enabled); 167 } 168 169 @action 170 accept() { 171 this.storeCardHeight(); 172 this.args.summary 173 .save() 174 .then( 175 () => this.onApplied.perform(), 176 e => this.onError.perform(e) 177 ) 178 .catch(e => { 179 if (!didCancel(e)) { 180 throw e; 181 } 182 }); 183 } 184 185 @action 186 dismiss() { 187 this.storeCardHeight(); 188 this.args.summary.excludedRecommendations.pushObjects(this.args.summary.recommendations); 189 this.args.summary 190 .save() 191 .then( 192 () => this.onDismissed.perform(), 193 e => this.onError.perform(e) 194 ) 195 .catch(e => { 196 if (!didCancel(e)) { 197 throw e; 198 } 199 }); 200 } 201 202 @(task(function*() { 203 this.interstitialComponent = 'accepted'; 204 yield timeout(Ember.testing ? 0 : 2000); 205 206 this.args.proceed.perform(); 207 this.resetInterstitial(); 208 }).drop()) 209 onApplied; 210 211 @(task(function*() { 212 const { manuallyDismissed } = yield new Promise(resolve => { 213 this.proceedPromiseResolve = resolve; 214 this.interstitialComponent = 'dismissed'; 215 }); 216 217 if (!manuallyDismissed) { 218 yield timeout(Ember.testing ? 0 : 2000); 219 } 220 221 this.args.proceed.perform(); 222 this.resetInterstitial(); 223 }).drop()) 224 onDismissed; 225 226 @(task(function*(error) { 227 yield new Promise(resolve => { 228 this.proceedPromiseResolve = resolve; 229 this.interstitialComponent = 'error'; 230 this.error = error.toString(); 231 }); 232 233 this.args.proceed.perform(); 234 this.resetInterstitial(); 235 }).drop()) 236 onError; 237 238 get interstitialStyle() { 239 return htmlSafe(`height: ${this.cardHeight}px`); 240 } 241 242 resetInterstitial() { 243 if (!this.args.skipReset) { 244 this.interstitialComponent = undefined; 245 this.error = undefined; 246 } 247 } 248 249 @action 250 cardInserted(element) { 251 this.element = element; 252 } 253 254 storeCardHeight() { 255 this.cardHeight = this.element.clientHeight; 256 } 257 } 258 259 function verbForDelta(delta) { 260 if (delta > 0) { 261 return 'add'; 262 } else { 263 return 'save'; 264 } 265 }