github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/ui/app/controllers/jobs/index.js (about)

     1  /* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */
     2  import { inject as service } from '@ember/service';
     3  import { alias, readOnly } from '@ember/object/computed';
     4  import Controller, { inject as controller } from '@ember/controller';
     5  import { action, computed } from '@ember/object';
     6  import { scheduleOnce } from '@ember/runloop';
     7  import intersection from 'lodash.intersection';
     8  import Sortable from 'nomad-ui/mixins/sortable';
     9  import Searchable from 'nomad-ui/mixins/searchable';
    10  import { serialize, deserializedQueryParam as selection } from 'nomad-ui/utils/qp-serialize';
    11  import classic from 'ember-classic-decorator';
    12  
    13  @classic
    14  export default class IndexController extends Controller.extend(Sortable, Searchable) {
    15    @service system;
    16    @service userSettings;
    17    @controller('jobs') jobsController;
    18  
    19    @alias('jobsController.isForbidden') isForbidden;
    20  
    21    queryParams = [
    22      {
    23        currentPage: 'page',
    24      },
    25      {
    26        searchTerm: 'search',
    27      },
    28      {
    29        sortProperty: 'sort',
    30      },
    31      {
    32        sortDescending: 'desc',
    33      },
    34      {
    35        qpType: 'type',
    36      },
    37      {
    38        qpStatus: 'status',
    39      },
    40      {
    41        qpDatacenter: 'dc',
    42      },
    43      {
    44        qpPrefix: 'prefix',
    45      },
    46    ];
    47  
    48    currentPage = 1;
    49    @readOnly('userSettings.pageSize') pageSize;
    50  
    51    sortProperty = 'modifyIndex';
    52    sortDescending = true;
    53  
    54    @computed
    55    get searchProps() {
    56      return ['id', 'name'];
    57    }
    58  
    59    @computed
    60    get fuzzySearchProps() {
    61      return ['name'];
    62    }
    63  
    64    fuzzySearchEnabled = true;
    65  
    66    qpType = '';
    67    qpStatus = '';
    68    qpDatacenter = '';
    69    qpPrefix = '';
    70  
    71    @selection('qpType') selectionType;
    72    @selection('qpStatus') selectionStatus;
    73    @selection('qpDatacenter') selectionDatacenter;
    74    @selection('qpPrefix') selectionPrefix;
    75  
    76    @computed
    77    get optionsType() {
    78      return [
    79        { key: 'batch', label: 'Batch' },
    80        { key: 'parameterized', label: 'Parameterized' },
    81        { key: 'periodic', label: 'Periodic' },
    82        { key: 'service', label: 'Service' },
    83        { key: 'system', label: 'System' },
    84      ];
    85    }
    86  
    87    @computed
    88    get optionsStatus() {
    89      return [
    90        { key: 'pending', label: 'Pending' },
    91        { key: 'running', label: 'Running' },
    92        { key: 'dead', label: 'Dead' },
    93      ];
    94    }
    95  
    96    @computed('selectionDatacenter', 'visibleJobs.[]')
    97    get optionsDatacenter() {
    98      const flatten = (acc, val) => acc.concat(val);
    99      const allDatacenters = new Set(this.visibleJobs.mapBy('datacenters').reduce(flatten, []));
   100  
   101      // Remove any invalid datacenters from the query param/selection
   102      const availableDatacenters = Array.from(allDatacenters).compact();
   103      scheduleOnce('actions', () => {
   104        // eslint-disable-next-line ember/no-side-effects
   105        this.set(
   106          'qpDatacenter',
   107          serialize(intersection(availableDatacenters, this.selectionDatacenter))
   108        );
   109      });
   110  
   111      return availableDatacenters.sort().map(dc => ({ key: dc, label: dc }));
   112    }
   113  
   114    @computed('selectionPrefix', 'visibleJobs.[]')
   115    get optionsPrefix() {
   116      // A prefix is defined as the start of a job name up to the first - or .
   117      // ex: mktg-analytics -> mktg, ds.supermodel.classifier -> ds
   118      const hasPrefix = /.[-._]/;
   119  
   120      // Collect and count all the prefixes
   121      const allNames = this.visibleJobs.mapBy('name');
   122      const nameHistogram = allNames.reduce((hist, name) => {
   123        if (hasPrefix.test(name)) {
   124          const prefix = name.match(/(.+?)[-._]/)[1];
   125          hist[prefix] = hist[prefix] ? hist[prefix] + 1 : 1;
   126        }
   127        return hist;
   128      }, {});
   129  
   130      // Convert to an array
   131      const nameTable = Object.keys(nameHistogram).map(key => ({
   132        prefix: key,
   133        count: nameHistogram[key],
   134      }));
   135  
   136      // Only consider prefixes that match more than one name
   137      const prefixes = nameTable.filter(name => name.count > 1);
   138  
   139      // Remove any invalid prefixes from the query param/selection
   140      const availablePrefixes = prefixes.mapBy('prefix');
   141      scheduleOnce('actions', () => {
   142        // eslint-disable-next-line ember/no-side-effects
   143        this.set('qpPrefix', serialize(intersection(availablePrefixes, this.selectionPrefix)));
   144      });
   145  
   146      // Sort, format, and include the count in the label
   147      return prefixes.sortBy('prefix').map(name => ({
   148        key: name.prefix,
   149        label: `${name.prefix} (${name.count})`,
   150      }));
   151    }
   152  
   153    /**
   154      Visible jobs are those that match the selected namespace and aren't children
   155      of periodic or parameterized jobs.
   156    */
   157    @computed('model.@each.parent', 'system.{activeNamespace.id,namespaces.length}')
   158    get visibleJobs() {
   159      // Namespace related properties are ommitted from the dependent keys
   160      // due to a prop invalidation bug caused by region switching.
   161      const hasNamespaces = this.get('system.namespaces.length');
   162      const activeNamespace = this.get('system.activeNamespace.id') || 'default';
   163  
   164      return this.model
   165        .compact()
   166        .filter(job => !job.isNew)
   167        .filter(job => !hasNamespaces || job.get('namespace.id') === activeNamespace)
   168        .filter(job => !job.get('parent.content'));
   169    }
   170  
   171    @computed(
   172      'visibleJobs.[]',
   173      'selectionType',
   174      'selectionStatus',
   175      'selectionDatacenter',
   176      'selectionPrefix'
   177    )
   178    get filteredJobs() {
   179      const {
   180        selectionType: types,
   181        selectionStatus: statuses,
   182        selectionDatacenter: datacenters,
   183        selectionPrefix: prefixes,
   184      } = this;
   185  
   186      // A job must match ALL filter facets, but it can match ANY selection within a facet
   187      // Always return early to prevent unnecessary facet predicates.
   188      return this.visibleJobs.filter(job => {
   189        if (types.length && !types.includes(job.get('displayType'))) {
   190          return false;
   191        }
   192  
   193        if (statuses.length && !statuses.includes(job.get('status'))) {
   194          return false;
   195        }
   196  
   197        if (datacenters.length && !job.get('datacenters').find(dc => datacenters.includes(dc))) {
   198          return false;
   199        }
   200  
   201        const name = job.get('name');
   202        if (prefixes.length && !prefixes.find(prefix => name.startsWith(prefix))) {
   203          return false;
   204        }
   205  
   206        return true;
   207      });
   208    }
   209  
   210    @alias('filteredJobs') listToSort;
   211    @alias('listSorted') listToSearch;
   212    @alias('listSearched') sortedJobs;
   213  
   214    isShowingDeploymentDetails = false;
   215  
   216    setFacetQueryParam(queryParam, selection) {
   217      this.set(queryParam, serialize(selection));
   218    }
   219  
   220    @action
   221    gotoJob(job) {
   222      this.transitionToRoute('jobs.job', job.get('plainId'));
   223    }
   224  }