github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/controllers/evaluations/index.js (about)

     1  import { getOwner } from '@ember/application';
     2  import Controller from '@ember/controller';
     3  import { action } from '@ember/object';
     4  import { tracked } from '@glimmer/tracking';
     5  import { schedule } from '@ember/runloop';
     6  import { inject as service } from '@ember/service';
     7  import { useMachine } from 'ember-statecharts';
     8  import { use } from 'ember-usable';
     9  import evaluationsMachine from '../../machines/evaluations';
    10  
    11  const ALL_NAMESPACE_WILDCARD = '*';
    12  
    13  export default class EvaluationsController extends Controller {
    14    @service store;
    15    @service userSettings;
    16  
    17    // We use statecharts here to manage complex user flows for the sidebar logic
    18    @use
    19    statechart = useMachine(evaluationsMachine).withConfig({
    20      services: {
    21        loadEvaluation: this.loadEvaluation,
    22      },
    23      actions: {
    24        updateEvaluationQueryParameter: this.updateEvaluationQueryParameter,
    25        removeCurrentEvaluationQueryParameter:
    26          this.removeCurrentEvaluationQueryParameter,
    27      },
    28      guards: {
    29        sidebarIsOpen: this._sidebarIsOpen,
    30      },
    31    });
    32  
    33    queryParams = [
    34      'nextToken',
    35      'currentEval',
    36      'pageSize',
    37      'status',
    38      { qpNamespace: 'namespace' },
    39      'type',
    40      'searchTerm',
    41    ];
    42    @tracked currentEval = null;
    43  
    44    @action
    45    _sidebarIsOpen() {
    46      return !!this.currentEval;
    47    }
    48  
    49    @action
    50    async loadEvaluation(context, { evaluation }) {
    51      let evaluationId;
    52      if (evaluation?.id) {
    53        evaluationId = evaluation.id;
    54      } else {
    55        evaluationId = this.currentEval;
    56      }
    57  
    58      return this.store.findRecord('evaluation', evaluationId, {
    59        reload: true,
    60        adapterOptions: { related: true },
    61      });
    62    }
    63  
    64    @action
    65    async handleEvaluationClick(evaluation, e) {
    66      if (
    67        e instanceof MouseEvent ||
    68        (e instanceof KeyboardEvent &&
    69          (e.code === 'Enter' || e.code === 'Space')) ||
    70        !e
    71      ) {
    72        this.statechart.send('LOAD_EVALUATION', { evaluation });
    73      }
    74    }
    75  
    76    @action
    77    notifyEvalChange([evaluation]) {
    78      schedule('actions', this, () => {
    79        this.statechart.send('CHANGE_EVAL', { evaluation });
    80      });
    81    }
    82  
    83    @action
    84    updateEvaluationQueryParameter(context, { evaluation }) {
    85      this.currentEval = evaluation.id;
    86    }
    87  
    88    @action
    89    removeCurrentEvaluationQueryParameter() {
    90      this.currentEval = null;
    91    }
    92  
    93    get shouldDisableNext() {
    94      return !this.model.meta?.nextToken;
    95    }
    96  
    97    get shouldDisablePrev() {
    98      return !this.previousTokens.length;
    99    }
   100  
   101    get optionsEvaluationsStatus() {
   102      return [
   103        { key: null, label: 'All' },
   104        { key: 'blocked', label: 'Blocked' },
   105        { key: 'pending', label: 'Pending' },
   106        { key: 'complete', label: 'Complete' },
   107        { key: 'failed', label: 'Failed' },
   108        { key: 'canceled', label: 'Canceled' },
   109      ];
   110    }
   111  
   112    get optionsTriggeredBy() {
   113      return [
   114        { key: null, label: 'All' },
   115        { key: 'job-register', label: 'Job Register' },
   116        { key: 'job-deregister', label: 'Job Deregister' },
   117        { key: 'periodic-job', label: 'Periodic Job' },
   118        { key: 'node-drain', label: 'Node Drain' },
   119        { key: 'node-update', label: 'Node Update' },
   120        { key: 'alloc-stop', label: 'Allocation Stop' },
   121        { key: 'scheduled', label: 'Scheduled' },
   122        { key: 'rolling-update', label: 'Rolling Update' },
   123        { key: 'deployment-watcher', label: 'Deployment Watcher' },
   124        { key: 'failed-follow-up', label: 'Failed Follow Up' },
   125        { key: 'max-disconnect-timeout', label: 'Max Disconnect Timeout' },
   126        { key: 'max-plan-attempts', label: 'Max Plan Attempts' },
   127        { key: 'alloc-failure', label: 'Allocation Failure' },
   128        { key: 'queued-allocs', label: 'Queued Allocations' },
   129        { key: 'preemption', label: 'Preemption' },
   130        { key: 'job-scaling', label: 'Job Scalling' },
   131      ];
   132    }
   133  
   134    get optionsNamespaces() {
   135      const namespaces = this.store.peekAll('namespace').map((namespace) => ({
   136        key: namespace.name,
   137        label: namespace.name,
   138      }));
   139  
   140      // Create default namespace selection
   141      namespaces.unshift({
   142        key: ALL_NAMESPACE_WILDCARD,
   143        label: 'All (*)',
   144      });
   145  
   146      return namespaces;
   147    }
   148  
   149    get optionsType() {
   150      return [
   151        { key: null, label: 'All' },
   152        { key: 'client', label: 'Client' },
   153        { key: 'no client', label: 'No Client' },
   154      ];
   155    }
   156  
   157    filters = ['status', 'qpNamespace', 'type', 'triggeredBy', 'searchTerm'];
   158  
   159    get hasFiltersApplied() {
   160      return this.filters.reduce((result, filter) => {
   161        // By default we always set qpNamespace to the '*' wildcard
   162        // We need to ensure that if namespace is the only filter, that we send the correct error message to the user
   163        if (this[filter] && filter !== 'qpNamespace') {
   164          result = true;
   165        }
   166        return result;
   167      }, false);
   168    }
   169  
   170    get currentFilters() {
   171      const result = [];
   172      for (const filter of this.filters) {
   173        const isNamespaceWildcard =
   174          filter === 'qpNamespace' && this[filter] === '*';
   175        if (this[filter] && !isNamespaceWildcard) {
   176          result.push({ [filter]: this[filter] });
   177        }
   178      }
   179      return result;
   180    }
   181  
   182    get noMatchText() {
   183      let text = '';
   184      const cleanNames = {
   185        status: 'Status',
   186        qpNamespace: 'Namespace',
   187        type: 'Type',
   188        triggeredBy: 'Triggered By',
   189        searchTerm: 'Search Term',
   190      };
   191      if (this.hasFiltersApplied) {
   192        for (let i = 0; i < this.currentFilters.length; i++) {
   193          const filter = this.currentFilters[i];
   194          const [name] = Object.keys(filter);
   195          const filterName = cleanNames[name];
   196          const filterValue = filter[name];
   197          if (this.currentFilters.length === 1)
   198            return `${filterName}: ${filterValue}.`;
   199          if (i !== 0 && i !== this.currentFilters.length - 1)
   200            text = text.concat(`, ${filterName}: ${filterValue}`);
   201          if (i === 0) text = text.concat(`${filterName}: ${filterValue}`);
   202          if (i === this.currentFilters.length - 1) {
   203            return text.concat(`, ${filterName}: ${filterValue}.`);
   204          }
   205        }
   206      }
   207  
   208      return text;
   209    }
   210  
   211    @tracked pageSize = this.userSettings.pageSize;
   212    @tracked nextToken = null;
   213    @tracked previousTokens = [];
   214    @tracked status = null;
   215    @tracked triggeredBy = null;
   216    @tracked qpNamespace = ALL_NAMESPACE_WILDCARD;
   217    @tracked type = null;
   218    @tracked searchTerm = null;
   219  
   220    @action
   221    onChange(newPageSize) {
   222      this.pageSize = newPageSize;
   223    }
   224  
   225    @action
   226    onNext(nextToken) {
   227      this.previousTokens = [...this.previousTokens, this.nextToken];
   228      this.nextToken = nextToken;
   229    }
   230  
   231    @action
   232    onPrev() {
   233      const lastToken = this.previousTokens.pop();
   234      this.previousTokens = [...this.previousTokens];
   235      this.nextToken = lastToken;
   236    }
   237  
   238    @action
   239    refresh() {
   240      const isDefaultParams = this.nextToken === null && this.status === null;
   241      if (isDefaultParams) {
   242        getOwner(this).lookup('route:evaluations.index').refresh();
   243        return;
   244      }
   245  
   246      this._resetTokens();
   247      this.status = null;
   248      this.pageSize = this.userSettings.pageSize;
   249    }
   250  
   251    @action
   252    setQueryParam(qp, selection) {
   253      this._resetTokens();
   254      this[qp] = selection;
   255    }
   256  
   257    @action
   258    toggle() {
   259      this._resetTokens();
   260      this.shouldOnlyDisplayClientEvals = !this.shouldOnlyDisplayClientEvals;
   261    }
   262  
   263    @action
   264    _resetTokens() {
   265      this.nextToken = null;
   266      this.previousTokens = [];
   267    }
   268  }