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  }