Add Ember ESLint plugin (#8134)

This is extracted from #8094, where I have run into some snags. Since
these ESLint fixes aren’t actually connected to the Ember 3.16 update
but involve changes to many files, we might as well address them
separately. Where possible I fixed the problems but in cases where
a fix seemed too involved, I added per-line or -file exceptions.
This commit is contained in:
Buck Doyle
2020-06-09 16:03:28 -05:00
committed by GitHub
parent a7291fc6bd
commit 11d80ae489
73 changed files with 315 additions and 195 deletions

6
.circleci/config.yml generated
View File

@@ -708,13 +708,13 @@ jobs:
- checkout
- restore_cache:
keys:
- v1-deps-{{ checksum "ui/yarn.lock" }}
- v1-deps-
- v2-deps-{{ checksum "ui/yarn.lock" }}
- v2-deps-
- run:
command: cd ui && yarn install
name: yarn install
- save_cache:
key: v1-deps-{{ checksum "ui/yarn.lock" }}
key: v2-deps-{{ checksum "ui/yarn.lock" }}
paths:
- ./ui/node_modules
- run:

View File

@@ -7,13 +7,13 @@ steps:
- checkout
- restore_cache:
keys:
- v1-deps-{{ checksum "ui/yarn.lock" }}
- v1-deps-
- v2-deps-{{ checksum "ui/yarn.lock" }}
- v2-deps-
- run:
name: yarn install
command: cd ui && yarn install
- save_cache:
key: v1-deps-{{ checksum "ui/yarn.lock" }}
key: v2-deps-{{ checksum "ui/yarn.lock" }}
paths:
- ./ui/node_modules
- run:

View File

@@ -7,12 +7,18 @@ module.exports = {
browser: true,
es6: true,
},
extends: 'eslint:recommended',
extends: [
'eslint:recommended',
'plugin:ember/recommended',
],
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: [
'ember'
],
rules: {
indent: ['error', 2, { SwitchCase: 1 }],
'linebreak-style': ['error', 'unix'],

View File

@@ -24,6 +24,8 @@ export default RESTAdapter.extend({
'X-Nomad-Token': token,
};
}
return;
}),
handleResponse(status, headers, payload) {

View File

@@ -3,9 +3,9 @@ import addToPath from 'nomad-ui/utils/add-to-path';
import WithNamespaceIDs from 'nomad-ui/mixins/with-namespace-ids';
export default Watchable.extend(WithNamespaceIDs, {
relationshipFallbackLinks: {
relationshipFallbackLinks: Object.freeze({
summary: '/summary',
},
}),
fetchRawDefinition(job) {
const url = this.urlForFindRecord(job.get('id'), 'job');

View File

@@ -1,7 +1,7 @@
import Watchable from './watchable';
export default Watchable.extend({
queryParamsToAttrs: {
queryParamsToAttrs: Object.freeze({
type: 'type',
},
}),
});

View File

@@ -2,8 +2,8 @@ import Watchable from './watchable';
import WithNamespaceIDs from 'nomad-ui/mixins/with-namespace-ids';
export default Watchable.extend(WithNamespaceIDs, {
queryParamsToAttrs: {
queryParamsToAttrs: Object.freeze({
type: 'type',
plugin_id: 'plugin.id',
},
}),
});

View File

@@ -24,6 +24,8 @@ export default Component.extend({
if (metric === 'cpu' || metric === 'memory') {
return this[this.metric];
}
return;
}),
formattedStat: computed('metric', 'stat.used', function() {
@@ -32,13 +34,9 @@ export default Component.extend({
return this.stat.used;
}),
formattedReserved: computed(
'metric',
'statsTracker.reservedMemory',
'statsTracker.reservedCPU',
function() {
if (this.metric === 'memory') return `${this.statsTracker.reservedMemory} MiB`;
if (this.metric === 'cpu') return `${this.statsTracker.reservedCPU} MHz`;
}
),
formattedReserved: computed('metric', 'statsTracker.{reservedMemory,reservedCPU}', function() {
if (this.metric === 'memory') return `${this.statsTracker.reservedMemory} MiB`;
if (this.metric === 'cpu') return `${this.statsTracker.reservedCPU} MHz`;
return;
}),
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-observers */
import Component from '@ember/component';
import { computed, observer, set } from '@ember/object';
import { run } from '@ember/runloop';

View File

@@ -27,14 +27,16 @@ export default Component.extend({
durationIsCustom: equal('selectedDurationQuickOption.value', 'custom'),
customDuration: '',
durationQuickOptions: computed(() => [
{ label: '1 Hour', value: '1h' },
{ label: '4 Hours', value: '4h' },
{ label: '8 Hours', value: '8h' },
{ label: '12 Hours', value: '12h' },
{ label: '1 Day', value: '1d' },
{ label: 'Custom', value: 'custom' },
]),
durationQuickOptions: computed(function() {
return [
{ label: '1 Hour', value: '1h' },
{ label: '4 Hours', value: '4h' },
{ label: '8 Hours', value: '8h' },
{ label: '12 Hours', value: '12h' },
{ label: '1 Day', value: '1d' },
{ label: 'Custom', value: 'custom' },
];
}),
deadline: computed(
'deadlineEnabled',

View File

@@ -19,6 +19,8 @@ export default Component.extend({
if (this.model.allocation) {
return this.model;
}
return;
}),
type: computed('taskState', function() {
@@ -53,5 +55,4 @@ export default Component.extend({
}
}
),
});

View File

@@ -64,19 +64,15 @@ export default Component.extend({
} else if (this.mode === 'head' || this.mode === 'tail') {
return 'readat';
}
return;
}),
fileUrl: computed(
'allocation.id',
'allocation.node.httpAddr',
'fetchMode',
'useServer',
function() {
const address = this.get('allocation.node.httpAddr');
const url = `/v1/client/fs/${this.fetchMode}/${this.allocation.id}`;
return this.useServer ? url : `//${address}${url}`;
}
),
fileUrl: computed('allocation.{id,node.httpAddr}', 'fetchMode', 'useServer', function() {
const address = this.get('allocation.node.httpAddr');
const url = `/v1/client/fs/${this.fetchMode}/${this.allocation.id}`;
return this.useServer ? url : `//${address}${url}`;
}),
fileParams: computed('taskState.name', 'file', 'mode', function() {
// The Log class handles encoding query params

View File

@@ -8,11 +8,15 @@ export default Component.extend({
if (this.taskState && this.taskState.state === 'running') {
return 'is-active';
}
return;
}),
finishedClass: computed('taskState.finishedAt', function() {
if (this.taskState && this.taskState.finishedAt) {
return 'is-finished';
}
return;
}),
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-observers */
import Component from '@ember/component';
import { computed, observer } from '@ember/object';
import { computed as overridable } from 'ember-overridable-computed';

View File

@@ -25,6 +25,7 @@ export default Component.extend({
};
});
// eslint-disable-next-line ember/no-side-effects
this.set('stateCache', decoratedSource);
return decoratedSource;
}),

View File

@@ -29,12 +29,14 @@ export default Component.extend({
didReceiveAttrs() {
const dropdown = this.dropdown;
if (this.isOpen && dropdown) {
run.scheduleOnce('afterRender', () => {
dropdown.actions.reposition();
});
run.scheduleOnce('afterRender', this, this.repositionDropdown);
}
},
repositionDropdown() {
this.dropdown.actions.reposition();
},
actions: {
toggle({ key }) {
const newSelection = this.selection.slice();

View File

@@ -12,7 +12,7 @@ const FOCUSABLE = [
].join(', ');
export default Component.extend({
classnames: ['popover'],
classNames: ['popover'],
triggerClass: '',
isOpen: false,
@@ -31,12 +31,14 @@ export default Component.extend({
didReceiveAttrs() {
const dropdown = this.dropdown;
if (this.isOpen && dropdown) {
run.scheduleOnce('afterRender', () => {
dropdown.actions.reposition();
});
run.scheduleOnce('afterRender', this, this.repositionDropdown);
}
},
repositionDropdown() {
this.dropdown.actions.reposition();
},
actions: {
openOnArrowDown(dropdown, e) {
if (!this.isOpen && e.keyCode === ARROW_DOWN) {

View File

@@ -17,23 +17,25 @@ export default Component.extend(WindowResizable, {
return;
}
run.scheduleOnce('actions', () => {
switch (this.mode) {
case 'head':
this.head.perform();
break;
case 'tail':
this.tail.perform();
break;
case 'streaming':
if (this.isStreaming) {
this.stream.perform();
} else {
this.logger.stop();
}
break;
}
});
run.scheduleOnce('actions', this, this.performTask);
},
performTask() {
switch (this.mode) {
case 'head':
this.head.perform();
break;
case 'tail':
this.tail.perform();
break;
case 'streaming':
if (this.isStreaming) {
this.stream.perform();
} else {
this.logger.stop();
}
break;
}
},
didInsertElement() {
@@ -54,17 +56,16 @@ export default Component.extend(WindowResizable, {
head: task(function*() {
yield this.get('logger.gotoHead').perform();
run.scheduleOnce('afterRender', () => {
this.element.scrollTop = 0;
});
run.scheduleOnce('afterRender', this, this.scrollToTop);
}),
scrollToTop() {
this.element.scrollTop = 0;
},
tail: task(function*() {
yield this.get('logger.gotoTail').perform();
run.scheduleOnce('afterRender', () => {
const cliWindow = this.element;
cliWindow.scrollTop = cliWindow.scrollHeight;
});
run.scheduleOnce('afterRender', this, this.synchronizeScrollPosition, [true]);
}),
synchronizeScrollPosition(force = false) {
@@ -78,7 +79,7 @@ export default Component.extend(WindowResizable, {
stream: task(function*() {
// Force the scroll position to the bottom of the window when starting streaming
this.logger.one('tick', () => {
run.scheduleOnce('afterRender', () => this.synchronizeScrollPosition(true));
run.scheduleOnce('afterRender', this, this.synchronizeScrollPosition, [true]);
});
// Follow the log if the scroll position is near the bottom of the cli window
@@ -89,7 +90,7 @@ export default Component.extend(WindowResizable, {
}),
scheduleScrollSynchronization() {
run.scheduleOnce('afterRender', () => this.synchronizeScrollPosition());
run.scheduleOnce('afterRender', this, this.synchronizeScrollPosition);
},
willDestroy() {

View File

@@ -28,7 +28,7 @@ export default Component.extend({
mode: 'stdout',
logUrl: computed('allocation.id', 'allocation.node.httpAddr', 'useServer', function() {
logUrl: computed('allocation.{id,node.httpAddr}', 'useServer', function() {
const address = this.get('allocation.node.httpAddr');
const allocation = this.get('allocation.id');

View File

@@ -19,7 +19,9 @@ export default Component.extend({
// Internal state
statsError: false,
enablePolling: computed(() => !Ember.testing),
enablePolling: computed(function() {
return !Ember.testing;
}),
// Since all tasks for an allocation share the same tracker, use the registry
stats: computed('task', 'task.isRunning', function() {

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-observers */
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { computed, observer } from '@ember/object';

View File

@@ -15,7 +15,7 @@ export default Controller.extend({
}),
network: alias('model.resources.networks.firstObject'),
ports: computed('network.reservedPorts.[]', 'network.dynamicPorts.[]', function() {
ports: computed('network.{reservedPorts.[],dynamicPorts.[]}', function() {
return (this.get('network.reservedPorts') || [])
.map(port => ({
name: port.Label,

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-observers */
import { inject as service } from '@ember/service';
import Controller from '@ember/controller';
import { run } from '@ember/runloop';

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-observers */
import { alias } from '@ember/object/computed';
import Controller from '@ember/controller';
import { computed, observer } from '@ember/object';
@@ -24,7 +25,9 @@ export default Controller.extend(Sortable, Searchable, {
sortProperty: 'modifyIndex',
sortDescending: true,
searchProps: computed(() => ['shortId', 'name']),
searchProps: computed(function() {
return ['shortId', 'name'];
}),
onlyPreemptions: false,

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */
import { alias, readOnly } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Controller, { inject as controller } from '@ember/controller';
@@ -35,7 +36,9 @@ export default Controller.extend(
sortProperty: 'modifyIndex',
sortDescending: true,
searchProps: computed(() => ['id', 'name', 'datacenter']),
searchProps: computed(function() {
return ['id', 'name', 'datacenter'];
}),
qpClass: '',
qpState: '',
@@ -54,25 +57,29 @@ export default Controller.extend(
// Remove any invalid node classes from the query param/selection
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set('qpClass', serialize(intersection(classes, this.selectionClass)));
});
return classes.sort().map(dc => ({ key: dc, label: dc }));
}),
optionsState: computed(() => [
{ key: 'initializing', label: 'Initializing' },
{ key: 'ready', label: 'Ready' },
{ key: 'down', label: 'Down' },
{ key: 'ineligible', label: 'Ineligible' },
{ key: 'draining', label: 'Draining' },
]),
optionsState: computed(function() {
return [
{ key: 'initializing', label: 'Initializing' },
{ key: 'ready', label: 'Ready' },
{ key: 'down', label: 'Down' },
{ key: 'ineligible', label: 'Ineligible' },
{ key: 'draining', label: 'Draining' },
];
}),
optionsDatacenter: computed('nodes.[]', function() {
const datacenters = Array.from(new Set(this.nodes.mapBy('datacenter'))).compact();
// Remove any invalid datacenters from the query param/selection
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set('qpDatacenter', serialize(intersection(datacenters, this.selectionDatacenter)));
});
@@ -86,6 +93,7 @@ export default Controller.extend(
const volumes = Array.from(new Set(allVolumes.mapBy('name')));
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set('qpVolume', serialize(intersection(volumes, this.selectionVolume)));
});

View File

@@ -30,8 +30,12 @@ export default Controller.extend(
currentPage: 1,
pageSize: readOnly('userSettings.pageSize'),
searchProps: computed(() => ['id']),
fuzzySearchProps: computed(() => ['id']),
searchProps: computed(function() {
return ['id'];
}),
fuzzySearchProps: computed(function() {
return ['id'];
}),
sortProperty: 'id',
sortDescending: false,

View File

@@ -29,24 +29,21 @@ export default Controller.extend(SortableFactory(['updateTime', 'healthy']), {
selectionType: selection('qpType'),
selectionHealth: selection('qpHealth'),
optionsType: computed(() => [
{ key: 'controller', label: 'Controller' },
{ key: 'node', label: 'Node' },
]),
optionsType: computed(function() {
return [{ key: 'controller', label: 'Controller' }, { key: 'node', label: 'Node' }];
}),
optionsHealth: computed(() => [
{ key: 'true', label: 'Healthy' },
{ key: 'false', label: 'Unhealthy' },
]),
optionsHealth: computed(function() {
return [{ key: 'true', label: 'Healthy' }, { key: 'false', label: 'Unhealthy' }];
}),
combinedAllocations: computed('model.controllers.[]', 'model.nodes.[]', function() {
combinedAllocations: computed('model.{controllers.[],nodes.[]}', function() {
return this.model.controllers.toArray().concat(this.model.nodes.toArray());
}),
filteredAllocations: computed(
'combinedAllocations.[]',
'model.controllers.[]',
'model.nodes.[]',
'model.{controllers.[],nodes.[]}',
'selectionType',
'selectionHealth',
function() {

View File

@@ -35,14 +35,18 @@ export default Controller.extend(
sortProperty: 'id',
sortDescending: false,
searchProps: computed(() => ['name']),
fuzzySearchProps: computed(() => ['name']),
searchProps: computed(function() {
return ['name'];
}),
fuzzySearchProps: computed(function() {
return ['name'];
}),
fuzzySearchEnabled: true,
/**
Visible volumes are those that match the selected namespace
*/
visibleVolumes: computed('model.[]', 'model.@each.parent', function() {
visibleVolumes: computed('model.{[],@each.parent}', function() {
if (!this.model) return [];
// Namespace related properties are ommitted from the dependent keys

View File

@@ -43,14 +43,12 @@ export default Controller.extend({
allocations: alias('model.allocations'),
taskState: computed(
'allocations.[]',
'allocations.{[],@each.isActive}',
'allocationShortId',
'allocations.@each.isActive',
'taskName',
'taskGroupName',
'allocation',
'allocation.states.@each.name',
'allocation.states.@each.isRunning',
'allocation.states.@each.{name,isRunning}',
function() {
if (!this.allocations) {
return false;
@@ -72,6 +70,8 @@ export default Controller.extend({
if (allocation) {
return allocation.states.find(state => state.name === this.taskName);
}
return;
}
),

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/no-incorrect-calls-with-inline-anonymous-functions */
import { inject as service } from '@ember/service';
import { alias, readOnly } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
@@ -32,8 +33,12 @@ export default Controller.extend(Sortable, Searchable, {
sortProperty: 'modifyIndex',
sortDescending: true,
searchProps: computed(() => ['id', 'name']),
fuzzySearchProps: computed(() => ['name']),
searchProps: computed(function() {
return ['id', 'name'];
}),
fuzzySearchProps: computed(function() {
return ['name'];
}),
fuzzySearchEnabled: true,
qpType: '',
@@ -46,19 +51,23 @@ export default Controller.extend(Sortable, Searchable, {
selectionDatacenter: selection('qpDatacenter'),
selectionPrefix: selection('qpPrefix'),
optionsType: computed(() => [
{ key: 'batch', label: 'Batch' },
{ key: 'parameterized', label: 'Parameterized' },
{ key: 'periodic', label: 'Periodic' },
{ key: 'service', label: 'Service' },
{ key: 'system', label: 'System' },
]),
optionsType: computed(function() {
return [
{ key: 'batch', label: 'Batch' },
{ key: 'parameterized', label: 'Parameterized' },
{ key: 'periodic', label: 'Periodic' },
{ key: 'service', label: 'Service' },
{ key: 'system', label: 'System' },
];
}),
optionsStatus: computed(() => [
{ key: 'pending', label: 'Pending' },
{ key: 'running', label: 'Running' },
{ key: 'dead', label: 'Dead' },
]),
optionsStatus: computed(function() {
return [
{ key: 'pending', label: 'Pending' },
{ key: 'running', label: 'Running' },
{ key: 'dead', label: 'Dead' },
];
}),
optionsDatacenter: computed('visibleJobs.[]', function() {
const flatten = (acc, val) => acc.concat(val);
@@ -67,6 +76,7 @@ export default Controller.extend(Sortable, Searchable, {
// Remove any invalid datacenters from the query param/selection
const availableDatacenters = Array.from(allDatacenters).compact();
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set(
'qpDatacenter',
serialize(intersection(availableDatacenters, this.selectionDatacenter))
@@ -103,6 +113,7 @@ export default Controller.extend(Sortable, Searchable, {
// Remove any invalid prefixes from the query param/selection
const availablePrefixes = prefixes.mapBy('prefix');
scheduleOnce('actions', () => {
// eslint-disable-next-line ember/no-side-effects
this.set('qpPrefix', serialize(intersection(availablePrefixes, this.selectionPrefix)));
});
@@ -117,7 +128,7 @@ export default Controller.extend(Sortable, Searchable, {
Visible jobs are those that match the selected namespace and aren't children
of periodic or parameterized jobs.
*/
visibleJobs: computed('model.[]', 'model.@each.parent', function() {
visibleJobs: computed('model.{[],@each.parent}', function() {
// Namespace related properties are ommitted from the dependent keys
// due to a prop invalidation bug caused by region switching.
const hasNamespaces = this.get('system.namespaces.length');

View File

@@ -21,7 +21,9 @@ export default Controller.extend(Sortable, Searchable, WithNamespaceResetting, {
job: alias('model'),
searchProps: computed(() => ['shortId', 'name', 'taskGroupName']),
searchProps: computed(function() {
return ['shortId', 'name', 'taskGroupName'];
}),
allocations: computed('model.allocations.[]', function() {
return this.get('model.allocations') || [];

View File

@@ -22,7 +22,9 @@ export default Controller.extend(Sortable, Searchable, WithNamespaceResetting, {
sortProperty: 'modifyIndex',
sortDescending: true,
searchProps: computed(() => ['shortId', 'name']),
searchProps: computed(function() {
return ['shortId', 'name'];
}),
allocations: computed('model.allocations.[]', function() {
return this.get('model.allocations') || [];

View File

@@ -22,9 +22,12 @@ import Fuse from 'fuse.js';
Properties provided:
- listSearched: a subset of listToSearch of items that meet the search criteria
*/
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
searchTerm: '',
listToSearch: computed(() => []),
listToSearch: computed(function() {
return [];
}),
searchProps: null,
exactMatchSearchProps: reads('searchProps'),
@@ -83,11 +86,7 @@ export default Mixin.create({
if (this.exactMatchEnabled) {
results.push(
...exactMatchSearch(
searchTerm,
this.listToSearch,
this.exactMatchSearchProps
)
...exactMatchSearch(searchTerm, this.listToSearch, this.exactMatchSearchProps)
);
}
@@ -96,9 +95,7 @@ export default Mixin.create({
}
if (this.regexEnabled) {
results.push(
...regexSearch(searchTerm, this.listToSearch, this.regexSearchProps)
);
results.push(...regexSearch(searchTerm, this.listToSearch, this.regexSearchProps));
}
return results.uniq();

View File

@@ -21,11 +21,14 @@ import { warn } from '@ember/debug';
export default function sortableFactory(properties, fromSortableMixin) {
const eachProperties = properties.map(property => `listToSort.@each.${property}`);
// eslint-disable-next-line ember/no-new-mixins
return Mixin.create({
// Override in mixin consumer
sortProperty: null,
sortDescending: true,
listToSort: computed(() => []),
listToSort: computed(function() {
return [];
}),
_sortableFactoryWarningPrinted: false,
@@ -44,6 +47,7 @@ export default function sortableFactory(properties, fromSortableMixin) {
}
warn(message, properties.length > 0, { id: 'nomad.no-sortable-properties' });
// eslint-disable-next-line ember/no-side-effects
this.set('_sortableFactoryWarningPrinted', true);
}

View File

@@ -3,18 +3,21 @@ import { run } from '@ember/runloop';
import { assert } from '@ember/debug';
import { on } from '@ember/object/evented';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
windowResizeHandler() {
assert('windowResizeHandler needs to be overridden in the Component', false);
},
setupWindowResize: on('didInsertElement', function() {
run.scheduleOnce('afterRender', this, () => {
this.set('_windowResizeHandler', this.windowResizeHandler.bind(this));
window.addEventListener('resize', this._windowResizeHandler);
});
run.scheduleOnce('afterRender', this, this.addResizeListener);
}),
addResizeListener() {
this.set('_windowResizeHandler', this.windowResizeHandler.bind(this));
window.addEventListener('resize', this._windowResizeHandler);
},
removeWindowResize: on('willDestroyElement', function() {
window.removeEventListener('resize', this._windowResizeHandler);
}),

View File

@@ -3,6 +3,7 @@ import Mixin from '@ember/object/mixin';
import { assert } from '@ember/debug';
import { on } from '@ember/object/evented';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
visibilityHandler() {
assert('visibilityHandler needs to be overridden in the Component', false);

View File

@@ -1,5 +1,6 @@
import Mixin from '@ember/object/mixin';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
setupController(controller) {
if (this.isForbidden) {

View File

@@ -1,6 +1,7 @@
import Mixin from '@ember/object/mixin';
import notifyError from 'nomad-ui/utils/notify-error';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
model() {
return this._super(...arguments).catch(notifyError(this));

View File

@@ -1,6 +1,7 @@
import { inject as service } from '@ember/service';
import Mixin from '@ember/object/mixin';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
system: service(),

View File

@@ -2,6 +2,7 @@ import { inject as controller } from '@ember/controller';
import { inject as service } from '@ember/service';
import Mixin from '@ember/object/mixin';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
system: service(),
jobsController: controller('jobs'),

View File

@@ -3,6 +3,7 @@ import Mixin from '@ember/object/mixin';
import { assert } from '@ember/debug';
import { on } from '@ember/object/evented';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
visibilityHandler() {
assert('visibilityHandler needs to be overridden in the Route', false);

View File

@@ -3,8 +3,11 @@ import { computed } from '@ember/object';
import { assert } from '@ember/debug';
import WithVisibilityDetection from './with-route-visibility-detection';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create(WithVisibilityDetection, {
watchers: computed(() => []),
watchers: computed(function() {
return [];
}),
cancelAllWatchers() {
this.watchers.forEach(watcher => {

View File

@@ -1,4 +1,4 @@
import { alias, equal, or, and } from '@ember/object/computed';
import { alias, equal, or, and, mapBy } from '@ember/object/computed';
import { computed } from '@ember/object';
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
@@ -61,8 +61,7 @@ export default Model.extend({
'type',
'periodic',
'parameterized',
'parent.periodic',
'parent.parameterized',
'parent.{periodic,parameterized}',
function() {
const type = this.type;
@@ -131,9 +130,11 @@ export default Model.extend({
.uniq();
}),
allocationsUnhealthyDrivers: mapBy('allocations', 'unhealthyDrivers'),
// Getting all unhealthy drivers for a job can be incredibly expensive if the job
// has many allocations. This can lead to making an API request for many nodes.
unhealthyDrivers: computed('allocations.@each.unhealthyDrivers.[]', function() {
unhealthyDrivers: computed('allocationsUnhealthyDrivers.[]', function() {
return this.allocations
.mapBy('unhealthyDrivers')
.reduce((all, drivers) => {
@@ -149,7 +150,7 @@ export default Model.extend({
hasPlacementFailures: and('latestFailureEvaluation', 'hasBlockedEvaluation'),
latestEvaluation: computed('evaluations.@each.modifyIndex', 'evaluations.isPending', function() {
latestEvaluation: computed('evaluations.{@each.modifyIndex,isPending}', function() {
const evaluations = this.evaluations;
if (!evaluations || evaluations.get('isPending')) {
return null;
@@ -157,21 +158,19 @@ export default Model.extend({
return evaluations.sortBy('modifyIndex').get('lastObject');
}),
latestFailureEvaluation: computed(
'evaluations.@each.modifyIndex',
'evaluations.isPending',
function() {
const evaluations = this.evaluations;
if (!evaluations || evaluations.get('isPending')) {
return null;
}
const failureEvaluations = evaluations.filterBy('hasPlacementFailures');
if (failureEvaluations) {
return failureEvaluations.sortBy('modifyIndex').get('lastObject');
}
latestFailureEvaluation: computed('evaluations.{@each.modifyIndex,isPending}', function() {
const evaluations = this.evaluations;
if (!evaluations || evaluations.get('isPending')) {
return null;
}
),
const failureEvaluations = evaluations.filterBy('hasPlacementFailures');
if (failureEvaluations) {
return failureEvaluations.sortBy('modifyIndex').get('lastObject');
}
return;
}),
supportsDeployments: equal('type', 'service'),
@@ -180,6 +179,7 @@ export default Model.extend({
runningDeployment: computed('latestDeployment', 'latestDeployment.isRunning', function() {
const latest = this.latestDeployment;
if (latest.get('isRunning')) return latest;
return;
}),
fetchRawDefinition() {

View File

@@ -62,6 +62,8 @@ export default Model.extend({
if (allocation) {
return allocation.modifyTime;
}
return;
}),
drivers: fragmentArray('node-driver'),

View File

@@ -8,12 +8,12 @@ export default Route.extend(WithForbiddenState, {
store: service(),
system: service(),
breadcrumbs: [
breadcrumbs: Object.freeze([
{
label: 'Clients',
args: ['clients.index'],
},
],
]),
beforeModel() {
return this.get('system.leader');

View File

@@ -6,12 +6,12 @@ import notifyForbidden from 'nomad-ui/utils/notify-forbidden';
export default Route.extend(WithForbiddenState, {
store: service(),
breadcrumbs: [
breadcrumbs: Object.freeze([
{
label: 'Storage',
args: ['csi.index'],
},
],
]),
model() {
return this.store.query('plugin', { type: 'csi' }).catch(notifyForbidden(this));

View File

@@ -7,12 +7,12 @@ export default Route.extend(WithForbiddenState, {
system: service(),
store: service(),
breadcrumbs: [
breadcrumbs: Object.freeze([
{
label: 'Storage',
args: ['csi.index'],
},
],
]),
queryParams: {
volumeNamespace: {

View File

@@ -7,12 +7,12 @@ export default Route.extend(WithForbiddenState, {
system: service(),
store: service(),
breadcrumbs: [
breadcrumbs: Object.freeze([
{
label: 'Jobs',
args: ['jobs.index'],
},
],
]),
queryParams: {
jobNamespace: {

View File

@@ -6,12 +6,12 @@ export default Route.extend({
store: service(),
system: service(),
breadcrumbs: [
breadcrumbs: Object.freeze([
{
label: 'Run',
args: ['jobs.run'],
},
],
]),
beforeModel() {
if (this.can.cannot('run job')) {

View File

@@ -8,12 +8,12 @@ export default Route.extend(WithForbiddenState, {
store: service(),
system: service(),
breadcrumbs: [
breadcrumbs: Object.freeze([
{
label: 'Servers',
args: ['servers.index'],
},
],
]),
beforeModel() {
return this.get('system.leader');

View File

@@ -6,7 +6,7 @@ export default ApplicationSerializer.extend({
externalId: 'ExternalID',
},
embeddedRelationships: ['writeAllocations', 'readAllocations'],
embeddedRelationships: Object.freeze(['writeAllocations', 'readAllocations']),
// Volumes treat Allocations as embedded records. Ember has an
// EmbeddedRecords mixin, but it assumes an application is using

View File

@@ -9,7 +9,7 @@ export default Service.extend({
// currentRouteName has all information necessary to compute breadcrumbs,
// but it doesn't change when a transition to the same route with a different
// model occurs.
breadcrumbs: computed('router.currentURL', 'router.currentRouteName', function() {
breadcrumbs: computed('router.{currentURL,currentRouteName}', function() {
const owner = getOwner(this);
const allRoutes = (this.get('router.currentRouteName') || '')
.split('.')

View File

@@ -28,7 +28,9 @@ export default Service.extend({
// A read-only way of getting a reference to the registry.
// Since this could be overwritten by a bad actor, it isn't
// used in getTracker
registryRef: computed(() => registry),
registryRef: computed(function() {
return registry;
}),
getTracker(resource) {
if (!resource) return;

View File

@@ -59,6 +59,7 @@ export default Service.extend({
set(key, value) {
if (value == null) {
window.localStorage.removeItem('nomadActiveRegion');
return;
} else {
// All localStorage values are strings. Stringify first so
// the return value is consistent with what is persisted.
@@ -110,6 +111,7 @@ export default Service.extend({
set(key, value) {
if (value == null) {
window.localStorage.removeItem('nomadActiveNamespace');
return;
} else if (typeof value === 'string') {
window.localStorage.nomadActiveNamespace = value;
return this.namespaces.findBy('id', value);

View File

@@ -43,6 +43,7 @@ export default Service.extend({
selfToken: computed('secret', 'fetchSelfToken.lastSuccessful.value', function() {
if (this.secret) return this.get('fetchSelfToken.lastSuccessful.value');
return;
}),
fetchSelfTokenPolicies: task(function*() {

View File

@@ -6,7 +6,9 @@ import Service from '@ember/service';
let list = {};
export default Service.extend({
_list: computed(() => copy(list, true)),
_list: computed(function() {
return copy(list, true);
}),
list: readOnly('_list'),

View File

@@ -7,6 +7,7 @@ import queryString from 'query-string';
const MAX_OUTPUT_LENGTH = 50000;
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
url: '',
params: overridable(() => ({})),

View File

@@ -4,6 +4,7 @@ import { assert } from '@ember/debug';
import { task, timeout } from 'ember-concurrency';
import jsonWithDefault from 'nomad-ui/utils/json-with-default';
// eslint-disable-next-line ember/no-new-mixins
export default Mixin.create({
url: '',

View File

@@ -8,9 +8,11 @@ import { fetchFailure } from './log';
export default EmberObject.extend(AbstractLogger, {
reader: null,
additionalParams: computed(() => ({
follow: true,
})),
additionalParams: computed(function() {
return {
follow: true,
};
}),
start() {
return this.poll.perform();

View File

@@ -1,4 +1,3 @@
import Ember from 'ember';
import fetch from 'fetch';
import config from '../config/environment';
@@ -9,6 +8,6 @@ const mirageEnabled =
config['ember-cli-mirage'] &&
config['ember-cli-mirage'].enabled !== false;
const fetchToUse = Ember.testing || mirageEnabled ? fetch : window.fetch || fetch;
const fetchToUse = mirageEnabled ? fetch : window.fetch || fetch;
export default fetchToUse;

View File

@@ -95,6 +95,7 @@
"ember-test-selectors": "^2.1.0",
"ember-truth-helpers": "^2.0.0",
"eslint": "^5.16.0",
"eslint-plugin-ember": "^6.2.0",
"eslint-plugin-node": "^9.0.1",
"faker": "^4.1.0",
"flat": "^4.0.0",

View File

@@ -111,7 +111,7 @@ export let LiveUpdating = () => {
clearInterval(this.timer);
},
distributionBarDataRotating: computed('timerTicks', () => {
distributionBarDataRotating: computed('timerTicks', function() {
return [
{ label: 'one', value: Math.round(Math.random() * 50) },
{ label: 'two', value: Math.round(Math.random() * 50) },

View File

@@ -92,6 +92,8 @@ export let LiveData = () => {
context: {
controller: EmberObject.extend({
startTimer: on('init', function() {
this.lineChartLive = [];
this.set(
'timer',
setInterval(() => {
@@ -110,8 +112,6 @@ export let LiveData = () => {
clearInterval(this.timer);
},
lineChartLive: [],
secondsFormat() {
return date => moment(date).format('HH:mm:ss');
},

View File

@@ -102,11 +102,11 @@ export let HighLowComparison = () => {
clearInterval(this.timer);
},
metricsHigh: computed(() => {
metricsHigh: computed(function() {
return [];
}),
metricsLow: computed(() => {
metricsLow: computed(function() {
return [];
}),

View File

@@ -240,7 +240,7 @@ export let SortableColumns = () => {
})
),
sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() {
sortedShortList: computed('controller.{sortProperty,sortDescending}', function() {
let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name');
return this.get('controller.sortDescending') ? sorted.reverse() : sorted;
}),
@@ -278,7 +278,7 @@ export let MultiRow = () => {
})
),
sortedShortList: computed('controller.sortProperty', 'controller.sortDescending', function() {
sortedShortList: computed('controller.{sortProperty,sortDescending}', function() {
let sorted = productMetadata.sortBy(this.get('controller.sortProperty') || 'name');
return this.get('controller.sortDescending') ? sorted.reverse() : sorted;
}),

View File

@@ -11,7 +11,10 @@ module('Integration | Component | app breadcrumbs', function(hooks) {
hooks.beforeEach(function() {
const mockBreadcrumbs = Service.extend({
breadcrumbs: [],
init() {
this._super(...arguments);
this.breadcrumbs = [];
},
});
this.owner.register('service:breadcrumbs', mockBreadcrumbs);

View File

@@ -31,8 +31,12 @@ module('Integration | Component | primary metric', function(hooks) {
yield trackerSignalPauseSpy();
}),
cpu: computed(() => []),
memory: computed(() => []),
cpu: computed(function() {
return [];
}),
memory: computed(function() {
return [];
}),
});
const mockStatsTrackersRegistry = Service.extend({

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import Service from '@ember/service';

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import Service from '@ember/service';

View File

@@ -1,3 +1,4 @@
/* eslint-disable ember/avoid-leaking-state-in-ember-objects */
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import Service from '@ember/service';

View File

@@ -9,9 +9,12 @@ module('Unit | Mixin | Searchable', function(hooks) {
hooks.beforeEach(function() {
this.subject = function() {
// eslint-disable-next-line ember/no-new-mixins
const SearchableObject = EmberObject.extend(Searchable, {
source: null,
searchProps: computed(() => ['id', 'name']),
searchProps: computed(function() {
return ['id', 'name'];
}),
listToSearch: alias('source'),
});
@@ -193,9 +196,12 @@ module('Unit | Mixin | Searchable (with pagination)', function(hooks) {
hooks.beforeEach(function() {
this.subject = function() {
// eslint-disable-next-line ember/no-new-mixins
const SearchablePaginatedObject = EmberObject.extend(Searchable, {
source: null,
searchProps: computed(() => ['id', 'name']),
searchProps: computed(function() {
return ['id', 'name'];
}),
listToSearch: alias('source'),
currentPage: 1,
});

View File

@@ -10,11 +10,11 @@ import { settled } from '@ember/test-helpers';
let startSpy, stopSpy, initSpy, fetchSpy;
const MockStreamer = EmberObject.extend({
poll: {
isRunning: false,
},
init() {
this.poll = {
isRunning: false,
};
initSpy(...arguments);
},

View File

@@ -1279,6 +1279,11 @@
ember-cli-typescript "^2.0.2"
inflection "1.12.0"
"@ember-data/rfc395-data@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@ember-data/rfc395-data/-/rfc395-data-0.0.4.tgz#ecb86efdf5d7733a76ff14ea651a1b0ed1f8a843"
integrity sha512-tGRdvgC9/QMQSuSuJV45xoyhI0Pzjm7A9o/MVVA3HakXIImJbbzx/k/6dO9CUEQXIyS2y0fW6C1XaYOG7rY0FQ==
"@ember-data/serializer@3.12.4":
version "3.12.4"
resolved "https://registry.yarnpkg.com/@ember-data/serializer/-/serializer-3.12.4.tgz#e3ae7143f0cd736722daa3a640e11dc07c8aef5a"
@@ -7427,6 +7432,11 @@ ember-responsive@^3.0.4:
dependencies:
ember-cli-babel "^6.6.0"
ember-rfc176-data@^0.3.11:
version "0.3.13"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.13.tgz#ed1712a26e65fec703655f35410414aa1982cf3b"
integrity sha512-m9JbwQlT6PjY7x/T8HslnXP7Sz9bx/pz3FrNfNi2NesJnbNISly0Lix6NV1fhfo46572cpq4jrM+/6yYlMefTQ==
ember-rfc176-data@^0.3.12, ember-rfc176-data@^0.3.9:
version "0.3.12"
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.12.tgz#90d82878e69e2ac9a5438e8ce14d12c6031c5bd2"
@@ -7714,6 +7724,15 @@ escodegen@^1.11.0:
optionalDependencies:
source-map "~0.6.1"
eslint-plugin-ember@^6.2.0:
version "6.10.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-ember/-/eslint-plugin-ember-6.10.1.tgz#ca7a5cc28b91a247c31b1686421a66281467f238"
integrity sha512-RZI0+UoR4xeD6UE3KQCUwbN2nZOIIPaFCCXqBIRXDr0rFuwvknAHqYtDPJVZicvTzNHa4TEZvAKqfbE8t7SztQ==
dependencies:
"@ember-data/rfc395-data" "^0.0.4"
ember-rfc176-data "^0.3.11"
snake-case "^2.1.0"
eslint-plugin-es@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz#12acae0f4953e76ba444bfd1b2271081ac620998"
@@ -13960,6 +13979,13 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
snake-case@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-2.1.0.tgz#41bdb1b73f30ec66a04d4e2cad1b76387d4d6d9f"
integrity sha1-Qb2xtz8w7GagTU4srRt2OH1NbZ8=
dependencies:
no-case "^2.2.0"
snapdragon-node@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"