github.com/emate/nomad@v0.8.2-wo-binpacking/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    hasBlockedEvaluation: computed('evaluations.@each.isBlocked', function() {
   120      return this.get('evaluations')
   121        .toArray()
   122        .some(evaluation => evaluation.get('isBlocked'));
   123    }),
   124  
   125    hasPlacementFailures: and('latestFailureEvaluation', 'hasBlockedEvaluation'),
   126  
   127    latestEvaluation: computed('evaluations.@each.modifyIndex', 'evaluations.isPending', function() {
   128      const evaluations = this.get('evaluations');
   129      if (!evaluations || evaluations.get('isPending')) {
   130        return null;
   131      }
   132      return evaluations.sortBy('modifyIndex').get('lastObject');
   133    }),
   134  
   135    latestFailureEvaluation: computed(
   136      'evaluations.@each.modifyIndex',
   137      'evaluations.isPending',
   138      function() {
   139        const evaluations = this.get('evaluations');
   140        if (!evaluations || evaluations.get('isPending')) {
   141          return null;
   142        }
   143  
   144        const failureEvaluations = evaluations.filterBy('hasPlacementFailures');
   145        if (failureEvaluations) {
   146          return failureEvaluations.sortBy('modifyIndex').get('lastObject');
   147        }
   148      }
   149    ),
   150  
   151    supportsDeployments: equal('type', 'service'),
   152  
   153    runningDeployment: computed('deployments.@each.status', function() {
   154      return this.get('deployments').findBy('status', 'running');
   155    }),
   156  
   157    fetchRawDefinition() {
   158      return this.store.adapterFor('job').fetchRawDefinition(this);
   159    },
   160  
   161    forcePeriodic() {
   162      return this.store.adapterFor('job').forcePeriodic(this);
   163    },
   164  
   165    stop() {
   166      return this.store.adapterFor('job').stop(this);
   167    },
   168  
   169    statusClass: computed('status', function() {
   170      const classMap = {
   171        pending: 'is-pending',
   172        running: 'is-primary',
   173        dead: 'is-light',
   174      };
   175  
   176      return classMap[this.get('status')] || 'is-dark';
   177    }),
   178  
   179    payload: attr('string'),
   180    decodedPayload: computed('payload', function() {
   181      // Lazily decode the base64 encoded payload
   182      return window.atob(this.get('payload') || '');
   183    }),
   184  });