github.com/thomasobenaus/nomad@v0.11.1/ui/app/controllers/jobs/index.js (about)

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